Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { shallowMount, mount } from '@opencloud-eu/web-test-helpers'
import { mount, shallowMount } from '@opencloud-eu/web-test-helpers'
import Drop from './OcDrop.vue'
import { getSizeClass } from '../../helpers'

Expand Down
34 changes: 19 additions & 15 deletions packages/design-system/src/components/OcDrop/OcDrop.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,12 @@ const hide = (duration?: number) => {

defineExpose({ show, hide, tippy: tippyInstance })

const onClick = (event) => {
const isNestedToggle = event.target.closest('.oc-drop-nested-toggle')
const onClick = (event: Event) => {
const isNestedDropToggle = (event.target as HTMLElement)
.closest('.oc-button')
?.hasAttribute('aria-expanded')

if (closeOnClick && !isNestedToggle) {
if (closeOnClick && !isNestedDropToggle) {
hide()
}
}
Expand All @@ -148,14 +150,19 @@ onBeforeUnmount(() => {
})

const isTouchDevice = () => {
return window.matchMedia('(hover: none) and (pointer: coarse)').matches
return window.matchMedia?.('(hover: none) and (pointer: coarse)')?.matches
}

const triggerMapping = computed(() => {
if (mode === 'hover') {
return isTouchDevice() ? 'click' : 'mouseenter focus'
}
return mode
return (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted to original

{
hover: 'mouseenter focus'
}[unref(dropMode)] || unref(dropMode)
)
})

const dropMode = computed(() => {
return isTouchDevice() ? 'click' : mode
})

const paddingClass = computed(() => {
Expand All @@ -169,12 +176,9 @@ watch(
}
)

watch(
() => mode,
() => {
unref(tippyInstance)?.setProps({ trigger: triggerMapping.value })
}
)
watch(dropMode, () => {
unref(tippyInstance)?.setProps({ trigger: triggerMapping.value })
})

onBeforeUnmount(() => {
destroy(unref(tippyInstance))
Expand All @@ -196,7 +200,7 @@ onMounted(() => {
trigger: triggerMapping.value,
placement: position,
arrow: false,
hideOnClick: true,
hideOnClick: !(isNested && unref(dropMode) === 'hover'),
interactive: true,
plugins: [hideOnEsc],
theme: 'none',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,31 @@
<template>
<li class="context-menu oc-files-context-action oc-px-s oc-rounded oc-menu-item-hover">
<oc-button
:id="`oc-files-context-actions-${menuSection.name}-toggle`"
:id="toggleId"
appearance="raw"
gap-size="medium"
class="oc-drop-nested-toggle oc-py-s oc-px-m oc-width-1-1 oc-flex-justify-between oc-width-1-1"
class="oc-py-s oc-px-m oc-width-1-1 oc-flex-justify-between oc-width-1-1"
>
<oc-icon :name="menuSection.drop.icon" size="medium" fill-type="line" />
<oc-icon :name="menuSectionDrop.icon" size="medium" fill-type="line" />
<span class="oc-flex oc-files-context-action-label">
<span v-text="menuSection.drop.label" />
<span v-text="menuSectionDrop.label" />
</span>
<oc-icon name="arrow-right-s" size="small" fill-type="line" />
</oc-button>
<oc-drop
:toggle="`#oc-files-context-actions-${menuSection.name}-toggle`"
:drop-id="dropId"
:toggle="`#${toggleId}`"
:is-nested="true"
mode="hover"
class="oc-width-auto"
class="oc-width-auto oc-files-context-action-drop"
padding-size="small"
position="auto-start"
close-on-click
>
<template v-if="menuSection.drop.items.length">
<template v-if="menuSectionDrop.items.length">
<action-menu-item
v-for="(action, actionIndex) in menuSection.drop.items"
:key="`section-${menuSection.name}-action-${actionIndex}`"
v-for="(action, actionIndex) in menuSectionDrop.items"
:key="`section-${menuSectionDrop.label}-action-${actionIndex}`"
:action="action"
:appearance="appearance"
:action-options="actionOptions"
Expand All @@ -33,22 +34,25 @@
</template>
<span
v-else
class="oc-py-s oc-px-m oc-text-muted"
v-text="menuSection.drop.emptyMessage || $gettext('No items available')"
class="oc-files-context-action-drop-empty-message oc-py-s oc-px-m oc-text-muted"
v-text="menuSectionDrop.emptyMessage || $gettext('No items available')"
/>
</oc-drop>
</li>
</template>

<script setup lang="ts">
import ActionMenuItem from './ActionMenuItem.vue'
import type { AppearanceType } from '@opencloud-eu/design-system/helpers'
import { AppearanceType, uniqueId } from '@opencloud-eu/design-system/helpers'
import type { ActionOptions } from '../../composables'
import type { MenuSection } from './ContextActionMenu.vue'
import type { MenuSectionDrop } from './ContextActionMenu.vue'

const { menuSection, appearance, actionOptions } = defineProps<{
menuSection: MenuSection
const { menuSectionDrop, appearance, actionOptions } = defineProps<{
menuSectionDrop: MenuSectionDrop
appearance: AppearanceType
actionOptions: ActionOptions
}>()

const dropId = uniqueId(`oc-files-context-actions-${menuSectionDrop.name}-drop-`)
const toggleId = uniqueId(`oc-files-context-actions-${menuSectionDrop.name}-toggle-`)
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
<script lang="ts">
import { computed, defineComponent, PropType, unref } from 'vue'
import { Action, ActionOptions, useConfigStore } from '../../composables'
import { useGettext } from 'vue3-gettext'
import { storeToRefs } from 'pinia'
import { AppearanceType } from '@opencloud-eu/design-system/helpers'

Expand Down Expand Up @@ -98,7 +97,6 @@ export default defineComponent({
}
},
setup(props) {
const { $gettext } = useGettext()
const configStore = useConfigStore()
const { options } = storeToRefs(configStore)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
</template>
<action-menu-drop-item
v-if="section.drop && (section.drop.items?.length || section.drop.renderOnEmpty)"
:menu-section="section"
:menu-section-drop="section.drop"
:appearance="appearance"
:action-options="actionOptions"
/>
Expand All @@ -36,6 +36,7 @@ import ActionMenuDropItem from './ActionMenuDropItem.vue'

export type MenuSectionDrop = {
label: string
name: string
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for e2e tests

icon: string
items?: Action[]
renderOnEmpty?: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
:drop-id="`context-menu-drop-${resourceDomSelector(item)}`"
:toggle="`#context-menu-trigger-${resourceDomSelector(item)}`"
position="bottom-end"
mode="hover"
mode="click"
padding-size="small"
close-on-click
>
Expand Down
5 changes: 3 additions & 2 deletions packages/web-pkg/src/components/FilesList/ContextActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</template>

<script lang="ts">
import ContextActionMenu from '../ContextActions/ContextActionMenu.vue'
import ContextActionMenu, { MenuSection } from '../ContextActions/ContextActionMenu.vue'
import { computed, defineComponent, PropType, Ref, toRef, unref } from 'vue'
import {
ActionExtension,
Expand Down Expand Up @@ -166,7 +166,7 @@ export default defineComponent({
})

const menuSections = computed(() => {
const sections = []
const sections: MenuSection[] = []
if (unref(actionOptions).resources.length > 1) {
if (unref(menuItemsBatchActions).length) {
sections.push({
Expand All @@ -186,6 +186,7 @@ export default defineComponent({
items: [...unref(menuItemsContext)],
drop: {
label: $gettext('Open with...'),
name: 'open-with',
icon: 'apps',
renderOnEmpty: !unref(actionOptions).resources[0]?.isFolder,
emptyMessage: $gettext('No applications available'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const useFileActionsNavigate = () => {
{
name: 'navigate',
icon: 'folder-open',
label: () => $gettext('Open folder'),
label: () => $gettext('Open'),
isVisible: ({ resources }) => {
if (isLocationTrashActive(router, 'files-trash-generic')) {
return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import ActionMenuDropItem from '../../../../src/components/ContextActions/ActionMenuDropItem.vue'
import { Action } from '../../../../src/composables/actions'
import { defaultPlugins, mount } from '@opencloud-eu/web-test-helpers'
import { MenuSectionDrop } from '../../../../src/components/ContextActions/ContextActionMenu.vue'

describe('ActionMenuDropItem component', () => {
it('renders drop menu with actions', () => {
const menuSectionDrop = {
label: 'Actions',
name: 'actions',
icon: 'eye',
items: [{ label: () => 'Copy' } as Action, { label: () => 'Paste' } as Action]
}
const { wrapper } = getWrapper(menuSectionDrop)
expect(wrapper.html()).toMatchSnapshot()
expect(
wrapper.find('.oc-files-context-action-drop').findAll('.oc-files-context-action').length
).toEqual(menuSectionDrop.items.length)
})
it('renders drop menu with empty message', () => {
const menuSectionDrop = {
label: 'Actions',
name: 'actions',
icon: 'eye',
renderOnEmpty: true,
items: []
}
const { wrapper } = getWrapper(menuSectionDrop)
expect(wrapper.find('.oc-files-context-action-drop-empty-message').exists()).toBeTruthy()
})
})

function getWrapper(menuSectionDrop: MenuSectionDrop) {
return {
wrapper: mount(ActionMenuDropItem, {
props: {
menuSectionDrop,
actionOptions: { resources: [] },
appearance: 'outline'
},
global: {
plugins: [...defaultPlugins()]
}
})
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
import ContextActionMenu from '../../../../src/components/ContextActions/ContextActionMenu.vue'
import { Action } from '../../../../src/composables/actions'
import { defaultPlugins, shallowMount } from '@opencloud-eu/web-test-helpers'
import { defaultPlugins, mount } from '@opencloud-eu/web-test-helpers'

describe('ContextActionMenu component', () => {
it('renders the menu with actions', () => {
const menuSections = [
{ name: 'action 1', items: [] as Action[] },
{ name: 'action 2', items: [] as Action[] }
{ name: 'section 1', items: [] as Action[] },
{ name: 'section 2', items: [] as Action[] }
]
const { wrapper } = getShallowWrapper(menuSections)
const { wrapper } = getWrapper(menuSections)
expect(wrapper.html()).toMatchSnapshot()
expect(wrapper.find('.oc-files-context-actions').exists()).toBeTruthy()
expect(wrapper.findAll('.oc-files-context-actions').length).toEqual(menuSections.length)
})

it('renders the menu with drop menu items', async () => {
const menuSections = [
{
name: 'apps',
items: [],
drop: {
label: 'Apps',
name: 'apps',
items: [{ label: () => 'Preview' } as Action, { label: () => 'PDF Viewer' } as Action]
}
},
{
name: 'actions',
items: [{ label: () => 'Download' } as Action],
drop: {
label: 'Actions',
name: 'actions',
items: [{ label: () => 'Copy' } as Action, { label: () => 'Paste' } as Action]
}
},
{
name: 'sidebar',
items: [{ label: () => 'Details' } as Action]
}
]
const { wrapper } = getWrapper(menuSections)
expect(wrapper.html()).toMatchSnapshot()

expect(wrapper.findAll('.oc-files-context-actions').length).toEqual(menuSections.length)
expect(wrapper.findAll('.oc-files-context-action-drop').length).toEqual(
menuSections.filter((m) => m.drop).length
)
})
})

function getShallowWrapper(menuSections: { name: string; items: Action[] }[]) {
function getWrapper(menuSections: { name: string; items: Action[] }[]) {
return {
wrapper: shallowMount(ContextActionMenu, {
wrapper: mount(ContextActionMenu, {
props: {
menuSections,
actionOptions: { resources: [] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ActionMenuDropItem component > renders drop menu with actions 1`] = `
"<li class="context-menu oc-files-context-action oc-px-s oc-rounded oc-menu-item-hover"><button type="button" class="oc-button oc-rounded oc-button-m oc-button-justify-content-center oc-button-gap-m oc-button-secondary oc-button-raw oc-button-secondary-raw oc-py-s oc-px-m oc-width-1-1 oc-flex-justify-between oc-width-1-1" id="oc-files-context-actions-actions-toggle-2">
<!--v-if-->
<!-- @slot Content of the button --> <span class="oc-icon oc-icon-m"><!----></span> <span class="oc-flex oc-files-context-action-label"><span>Actions</span></span> <span class="oc-icon oc-icon-s"><!----></span>
</button>
<div id="oc-files-context-actions-actions-drop-1" class="oc-drop oc-box-shadow-medium oc-rounded oc-width-auto oc-files-context-action-drop">
<div class="oc-card oc-card-body oc-p-s">
<li class="oc-files-context-action oc-rounded oc-menu-item-hover"><button type="button" aria-label="" class="oc-button oc-rounded oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-secondary oc-button-outline oc-button-secondary-outline action-menu-item oc-py-s oc-px-m oc-width-1-1" data-testid="action-handler">
<!--v-if-->
<!-- @slot Content of the button -->
<!--v-if--> <span class="oc-files-context-action-label oc-flex" data-testid="action-label"><span>Copy</span></span>
<!--v-if-->
</button></li>
<li class="oc-files-context-action oc-rounded oc-menu-item-hover"><button type="button" aria-label="" class="oc-button oc-rounded oc-button-m oc-button-justify-content-left oc-button-gap-m oc-button-secondary oc-button-outline oc-button-secondary-outline action-menu-item oc-py-s oc-px-m oc-width-1-1" data-testid="action-handler">
<!--v-if-->
<!-- @slot Content of the button -->
<!--v-if--> <span class="oc-files-context-action-label oc-flex" data-testid="action-label"><span>Paste</span></span>
<!--v-if-->
</button></li>
</div>
</div>
</li>"
`;
Loading