From 5ddc0faddc4a2bb1a079c9f272ea742a60ead195 Mon Sep 17 00:00:00 2001 From: Johannes Werner Date: Sat, 25 Sep 2021 21:39:14 +0200 Subject: [PATCH] feat(vue-menu): add disabled state for items --- .../components/data-display/_menu.scss | 1 + .../data-display/VueMenu/VueMenu.spec.ts | 48 +++++++++++++------ .../data-display/VueMenu/VueMenu.stories.ts | 2 +- .../data-display/VueMenu/VueMenu.vue | 21 ++++++-- src/interfaces/IItem.ts | 1 + 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/assets/design-system/variables/components/data-display/_menu.scss b/src/assets/design-system/variables/components/data-display/_menu.scss index 8743efd2..7a664982 100644 --- a/src/assets/design-system/variables/components/data-display/_menu.scss +++ b/src/assets/design-system/variables/components/data-display/_menu.scss @@ -14,6 +14,7 @@ $menu-item-bg-active: var(--brand-interaction-primary-hovered); $menu-item-color-active: var(--brand-text-inverse-high); $menu-item-icon-size: $space-20; $menu-item-icon-size-gap: $space-8; +$menu-item-disabled-opacity: 0.6; // separator $menu-separator-border: 1px solid var(--brand-border-default-low); diff --git a/src/components/data-display/VueMenu/VueMenu.spec.ts b/src/components/data-display/VueMenu/VueMenu.spec.ts index e2310f2a..9887120c 100644 --- a/src/components/data-display/VueMenu/VueMenu.spec.ts +++ b/src/components/data-display/VueMenu/VueMenu.spec.ts @@ -11,7 +11,7 @@ describe('VueMenu.vue', () => { { label: 'Value 1', value: 'Value 1', description: 'Description 1', leadingIcon: 'hashtag' }, { label: 'Value 2', value: 'Value 2', description: 'Description 2', trailingIcon: 'hashtag' }, { label: '', value: 'separator' }, - { label: 'Value 3', value: 'Value 3', description: 'Description 3', leadingIcon: 'hashtag' }, + { label: 'Value 3', value: 'Value 3', description: 'Description 3', leadingIcon: 'hashtag', disabled: true }, { label: 'Value 4', value: 'Value 4', description: 'Description 4', trailingIcon: 'hashtag' }, ], }, @@ -35,14 +35,15 @@ describe('VueMenu.vue', () => { test('should select 1st item and emit click event via keyboard', async () => { const { getByTestId, emitted } = harness; const firstItem = getByTestId('Value 1-0'); + const menu = firstItem.parentElement; - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowLeft', code: 'ArrowLeft' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowDown', code: 'ArrowDown' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowDown', code: 'ArrowDown' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowDown', code: 'ArrowDown' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowDown', code: 'ArrowDown' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowDown', code: 'ArrowDown' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'Enter', code: 'Enter' }); + await fireEvent.keyDown(menu, { key: 'ArrowLeft', code: 'ArrowLeft' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'Enter', code: 'Enter' }); expect(emitted().click).toEqual([ [ @@ -59,14 +60,15 @@ describe('VueMenu.vue', () => { test('should select last item and emit click event via keyboard', async () => { const { getByTestId, emitted } = harness; const firstItem = getByTestId('Value 1-0'); + const menu = firstItem.parentElement; - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowRight', code: 'ArrowRight' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowUp', code: 'ArrowUp' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowUp', code: 'ArrowUp' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowUp', code: 'ArrowUp' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowUp', code: 'ArrowUp' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'ArrowUp', code: 'ArrowUp' }); - await fireEvent.keyDown(firstItem.parentElement, { key: 'Enter', code: 'Enter' }); + await fireEvent.keyDown(menu, { key: 'ArrowRight', code: 'ArrowRight' }); + await fireEvent.keyDown(menu, { key: 'ArrowUp', code: 'ArrowUp' }); + await fireEvent.keyDown(menu, { key: 'ArrowUp', code: 'ArrowUp' }); + await fireEvent.keyDown(menu, { key: 'ArrowUp', code: 'ArrowUp' }); + await fireEvent.keyDown(menu, { key: 'ArrowUp', code: 'ArrowUp' }); + await fireEvent.keyDown(menu, { key: 'ArrowUp', code: 'ArrowUp' }); + await fireEvent.keyDown(menu, { key: 'Enter', code: 'Enter' }); expect(emitted().click).toEqual([ [ @@ -79,4 +81,20 @@ describe('VueMenu.vue', () => { ], ]); }); + + test('disabled item should not emit click event', async () => { + const { getByTestId, emitted } = harness; + const firstItem = getByTestId('Value 1-0'); + const menu = firstItem.parentElement; + + await fireEvent.keyDown(menu, { key: 'ArrowRight', code: 'ArrowRight' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'ArrowDown', code: 'ArrowDown' }); + await fireEvent.keyDown(menu, { key: 'Enter', code: 'Enter' }); + + await fireEvent.click(getByTestId('Value 3-3')); + + expect(emitted().click).toBeFalsy(); + }); }); diff --git a/src/components/data-display/VueMenu/VueMenu.stories.ts b/src/components/data-display/VueMenu/VueMenu.stories.ts index 468d831e..a374c942 100644 --- a/src/components/data-display/VueMenu/VueMenu.stories.ts +++ b/src/components/data-display/VueMenu/VueMenu.stories.ts @@ -14,7 +14,7 @@ story.add( return { items: [ { label: 'Value 1', value: 'Value 1', description: 'Description 1', leadingIcon: 'cog' }, - { label: 'Value 2', value: 'Value 2', description: 'Description 2', trailingIcon: 'times' }, + { label: 'Value 2', value: 'Value 2', description: 'Description 2', trailingIcon: 'times', disabled: true }, { label: '', value: 'separator' }, { label: 'Value 3', value: 'Value 3', description: 'Description 3', leadingIcon: 'cog' }, { label: 'Value 4', value: 'Value 4', description: 'Description 4', trailingIcon: 'times' }, diff --git a/src/components/data-display/VueMenu/VueMenu.vue b/src/components/data-display/VueMenu/VueMenu.vue index 9c0dfff6..742f580c 100644 --- a/src/components/data-display/VueMenu/VueMenu.vue +++ b/src/components/data-display/VueMenu/VueMenu.vue @@ -4,13 +4,17 @@ v-for="(item, idx) in items" :key="`${item.value}-${idx}`" :data-testid="`${item.value}-${idx}`" - :class="[selectedItemIndex === idx ? $style.active : '', item.value === 'separator' ? $style.separator : '']" + :class="[ + selectedItemIndex === idx ? $style.active : '', + item.value === 'separator' ? $style.separator : '', + item.disabled && $style.disabled, + ]" :tabindex="item.value === 'separator' ? -1 : 0" @mouseenter="selectedItemIndex = idx" @mouseleave="selectedItemIndex = -1" @focus="selectedItemIndex = idx" @blur="selectedItemIndex = -1" - @click.stop.prevent="onItemClick(item)" + @click.stop.prevent="item.disabled ? null : onItemClick(item)" >
@@ -19,7 +23,7 @@
{{ item.label }} - + {{ item.description }}
@@ -78,7 +82,11 @@ export default defineComponent({ const onKeyDown = (e: KeyboardEvent) => { checkForPropagation(e); - if (['Enter', 'Space'].includes(e.code) && selectedItemIndex.value > -1) { + if ( + ['Enter', 'Space'].includes(e.code) && + selectedItemIndex.value > -1 && + !items.value[selectedItemIndex.value].disabled + ) { onItemClick(items.value[selectedItemIndex.value]); } else if (e.code === 'ArrowDown') { handleSelection(getNewIndex('down')); @@ -168,6 +176,11 @@ export default defineComponent({ height: 0; border-top: $menu-separator-border; } + + &.disabled { + opacity: $menu-item-disabled-opacity; + cursor: not-allowed; + } } } diff --git a/src/interfaces/IItem.ts b/src/interfaces/IItem.ts index 8f3619a8..7f609e62 100644 --- a/src/interfaces/IItem.ts +++ b/src/interfaces/IItem.ts @@ -4,4 +4,5 @@ export interface IItem { description?: string; leadingIcon?: string; trailingIcon?: string; + disabled?: boolean; }