From 3c86a81b457542ee63d85e10dab3826225ec6dc5 Mon Sep 17 00:00:00 2001 From: Rogin Farrer Date: Mon, 16 Oct 2023 12:30:25 -0700 Subject: [PATCH] Add support for overriding id (#158) Co-authored-by: Rogin Farrer --- .changeset/small-pianos-work.md | 5 ++++ .prettierignore | 2 ++ .../react-collapsed/.storybook/preview.js | 4 +-- packages/react-collapsed/README.md | 1 + packages/react-collapsed/src/index.ts | 28 +++++++++++-------- packages/react-collapsed/tests/index.test.tsx | 14 ++++++++++ packages/react/.storybook/main.js | 13 ++++----- packages/react/.storybook/preview.js | 4 +-- 8 files changed, 48 insertions(+), 23 deletions(-) create mode 100644 .changeset/small-pianos-work.md create mode 100644 .prettierignore diff --git a/.changeset/small-pianos-work.md b/.changeset/small-pianos-work.md new file mode 100644 index 0000000..8b47f7f --- /dev/null +++ b/.changeset/small-pianos-work.md @@ -0,0 +1,5 @@ +--- +'react-collapsed': minor +--- + +Added support for overriding `id`. diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..de4d1f0 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/packages/react-collapsed/.storybook/preview.js b/packages/react-collapsed/.storybook/preview.js index 48afd56..750f637 100644 --- a/packages/react-collapsed/.storybook/preview.js +++ b/packages/react-collapsed/.storybook/preview.js @@ -1,9 +1,9 @@ export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, -} \ No newline at end of file +} diff --git a/packages/react-collapsed/README.md b/packages/react-collapsed/README.md index 8794039..3041df2 100644 --- a/packages/react-collapsed/README.md +++ b/packages/react-collapsed/README.md @@ -101,6 +101,7 @@ The following are optional properties passed into `useCollapse({ })`: | duration | number | `undefined` | The duration of the animation in milliseconds. By default, the duration is programmatically calculated based on the height of the collapsed element | | onTransitionStateChange | function | no-op | Handler called with at each stage of the transition animation | | hasDisabledAnimation | boolean | false | If true, will disable the animation | +| id | string \| number | `undefined` | Unique identifier used to for associating elements appropriately for accessibility. | ### What you get diff --git a/packages/react-collapsed/src/index.ts b/packages/react-collapsed/src/index.ts index 72b3cd1..c630ec5 100644 --- a/packages/react-collapsed/src/index.ts +++ b/packages/react-collapsed/src/index.ts @@ -65,6 +65,10 @@ export interface UseCollapseInput { | 'collapseEnd' | 'collapsing' ) => void + /** + * Unique identifier used to for associating elements appropriately for accessibility. + */ + id?: string | number; } export function useCollapse({ @@ -74,10 +78,11 @@ export function useCollapse({ isExpanded: configIsExpanded, defaultExpanded = false, hasDisabledAnimation, + id, ...initialConfig }: UseCollapseInput = {}) { const onTransitionStateChange = useEvent(propOnTransitionStateChange) - const uniqueId = useId() + const uniqueId = useId(id ? `${id}` : undefined) const [isExpanded, setExpanded] = useControlledState( configIsExpanded, @@ -220,7 +225,7 @@ export function useCollapse({ }, RefKey extends string | undefined = 'ref' >( - rest?: Args & { + args?: Args & { /** * Sets the key of the prop that the component uses for ref assignment * @default 'ref' @@ -238,16 +243,16 @@ export function useCollapse({ role?: 'button' tabIndex?: number } { - const { disabled, onClick, refKey } = { + const { disabled, onClick, refKey, ...rest } = { refKey: 'ref', onClick() {}, disabled: false, - ...rest, + ...args, } const isButton = toggleEl ? toggleEl.tagName === 'BUTTON' : undefined - const theirRef: any = rest?.[refKey || 'ref'] + const theirRef: any = args?.[refKey || 'ref'] const props: any = { id: `react-collapsed-toggle-${uniqueId}`, @@ -272,14 +277,15 @@ export function useCollapse({ } if (isButton === false) { - return { ...props, ...fakeButtonProps } + return { ...props, ...fakeButtonProps, ...rest } } else if (isButton === true) { - return { ...props, ...buttonProps } + return { ...props, ...buttonProps, ...rest } } else { return { ...props, ...buttonProps, ...fakeButtonProps, + ...rest } } }, @@ -288,7 +294,7 @@ export function useCollapse({ Args extends { style?: CSSProperties; [k: string]: unknown }, RefKey extends string | undefined = 'ref' >( - rest?: Args & { + args?: Args & { /** * Sets the key of the prop that the component uses for ref assignment * @default 'ref' @@ -303,13 +309,13 @@ export function useCollapse({ role: string style: CSSProperties } { - const { style, refKey } = { refKey: 'ref', style: {}, ...rest } - const theirRef: any = rest?.[refKey || 'ref'] + const { style, refKey } = { refKey: 'ref', style: {}, ...args } + const theirRef: any = args?.[refKey || 'ref'] return { id: `react-collapsed-panel-${uniqueId}`, 'aria-hidden': !isExpanded, role: 'region', - ...rest, + ...args, [refKey || 'ref']: mergeRefs(collapseElRef, theirRef), style: { boxSizing: 'border-box', diff --git a/packages/react-collapsed/tests/index.test.tsx b/packages/react-collapsed/tests/index.test.tsx index a41ad87..64f7444 100644 --- a/packages/react-collapsed/tests/index.test.tsx +++ b/packages/react-collapsed/tests/index.test.tsx @@ -144,3 +144,17 @@ test('permits access to the collapse ref', () => { const { queryByTestId } = render() expect(cb).toHaveBeenCalledWith(queryByTestId('collapse')) }) + +test('id argument modifies all rendered elements', () => { + const {container} = render() + expect(container.querySelector('#react-collapsed-toggle-foo')).toBeInTheDocument() + expect(container.querySelector('#react-collapsed-panel-foo')).toBeInTheDocument() +}) + +test('id will be overridden by prop getters', () => { + const {container} = render() + expect(container.querySelector('#react-collapsed-toggle-foo')).not.toBeInTheDocument() + expect(container.querySelector('#react-collapsed-panel-foo')).not.toBeInTheDocument() + expect(container.querySelector('#baz')).toBeInTheDocument() + expect(container.querySelector('#bar')).toBeInTheDocument() +}) diff --git a/packages/react/.storybook/main.js b/packages/react/.storybook/main.js index 5e7f178..fdbfec5 100644 --- a/packages/react/.storybook/main.js +++ b/packages/react/.storybook/main.js @@ -1,11 +1,8 @@ module.exports = { - "stories": [ - "../src/**/*.stories.mdx", - "../src/**/*.stories.@(js|jsx|ts|tsx)" + stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-a11y', ], - "addons": [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-a11y" - ] } diff --git a/packages/react/.storybook/preview.js b/packages/react/.storybook/preview.js index 48afd56..750f637 100644 --- a/packages/react/.storybook/preview.js +++ b/packages/react/.storybook/preview.js @@ -1,9 +1,9 @@ export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, -} \ No newline at end of file +}