diff --git a/.changeset/mighty-monkeys-occur.md b/.changeset/mighty-monkeys-occur.md new file mode 100644 index 0000000000..6dec991b4c --- /dev/null +++ b/.changeset/mighty-monkeys-occur.md @@ -0,0 +1,8 @@ +--- +'@storefront-ui/react': major +'@storefront-ui/vue': major +--- + +BREAKING CHANGE: Deprecated option `arrowKeysOn` from `useTrapFocus` is removed. +This option was separated and replaced by two more specialised options `arrowKeysLeftRight` and `arrowKeysUpDown`. +In order to migrate please one of those options or both. diff --git a/apps/docs/components/hooks/useTrapFocus.md b/apps/docs/components/hooks/useTrapFocus.md index 5d681c7be6..7be5b1f926 100644 --- a/apps/docs/components/hooks/useTrapFocus.md +++ b/apps/docs/components/hooks/useTrapFocus.md @@ -73,11 +73,6 @@ useTrapFocus(focusTrapElementRef) :::::: slot api - -::: warning DEPRECATION -Parameter `arrowKeysOn` will be deprecated since version 2.3 and removed in next major version. -::: - ## Parameters | Name | Type | Default value | Description | @@ -98,7 +93,6 @@ Parameter `arrowKeysOn` will be deprecated since version 2.3 and removed in next | activeState | `boolean` | `true` | Mount `useTrapFocus` when active is `true` | | initialFocus | `number | 'autofocus' | 'container'` | `0` | index number of desired focus element on init, `autofocus` for first marked element with attribute `autofocus`, `container` so `refElement` would be initially focused, `false` for disabling this option | -| arrowKeysOn | `boolean` | `false` | (deprecated) Enable/Disable possibility of using keyboard arrows `left`/`right` for jumping through focusable elements | | arrowKeysLeftRight | `boolean` | `false` | Enable/Disable possibility of using keyboard arrows `left | up`/`right | down` for jumping through focusable elements | | arrowKeysUpDown | `boolean` | `false` | Enable/Disable possibility of using keyboard arrows `up`/`down` for jumping through focusable elements | | initialFocusContainerFallback | `boolean` | `false` | Fallback for initial focus | diff --git a/apps/preview/next/pages/showcases/SelectDropdown/SelectDropdownDisabled.tsx b/apps/preview/next/pages/showcases/SelectDropdown/SelectDropdownDisabled.tsx index b636eefb47..79bd53a593 100644 --- a/apps/preview/next/pages/showcases/SelectDropdown/SelectDropdownDisabled.tsx +++ b/apps/preview/next/pages/showcases/SelectDropdown/SelectDropdownDisabled.tsx @@ -42,7 +42,8 @@ export default function SelectDropdownDisabled() { const { refs, style: dropdownStyle } = useDropdown({ isOpen, onClose: close }); useTrapFocus(refs.floating, { - arrowKeysOn: true, + arrowKeysLeftRight: true, + arrowKeysUpDown: true, activeState: isOpen, initialFocusContainerFallback: true, }); diff --git a/apps/preview/nuxt/pages/showcases/FormFields/FormFieldsRequired.vue b/apps/preview/nuxt/pages/showcases/FormFields/FormFieldsRequired.vue index c84cbcc852..6f752303b2 100644 --- a/apps/preview/nuxt/pages/showcases/FormFields/FormFieldsRequired.vue +++ b/apps/preview/nuxt/pages/showcases/FormFields/FormFieldsRequired.vue @@ -548,7 +548,8 @@ const { }); useTrapFocus(floatingRef as Ref, { - arrowKeysOn: true, + arrowKeysLeftRight: true, + arrowKeysUpDown: true, activeState: dropdownOpen, initialFocusContainerFallback: true, }); diff --git a/packages/sfui/frameworks/react/hooks/useTrapFocus/useTrapFocus.ts b/packages/sfui/frameworks/react/hooks/useTrapFocus/useTrapFocus.ts index 86e4845524..c4f5368796 100644 --- a/packages/sfui/frameworks/react/hooks/useTrapFocus/useTrapFocus.ts +++ b/packages/sfui/frameworks/react/hooks/useTrapFocus/useTrapFocus.ts @@ -15,11 +15,6 @@ type UseTrapFocusOptions = TabbableOptions & activeState?: boolean; initialFocus?: number | `${InitialFocusType}` | false; initialFocusContainerFallback?: boolean; - /** - * Enabling both `left` | `up` | `right` | `down` arrow keys. - * @deprecated Since version 2.3. Use arrowKeysLeftRight or/and arrowKeysUpDown options instead. - */ - arrowKeysOn?: boolean; arrowKeysLeftRight?: boolean; arrowKeysUpDown?: boolean; }; @@ -29,7 +24,6 @@ const defaultOptions = { activeState: true, initialFocus: 0, initialFocusContainerFallback: false, - arrowKeysOn: false, arrowKeysLeftRight: false, arrowKeysUpDown: false, }; @@ -41,7 +35,6 @@ export const useTrapFocus = (containerElementRef: RefObject, includeContainer, activeState, initialFocus, - arrowKeysOn, arrowKeysLeftRight, arrowKeysUpDown, initialFocusContainerFallback, @@ -86,9 +79,6 @@ export const useTrapFocus = (containerElementRef: RefObject, arrowFocusGroupSelector && containerElementRef.current?.querySelector(arrowFocusGroupSelector); const additionalData = isAnyGroupElement ? { arrowFocusGroupSelector } : {}; - if (arrowKeysOn && (event.key === 'ArrowLeft' || event.key === 'ArrowUp')) focusPreviousItem({ additionalData }); - if (arrowKeysOn && (event.key === 'ArrowRight' || event.key === 'ArrowDown')) focusNextItem({ additionalData }); - if (arrowKeysLeftRight && event.key === 'ArrowLeft') focusPreviousItem({ additionalData }); if (arrowKeysLeftRight && event.key === 'ArrowRight') focusNextItem({ additionalData }); if (arrowKeysUpDown && event.key === 'ArrowUp') focusPreviousItem({ additionalData }); diff --git a/packages/sfui/frameworks/vue/composables/useTrapFocus/useTrapFocus.ts b/packages/sfui/frameworks/vue/composables/useTrapFocus/useTrapFocus.ts index 620804f1ff..546d1717af 100644 --- a/packages/sfui/frameworks/vue/composables/useTrapFocus/useTrapFocus.ts +++ b/packages/sfui/frameworks/vue/composables/useTrapFocus/useTrapFocus.ts @@ -16,11 +16,6 @@ type UseTrapFocusOptions = TabbableOptions & activeState?: Ref; initialFocus?: number | `${InitialFocusType}` | false; initialFocusContainerFallback?: boolean; - /** - * Enabling both `left` | `up` | `right` | `down` arrow keys. - * @deprecated Since version 2.3. Use arrowKeysLeftRight or/and arrowKeysUpDown options instead. - */ - arrowKeysOn?: boolean; arrowKeysLeftRight?: boolean; arrowKeysUpDown?: boolean; }; @@ -38,7 +33,6 @@ const defaultOptions = { activeState: ref(true), initialFocus: 0, initialFocusContainerFallback: false, - arrowKeysOn: false, arrowKeysLeftRight: false, arrowKeysUpDown: false, }; @@ -53,7 +47,6 @@ export const useTrapFocus = ( includeContainer, activeState, initialFocus, - arrowKeysOn, arrowKeysLeftRight, arrowKeysUpDown, initialFocusContainerFallback, @@ -101,9 +94,6 @@ export const useTrapFocus = ( const isAnyGroupElement = arrowFocusGroupSelector && containerHTMLElement?.querySelector(arrowFocusGroupSelector); const additionalData = isAnyGroupElement ? { arrowFocusGroupSelector } : {}; - if (arrowKeysOn && (event.key === 'ArrowLeft' || event.key === 'ArrowUp')) focusPreviousItem({ additionalData }); - if (arrowKeysOn && (event.key === 'ArrowRight' || event.key === 'ArrowDown')) focusNextItem({ additionalData }); - if (arrowKeysLeftRight && event.key === 'ArrowLeft') focusPreviousItem({ additionalData }); if (arrowKeysLeftRight && event.key === 'ArrowRight') focusNextItem({ additionalData }); if (arrowKeysUpDown && event.key === 'ArrowUp') focusPreviousItem({ additionalData }); diff --git a/packages/tests/components/SfButton/SfButton.PageObject.ts b/packages/tests/components/SfButton/SfButton.PageObject.ts index bd2c930c02..ba96242ed3 100644 --- a/packages/tests/components/SfButton/SfButton.PageObject.ts +++ b/packages/tests/components/SfButton/SfButton.PageObject.ts @@ -11,8 +11,13 @@ export default class SfButtonBaseObject extends BasePage { return this; } - isSquare(value) { + hasClass(value: string) { this.container.should('have.class', value); return this; } + + doesNotHaveType() { + this.container.should('not.have.attr', 'type'); + return this; + } } diff --git a/packages/tests/components/SfButton/SfButton.cy.tsx b/packages/tests/components/SfButton/SfButton.cy.tsx index 34caafd638..efd4468851 100644 --- a/packages/tests/components/SfButton/SfButton.cy.tsx +++ b/packages/tests/components/SfButton/SfButton.cy.tsx @@ -22,15 +22,17 @@ describe('SfButton', () => { slotPrefix?: boolean; slotSuffix?: boolean; slotDefault?: boolean; + tag?: string; }; const initializeComponent = ({ - size = SfButtonSize.base, - variant = SfButtonVariant.primary, + size, + variant, disabled, square, slotPrefix, slotSuffix, slotDefault = true, + tag, }: InitializeComponentParams = {}) => { return mount({ vue: { @@ -46,6 +48,7 @@ describe('SfButton', () => { variant, disabled, square, + tag, }, slots: { ...(slotPrefix && { prefix: '' }), @@ -61,6 +64,7 @@ describe('SfButton', () => { square={square} slotPrefix={slotPrefix && } slotSuffix={slotSuffix && } + as={tag} > {slotDefault ? slotDefaultContent : undefined} @@ -90,6 +94,24 @@ describe('SfButton', () => { }); }); + const squarePaddingsToSize = { + [SfButtonSize.sm]: 'p-1.5', + [SfButtonSize.lg]: 'p-3', + [SfButtonSize.base]: 'p-2', + }; + + describe('when prop square=true and size is set to ', () => { + Object.values(SfButtonSize).forEach((componentSize) => { + describe(`${componentSize}`, () => { + it(`should render correct square ${componentSize} size`, () => { + initializeComponent({ size: componentSize, square: true }); + + page().hasClass(squarePaddingsToSize[componentSize]).makeSnapshot(); + }); + }); + }); + }); + describe('when prop variant is set to ', () => { Object.values(SfButtonVariant).forEach((componentVariant) => { describe(`${componentVariant}`, () => { @@ -100,69 +122,90 @@ describe('SfButton', () => { }); }); }); + }); - describe('when prop disabled=true', () => { - it(`should render as disabled`, () => { - initializeComponent({ disabled: true }); + describe('when no size specified via props', () => { + it(`should init with SfButtonSize.base size`, () => { + initializeComponent(); - page().isDisabled().makeSnapshot(); - }); + page().hasClass('py-2').hasClass('leading-6').hasClass('px-4').hasClass('gap-2').makeSnapshot(); }); + }); - describe('when prop square=true', () => { - it(`should render as square button`, () => { - initializeComponent({ square: true, size: SfButtonSize.base }); + describe('when no variant specified via props', () => { + it(`should init with SfButtonVariant.primary variant`, () => { + initializeComponent(); - page().isSquare('p-2').makeSnapshot(); - }); + page() + .hasClass('bg-primary-700') + .hasClass('hover:bg-primary-800') + .hasClass('active:bg-primary-900') + .hasClass('disabled:bg-disabled-300') + .makeSnapshot(); }); + }); - describe('when only prefix', () => { - it(`should render square button`, () => { - initializeComponent({ slotPrefix: true, slotDefault: false }); + describe('when prop disabled=true', () => { + it(`should render as disabled`, () => { + initializeComponent({ disabled: true }); - page().makeSnapshot(); - }); + page().isDisabled().makeSnapshot(); + }); + }); + + describe('when only prefix', () => { + it(`should render square button`, () => { + initializeComponent({ slotPrefix: true, slotDefault: false }); + + page().makeSnapshot(); }); + }); - describe('when only suffix', () => { - it(`should render square button`, () => { - initializeComponent({ slotSuffix: true, slotDefault: false }); + describe('when only suffix', () => { + it(`should render square button`, () => { + initializeComponent({ slotSuffix: true, slotDefault: false }); - page().makeSnapshot(); - }); + page().makeSnapshot(); }); + }); - describe('when suffix and prefix', () => { - it(`should render button with same gaps`, () => { - initializeComponent({ slotPrefix: true, slotSuffix: true, slotDefault: false }); + describe('when suffix and prefix', () => { + it(`should render button with same gaps`, () => { + initializeComponent({ slotPrefix: true, slotSuffix: true, slotDefault: false }); - page().makeSnapshot(); - }); + page().makeSnapshot(); }); + }); - describe('when suffix, content and prefix', () => { - it(`should render button with same gaps`, () => { - initializeComponent({ slotPrefix: true, slotSuffix: true, slotDefault: true }); + describe('when suffix, content and prefix', () => { + it(`should render button with same gaps`, () => { + initializeComponent({ slotPrefix: true, slotSuffix: true, slotDefault: true }); - page().makeSnapshot(); - }); + page().makeSnapshot(); }); + }); - describe('when prefix and content', () => { - it(`should render button with prefix and content with correct gaps`, () => { - initializeComponent({ slotPrefix: true, slotDefault: true }); + describe('when prefix and content', () => { + it(`should render button with prefix and content with correct gaps`, () => { + initializeComponent({ slotPrefix: true, slotDefault: true }); - page().makeSnapshot(); - }); + page().makeSnapshot(); }); + }); - describe('when content and suffix', () => { - it(`should render button with content and suffix with correct gaps`, () => { - initializeComponent({ slotSuffix: true, slotDefault: true }); + describe('when content and suffix', () => { + it(`should render button with content and suffix with correct gaps`, () => { + initializeComponent({ slotSuffix: true, slotDefault: true }); - page().makeSnapshot(); - }); + page().makeSnapshot(); + }); + }); + + describe('when tag is different than button', () => { + it(`should render tag without type attribute`, () => { + initializeComponent({ tag: 'div' }); + + page().doesNotHaveType().makeSnapshot(); }); }); }); diff --git a/packages/tests/components/SfListItem/SfListItem.PageObject.ts b/packages/tests/components/SfListItem/SfListItem.PageObject.ts index 2557270aeb..b485e6946f 100644 --- a/packages/tests/components/SfListItem/SfListItem.PageObject.ts +++ b/packages/tests/components/SfListItem/SfListItem.PageObject.ts @@ -18,11 +18,21 @@ export default class SfListItemObject extends BasePage { return this; } + hasDisabledPrefix() { + this.container.children().first().should('have.class', 'text-disabled-500'); + return this; + } + hasSuffix() { this.container.children().last().should('have.class', 'text-neutral-500'); return this; } + hasDisabledSuffix() { + this.container.children().last().should('have.class', 'text-disabled-500'); + return this; + } + isDisabled() { this.container.should('have.class', 'cursor-not-allowed pointer-events-none text-disabled-900'); return this; diff --git a/packages/tests/components/SfListItem/SfListItem.cy.tsx b/packages/tests/components/SfListItem/SfListItem.cy.tsx index a264a64dc6..01bad5ddf7 100644 --- a/packages/tests/components/SfListItem/SfListItem.cy.tsx +++ b/packages/tests/components/SfListItem/SfListItem.cy.tsx @@ -111,16 +111,44 @@ describe('SfListItem', () => { page().hasPrefix().makeSnapshot(); }); + + describe('when disabled=true', () => { + before(() => { + disabled = true; + }); + afterEach(() => { + disabled = false; + }); + it('should place text-disabled-500 class on prefix element', () => { + initializeComponent(); + + page().hasDisabledPrefix().makeSnapshot(); + }); + }); }); describe('when only suffix', () => { - before(() => (slotSuffix = true)); - after(() => (slotSuffix = false)); + beforeEach(() => (slotSuffix = true)); + afterEach(() => (slotSuffix = false)); it(`should render icon in suffix slot`, () => { initializeComponent(); page().hasSuffix().makeSnapshot(); }); + + describe('when disabled=true', () => { + before(() => { + disabled = true; + }); + afterEach(() => { + disabled = false; + }); + it('should place text-disabled-500 class on suffix element', () => { + initializeComponent(); + + page().hasDisabledSuffix().makeSnapshot(); + }); + }); }); describe('when component is clicked', () => { diff --git a/packages/tests/components/SfModel/SfModal.PageObject.ts b/packages/tests/components/SfModal/SfModal.PageObject.ts similarity index 100% rename from packages/tests/components/SfModel/SfModal.PageObject.ts rename to packages/tests/components/SfModal/SfModal.PageObject.ts diff --git a/packages/tests/components/SfModel/SfModal.cy.tsx b/packages/tests/components/SfModal/SfModal.cy.tsx similarity index 95% rename from packages/tests/components/SfModel/SfModal.cy.tsx rename to packages/tests/components/SfModal/SfModal.cy.tsx index b00de434c5..7f6593bcb4 100644 --- a/packages/tests/components/SfModel/SfModal.cy.tsx +++ b/packages/tests/components/SfModal/SfModal.cy.tsx @@ -3,15 +3,15 @@ import React from 'react'; import { ref } from 'vue'; import type { Ref } from 'vue'; import { mount, useComponent, Wrapper } from '../../utils/mount'; -import SfDialogBaseObject from './SfModal.PageObject'; +import SfModalBaseObject from './SfModal.PageObject'; import { waitForRerender } from '../../utils/waitForRerender'; const { vue: SfModalVue, react: SfModalReact } = useComponent('SfModal'); -describe('SfDialog', () => { +describe('SfModal', () => { const dialogContent = 'this is some dialog content'; - const page = () => new SfDialogBaseObject('modal'); + const page = () => new SfModalBaseObject('modal'); const initializeComponent = ({ modelValue = ref(true),