From b3ff66673237b145ecb143d61a19d9badaf92358 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Tue, 9 May 2023 12:22:19 +0800 Subject: [PATCH 01/12] Add dismiss handling --- .../react-components/react-tags/package.json | 1 + .../src/components/Tag/Tag.test.tsx | 38 ++++++++++++++ .../src/components/Tag/Tag.types.ts | 5 +- .../src/components/Tag/renderTag.tsx | 2 +- .../react-tags/src/components/Tag/useTag.tsx | 29 ++++++++++- .../src/components/Tag/useTagStyles.styles.ts | 4 ++ .../components/TagButton/TagButton.test.tsx | 32 ++++++++++++ .../components/TagButton/renderTagButton.tsx | 2 +- .../src/components/TagButton/useTagButton.tsx | 52 +++++++++++++++---- .../TagButton/useTagButtonStyles.styles.ts | 8 +++ 10 files changed, 159 insertions(+), 14 deletions(-) diff --git a/packages/react-components/react-tags/package.json b/packages/react-components/react-tags/package.json index 28e55548c911dd..24f18e74ab125f 100644 --- a/packages/react-components/react-tags/package.json +++ b/packages/react-components/react-tags/package.json @@ -34,6 +34,7 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { + "@fluentui/keyboard-keys": "^9.0.3", "@fluentui/react-aria": "^9.3.19", "@fluentui/react-avatar": "^9.5.0", "@fluentui/react-icons": "^2.0.196", diff --git a/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx b/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx index df2e9e30a5fdfa..d35b3c004850eb 100644 --- a/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx +++ b/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx @@ -1,6 +1,9 @@ +import * as React from 'react'; import { Tag } from './Tag'; import { isConformant } from '../../testing/isConformant'; import { TagProps } from './Tag.types'; +import { render, fireEvent } from '@testing-library/react'; +import { tagClassNames } from './useTagStyles.styles'; const requiredProps: TagProps = { dismissible: true, @@ -15,5 +18,40 @@ describe('Tag', () => { Component: Tag, displayName: 'Tag', requiredProps, + disabledTests: [ + // onDismiss: A second (data) argument is not needed + 'consistent-callback-args', + ], + }); + + it('should render root as a button', () => { + const { getByRole } = render(Tag); + expect(getByRole('button').className.includes(tagClassNames.root)).toBe(true); + }); + + it('should invoke onDismiss on click', () => { + const onDismiss = jest.fn(); + const { getByRole } = render( + + Tag + , + ); + + fireEvent.click(getByRole('button')); + + expect(onDismiss).toHaveBeenCalledTimes(1); + }); + + it('should invoke onDismiss on delete keyDown', () => { + const onDismiss = jest.fn(); + const { getByRole } = render( + + Tag + , + ); + + fireEvent.keyDown(getByRole('button'), { key: 'Delete' }); + + expect(onDismiss).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts index 49c35e5fd36725..1070fcc2fe18df 100644 --- a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts +++ b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts @@ -1,3 +1,4 @@ +import * as React from 'react'; import { AvatarSize, AvatarShape } from '@fluentui/react-avatar'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; @@ -36,10 +37,9 @@ export type TagSlots = { */ export type TagProps = ComponentProps> & { appearance?: 'filled-darker' | 'filled-lighter' | 'tint' | 'outline'; - // TODO implement tag checked state - // checked?: boolean; disabled?: boolean; dismissible?: boolean; + onDismiss?: (e: React.MouseEvent | React.KeyboardEvent) => void; shape?: 'rounded' | 'circular'; size?: 'extra-small' | 'small' | 'medium'; }; @@ -52,5 +52,6 @@ export type TagState = ComponentState & Pick & { avatarSize: AvatarSize | undefined; avatarShape: AvatarShape | undefined; + dismissed: boolean; } >; diff --git a/packages/react-components/react-tags/src/components/Tag/renderTag.tsx b/packages/react-components/react-tags/src/components/Tag/renderTag.tsx index b9cc3224c941d6..6d13ef45970c31 100644 --- a/packages/react-components/react-tags/src/components/Tag/renderTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/renderTag.tsx @@ -13,7 +13,7 @@ import { AvatarContextProvider } from '@fluentui/react-avatar'; export const renderTag_unstable = (state: TagState, contextValues: TagContextValues) => { const { slots, slotProps } = getSlotsNext(state); - return ( + return state.dismissed ? null : ( {slots.media && ( diff --git a/packages/react-components/react-tags/src/components/Tag/useTag.tsx b/packages/react-components/react-tags/src/components/Tag/useTag.tsx index 16397e53d9551c..c852268e1b5c97 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/useTag.tsx @@ -1,7 +1,8 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand, useEventCallback } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagProps, TagState } from './Tag.types'; +import { Delete, Backspace } from '@fluentui/keyboard-keys'; const tagAvatarSizeMap = { medium: 28, @@ -30,15 +31,39 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T appearance = 'filled-lighter', disabled = false, dismissible = false, + onDismiss, shape = 'rounded', size = 'medium', } = props; + const [dismissed, setDismissed] = React.useState(false); + + const handleClick = useEventCallback( + (ev: React.MouseEvent) => { + props.onClick?.(ev); + if (!ev.defaultPrevented) { + onDismiss?.(ev); + setDismissed(true); + } + }, + ); + + const handleKeyDown = useEventCallback( + (ev: React.KeyboardEvent) => { + props?.onKeyDown?.(ev); + if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { + onDismiss?.(ev); + setDismissed(true); + } + }, + ); + return { appearance, avatarShape: tagAvatarShapeMap[shape], avatarSize: tagAvatarSizeMap[size], disabled, + dismissed, dismissible, shape, size, @@ -55,6 +80,8 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T root: getNativeElementProps('button', { ref, ...props, + onClick: handleClick, + onKeyDown: handleKeyDown, }), media: resolveShorthand(props.media), diff --git a/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts b/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts index 731ee441aee7af..d4f165eab0d1a1 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts +++ b/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts @@ -93,6 +93,10 @@ const useTagStyles = makeStyles({ }, { enableOutline: true }, ), + + ':hover': { + cursor: 'pointer', + }, }, rootCircular: { ...shorthands.borderRadius(tokens.borderRadiusCircular), diff --git a/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx b/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx index 003e20c001ac91..0e83fc67dd4b30 100644 --- a/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx @@ -1,5 +1,7 @@ +import * as React from 'react'; import { TagButton } from './TagButton'; import { isConformant } from '../../testing/isConformant'; +import { render, fireEvent } from '@testing-library/react'; const requiredProps = { media: 'media', @@ -14,5 +16,35 @@ describe('TagButton', () => { Component: TagButton, displayName: 'TagButton', requiredProps, + disabledTests: [ + // onDismiss: A second (data) argument is not needed + 'consistent-callback-args', + ], + }); + + it('should invoke onDismiss on dismiss button click', () => { + const onDismiss = jest.fn(); + const { getByTestId } = render( + }> + Tag + , + ); + + fireEvent.click(getByTestId('foo')); + + expect(onDismiss).toHaveBeenCalledTimes(1); + }); + + it('should invoke onDismiss on dismiss button delete keyDown', () => { + const onDismiss = jest.fn(); + const { getByTestId } = render( + }> + Tag + , + ); + + fireEvent.keyDown(getByTestId('foo'), { key: 'Delete' }); + + expect(onDismiss).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx index d8ebda08cd235f..bcf31270ffe473 100644 --- a/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx @@ -13,7 +13,7 @@ import { AvatarContextProvider } from '@fluentui/react-avatar'; export const renderTagButton_unstable = (state: TagButtonState, contextValues: TagButtonContextValues) => { const { slots, slotProps } = getSlotsNext(state); - return ( + return state.dismissed ? null : ( {slots.content && ( diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx index dd4cf7b00bc7c1..6916dbed2b9672 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand, useEventCallback } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagButtonProps, TagButtonState } from './TagButton.types'; import { useARIAButtonShorthand } from '@fluentui/react-aria'; +import { Delete, Backspace } from '@fluentui/keyboard-keys'; const tagButtonAvatarSizeMap = { medium: 28, @@ -31,15 +32,48 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref) => { + props.onClick?.(ev); + if (!ev.defaultPrevented) { + onDismiss?.(ev); + setDismissed(true); + } + }, + ); + + const onDismissButtonKeyDown = useEventCallback( + (ev: React.KeyboardEvent) => { + props?.onKeyDown?.(ev); + if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { + onDismiss?.(ev); + setDismissed(true); + } + }, + ); + + const dismissButtonShorthand = useARIAButtonShorthand(props.dismissButton, { + required: props.dismissible, + defaultProps: { + disabled, + type: 'button', + children: , + }, + }); + return { appearance, avatarShape: tagButtonAvatarShapeMap[shape], avatarSize: tagButtonAvatarSizeMap[size], disabled, + dismissed, dismissible, shape, size, @@ -71,13 +105,13 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref, - }, - }), + + dismissButton: dismissButtonShorthand + ? { + ...dismissButtonShorthand, + onClick: onDismissButtonClick, + onKeyDown: onDismissButtonKeyDown, + } + : undefined, }; }; diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts b/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts index 19375c6b3c8a37..8b1c980ccf91c8 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts @@ -50,6 +50,10 @@ const useStyles = makeStyles({ }, { enableOutline: true }, ), + + ':hover': { + cursor: 'pointer', + }, }, circularContent: createCustomFocusIndicatorStyle(shorthands.borderRadius(tokens.borderRadiusCircular)), contentWithoutMedia: { @@ -78,6 +82,10 @@ const useStyles = makeStyles({ borderTopRightRadius: tokens.borderRadiusMedium, borderBottomRightRadius: tokens.borderRadiusMedium, }), + + ':hover': { + cursor: 'pointer', + }, }, dismissButtonCircular: createCustomFocusIndicatorStyle({ borderTopRightRadius: tokens.borderRadiusCircular, From 866ab68638b57a10e2da8f1a01a3d356aa43e256 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Tue, 16 May 2023 17:17:14 +0200 Subject: [PATCH 02/12] api --- packages/react-components/react-tags/etc/react-tags.api.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-tags/etc/react-tags.api.md b/packages/react-components/react-tags/etc/react-tags.api.md index 7b2fb641fecddd..dfd6e1cc4e1b37 100644 --- a/packages/react-components/react-tags/etc/react-tags.api.md +++ b/packages/react-components/react-tags/etc/react-tags.api.md @@ -17,10 +17,10 @@ import { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public -export const renderTag_unstable: (state: TagState, contextValues: TagContextValues) => JSX.Element; +export const renderTag_unstable: (state: TagState, contextValues: TagContextValues) => JSX.Element | null; // @public -export const renderTagButton_unstable: (state: TagButtonState, contextValues: TagButtonContextValues) => JSX.Element; +export const renderTagButton_unstable: (state: TagButtonState, contextValues: TagButtonContextValues) => JSX.Element | null; // @public export const Tag: ForwardRefComponent; @@ -52,6 +52,7 @@ export type TagProps = ComponentProps> & { appearance?: 'filled-darker' | 'filled-lighter' | 'tint' | 'outline'; disabled?: boolean; dismissible?: boolean; + onDismiss?: (e: React_2.MouseEvent | React_2.KeyboardEvent) => void; shape?: 'rounded' | 'circular'; size?: 'extra-small' | 'small' | 'medium'; }; @@ -70,6 +71,7 @@ export type TagSlots = { export type TagState = ComponentState & Required & { avatarSize: AvatarSize | undefined; avatarShape: AvatarShape | undefined; + dismissed: boolean; }>; // @public From 06cbe94d9a7b93bd02f54d768b443850c41cd888 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Fri, 28 Apr 2023 18:59:30 +0800 Subject: [PATCH 03/12] add TagGroup --- .../react-tags/src/TagGroup.ts | 1 + .../src/components/TagGroup/TagGroup.test.tsx | 11 ++++++++ .../src/components/TagGroup/TagGroup.tsx | 18 ++++++++++++ .../src/components/TagGroup/TagGroup.types.ts | 17 +++++++++++ .../src/components/TagGroup/index.ts | 5 ++++ .../components/TagGroup/renderTagGroup.tsx | 13 +++++++++ .../src/components/TagGroup/useTagGroup.ts | 28 +++++++++++++++++++ .../TagGroup/useTagGroupStyles.styles.ts | 28 +++++++++++++++++++ .../react-components/react-tags/src/index.ts | 8 ++++++ .../stories/TagGroup/TagGroupBestPractices.md | 5 ++++ .../TagGroup/TagGroupDefault.stories.tsx | 22 +++++++++++++++ .../stories/TagGroup/TagGroupDescription.md | 0 .../stories/TagGroup/index.stories.tsx | 18 ++++++++++++ 13 files changed, 174 insertions(+) create mode 100644 packages/react-components/react-tags/src/TagGroup.ts create mode 100644 packages/react-components/react-tags/src/components/TagGroup/TagGroup.test.tsx create mode 100644 packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx create mode 100644 packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts create mode 100644 packages/react-components/react-tags/src/components/TagGroup/index.ts create mode 100644 packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx create mode 100644 packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts create mode 100644 packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts create mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupBestPractices.md create mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx create mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupDescription.md create mode 100644 packages/react-components/react-tags/stories/TagGroup/index.stories.tsx diff --git a/packages/react-components/react-tags/src/TagGroup.ts b/packages/react-components/react-tags/src/TagGroup.ts new file mode 100644 index 00000000000000..bdda9a5083ef64 --- /dev/null +++ b/packages/react-components/react-tags/src/TagGroup.ts @@ -0,0 +1 @@ +export * from './components/TagGroup/index'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.test.tsx b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.test.tsx new file mode 100644 index 00000000000000..21d7525962a101 --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.test.tsx @@ -0,0 +1,11 @@ +import { TagGroup } from './TagGroup'; +import { isConformant } from '../../testing/isConformant'; + +describe('TagGroup', () => { + isConformant({ + Component: TagGroup, + displayName: 'TagGroup', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests +}); diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx new file mode 100644 index 00000000000000..3ff3807819a32d --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useTagGroup_unstable } from './useTagGroup'; +import { renderTagGroup_unstable } from './renderTagGroup'; +import { useTagGroupStyles_unstable } from './useTagGroupStyles.styles'; +import type { TagGroupProps } from './TagGroup.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * TagGroup component - TODO: add more docs + */ +export const TagGroup: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useTagGroup_unstable(props, ref); + + useTagGroupStyles_unstable(state); + return renderTagGroup_unstable(state); +}); + +TagGroup.displayName = 'TagGroup'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts new file mode 100644 index 00000000000000..2a773f75f20072 --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -0,0 +1,17 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; + +export type TagGroupSlots = { + root: Slot<'div'>; +}; + +/** + * TagGroup Props + */ +export type TagGroupProps = ComponentProps & {}; + +/** + * State used in rendering TagGroup + */ +export type TagGroupState = ComponentState; +// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from TagGroupProps. +// & Required> diff --git a/packages/react-components/react-tags/src/components/TagGroup/index.ts b/packages/react-components/react-tags/src/components/TagGroup/index.ts new file mode 100644 index 00000000000000..8ab5d0bbb3cebf --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/index.ts @@ -0,0 +1,5 @@ +export * from './TagGroup'; +export * from './TagGroup.types'; +export * from './renderTagGroup'; +export * from './useTagGroup'; +export * from './useTagGroupStyles.styles'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx new file mode 100644 index 00000000000000..b691f29cd5f377 --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { TagGroupState, TagGroupSlots } from './TagGroup.types'; + +/** + * Render the final JSX of TagGroup + */ +export const renderTagGroup_unstable = (state: TagGroupState) => { + const { slots, slotProps } = getSlots(state); + + // TODO Add additional slots in the appropriate place + return ; +}; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts new file mode 100644 index 00000000000000..8b5dafcc6b0c96 --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +import type { TagGroupProps, TagGroupState } from './TagGroup.types'; + +/** + * Create the state required to render TagGroup. + * + * The returned state can be modified with hooks such as useTagGroupStyles_unstable, + * before being passed to renderTagGroup_unstable. + * + * @param props - props from this instance of TagGroup + * @param ref - reference to root HTMLElement of TagGroup + */ +export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref): TagGroupState => { + return { + // TODO add appropriate props/defaults + components: { + // TODO add each slot's element type or component + root: 'div', + }, + // TODO add appropriate slots, for example: + // mySlot: resolveShorthand(props.mySlot), + root: getNativeElementProps('div', { + ref, + ...props, + }), + }; +}; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts new file mode 100644 index 00000000000000..b11cfed72ea9b4 --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts @@ -0,0 +1,28 @@ +import { makeStyles, mergeClasses } from '@griffel/react'; +import type { TagGroupSlots, TagGroupState } from './TagGroup.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { tokens } from '@fluentui/react-theme'; + +export const tagGroupClassNames: SlotClassNames = { + root: 'fui-TagGroup', +}; + +/** + * Styles for the root slot + */ +const useStyles = makeStyles({ + root: { + display: 'inline-flex', + columnGap: tokens.spacingHorizontalS, + }, +}); + +/** + * Apply styling to the TagGroup slots based on the state + */ +export const useTagGroupStyles_unstable = (state: TagGroupState): TagGroupState => { + const styles = useStyles(); + state.root.className = mergeClasses(tagGroupClassNames.root, styles.root, state.root.className); + + return state; +}; diff --git a/packages/react-components/react-tags/src/index.ts b/packages/react-components/react-tags/src/index.ts index 4d1d8029598994..d689581545f338 100644 --- a/packages/react-components/react-tags/src/index.ts +++ b/packages/react-components/react-tags/src/index.ts @@ -8,3 +8,11 @@ export { useTagButton_unstable, } from './TagButton'; export type { TagButtonProps, TagButtonSlots, TagButtonState } from './TagButton'; +export { + TagGroup, + renderTagGroup_unstable, + tagGroupClassNames, + useTagGroupStyles_unstable, + useTagGroup_unstable, +} from './TagGroup'; +export type { TagGroupProps, TagGroupSlots, TagGroupState } from './TagGroup'; diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupBestPractices.md b/packages/react-components/react-tags/stories/TagGroup/TagGroupBestPractices.md new file mode 100644 index 00000000000000..08ff8ddeeb5f86 --- /dev/null +++ b/packages/react-components/react-tags/stories/TagGroup/TagGroupBestPractices.md @@ -0,0 +1,5 @@ +## Best practices + +### Do + +### Don't diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx new file mode 100644 index 00000000000000..d8de6529606f14 --- /dev/null +++ b/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx @@ -0,0 +1,22 @@ +import * as React from 'react'; +import { TagGroup, Tag, TagButton, TagGroupProps } from '@fluentui/react-tags'; +import { Calendar3Day20Regular, Calendar3Day20Filled, bundleIcon } from '@fluentui/react-icons'; + +const Calendar3Day20Icon = bundleIcon(Calendar3Day20Filled, Calendar3Day20Regular); + +export const Default = (props: Partial) => ( + + }> + Tag 1 + + }> + Tag 2 + + }> + Tag 3 + + + Tag 4 + + +); diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupDescription.md b/packages/react-components/react-tags/stories/TagGroup/TagGroupDescription.md new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx new file mode 100644 index 00000000000000..a09a024b7bbc7a --- /dev/null +++ b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx @@ -0,0 +1,18 @@ +import { TagGroup } from '@fluentui/react-tags'; + +import descriptionMd from './TagGroupDescription.md'; +import bestPracticesMd from './TagGroupBestPractices.md'; + +export { Default } from './TagGroupDefault.stories'; + +export default { + title: 'Preview Components/Tag/TagGroup', + component: TagGroup, + parameters: { + docs: { + description: { + component: [descriptionMd, bestPracticesMd].join('\n'), + }, + }, + }, +}; From 7dae554256e684fcf8df28611b50f7759b20131a Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Tue, 9 May 2023 13:58:26 +0800 Subject: [PATCH 04/12] handle focus after dismiss --- .../react-tags/src/components/Tag/useTag.tsx | 12 ++++++++-- .../src/components/TagButton/useTagButton.tsx | 12 ++++++++-- .../src/components/TagGroup/TagGroup.tsx | 4 +++- .../src/components/TagGroup/TagGroup.types.ts | 9 ++++--- .../components/TagGroup/renderTagGroup.tsx | 10 +++++--- .../src/components/TagGroup/useTagGroup.ts | 24 ++++++++++++++----- .../TagGroup/useTagGroupContextValue.ts | 8 +++++++ .../src/contexts/TagGroupContext.tsx | 17 +++++++++++++ 8 files changed, 77 insertions(+), 19 deletions(-) create mode 100644 packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts create mode 100644 packages/react-components/react-tags/src/contexts/TagGroupContext.tsx diff --git a/packages/react-components/react-tags/src/components/Tag/useTag.tsx b/packages/react-components/react-tags/src/components/Tag/useTag.tsx index c852268e1b5c97..0fd85eb4adc6ab 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/useTag.tsx @@ -3,6 +3,7 @@ import { getNativeElementProps, resolveShorthand, useEventCallback } from '@flue import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagProps, TagState } from './Tag.types'; import { Delete, Backspace } from '@fluentui/keyboard-keys'; +import { useTagGroupContext_unstable } from '../../contexts/TagGroupContext'; const tagAvatarSizeMap = { medium: 28, @@ -27,6 +28,8 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of Tag */ export const useTag_unstable = (props: TagProps, ref: React.Ref): TagState => { + const { handleTagDismiss } = useTagGroupContext_unstable(); + const { appearance = 'filled-lighter', disabled = false, @@ -38,12 +41,17 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T const [dismissed, setDismissed] = React.useState(false); + const dismiss = React.useCallback(() => { + setDismissed(true); + handleTagDismiss(); + }, [handleTagDismiss]); + const handleClick = useEventCallback( (ev: React.MouseEvent) => { props.onClick?.(ev); if (!ev.defaultPrevented) { onDismiss?.(ev); - setDismissed(true); + dismiss(); } }, ); @@ -53,7 +61,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T props?.onKeyDown?.(ev); if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { onDismiss?.(ev); - setDismissed(true); + dismiss(); } }, ); diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx index 6916dbed2b9672..c5bb1e19eeb0e8 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx @@ -4,6 +4,7 @@ import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons import type { TagButtonProps, TagButtonState } from './TagButton.types'; import { useARIAButtonShorthand } from '@fluentui/react-aria'; import { Delete, Backspace } from '@fluentui/keyboard-keys'; +import { useTagGroupContext_unstable } from '../../contexts/TagGroupContext'; const tagButtonAvatarSizeMap = { medium: 28, @@ -28,6 +29,8 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of TagButton */ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref): TagButtonState => { + const { handleTagDismiss } = useTagGroupContext_unstable(); + const { appearance = 'filled-lighter', disabled = false, @@ -39,12 +42,17 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref { + setDismissed(true); + handleTagDismiss(); + }, [handleTagDismiss]); + const onDismissButtonClick = useEventCallback( (ev: React.MouseEvent) => { props.onClick?.(ev); if (!ev.defaultPrevented) { onDismiss?.(ev); - setDismissed(true); + dismiss(); } }, ); @@ -54,7 +62,7 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref = React.forwardRef((pr const state = useTagGroup_unstable(props, ref); useTagGroupStyles_unstable(state); - return renderTagGroup_unstable(state); + + return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); }); TagGroup.displayName = 'TagGroup'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index 2a773f75f20072..dc9b90b3a2642a 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -7,11 +7,10 @@ export type TagGroupSlots = { /** * TagGroup Props */ -export type TagGroupProps = ComponentProps & {}; - +export type TagGroupProps = ComponentProps; /** * State used in rendering TagGroup */ -export type TagGroupState = ComponentState; -// TODO: Remove semicolon from previous line, uncomment next line, and provide union of props to pick from TagGroupProps. -// & Required> +export type TagGroupState = ComponentState & { + handleTagDismiss: () => void; +}; diff --git a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx index b691f29cd5f377..7d7f9df326f11d 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx @@ -1,13 +1,17 @@ import * as React from 'react'; import { getSlots } from '@fluentui/react-utilities'; import type { TagGroupState, TagGroupSlots } from './TagGroup.types'; +import { TagGroupContextProvider, TagGroupContextValue } from '../../contexts/TagGroupContext'; /** * Render the final JSX of TagGroup */ -export const renderTagGroup_unstable = (state: TagGroupState) => { +export const renderTagGroup_unstable = (state: TagGroupState, contextValue: TagGroupContextValue) => { const { slots, slotProps } = getSlots(state); - // TODO Add additional slots in the appropriate place - return ; + return ( + + + + ); }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts index 8b5dafcc6b0c96..c86a302ccb0ca8 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts @@ -1,6 +1,8 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities'; import type { TagGroupProps, TagGroupState } from './TagGroup.types'; +import { useFocusFinders } from '@fluentui/react-tabster'; +import { tagButtonClassNames } from '../TagButton/useTagButtonStyles.styles'; /** * Create the state required to render TagGroup. @@ -12,17 +14,27 @@ import type { TagGroupProps, TagGroupState } from './TagGroup.types'; * @param ref - reference to root HTMLElement of TagGroup */ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref): TagGroupState => { + const { findLastFocusable, findPrevFocusable } = useFocusFinders(); + const innerRef = React.useRef(null); + + const handleTagDismiss = React.useCallback(() => { + const lastFocusable = findLastFocusable(innerRef.current as HTMLElement); + if (lastFocusable && lastFocusable.classList.contains(tagButtonClassNames.dismissButton)) { + findPrevFocusable(lastFocusable)?.focus(); + } else { + lastFocusable?.focus(); + } + }, [findLastFocusable, findPrevFocusable]); + return { - // TODO add appropriate props/defaults components: { - // TODO add each slot's element type or component root: 'div', }, - // TODO add appropriate slots, for example: - // mySlot: resolveShorthand(props.mySlot), root: getNativeElementProps('div', { - ref, + ref: useMergedRefs(ref, innerRef), + // TODO aria attributes ...props, }), + handleTagDismiss, }; }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts new file mode 100644 index 00000000000000..4b66871908adff --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts @@ -0,0 +1,8 @@ +import * as React from 'react'; +import type { TagGroupContextValue } from '../../contexts/TagGroupContext'; +import type { TagGroupState } from './TagGroup.types'; + +export function useTagGroupContextValue_unstable(state: TagGroupState): TagGroupContextValue { + const { handleTagDismiss } = state; + return React.useMemo(() => ({ handleTagDismiss }), [handleTagDismiss]); +} diff --git a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx new file mode 100644 index 00000000000000..ffa5c47785ceb5 --- /dev/null +++ b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { TagGroupState } from '../components/TagGroup/index'; + +export const TagGroupContext = React.createContext(undefined); + +const tagGroupContextDefaultValue: TagGroupContextValue = { + handleTagDismiss: () => null, +}; + +/** + * Context shared between TagGroup and its children components + */ +export type TagGroupContextValue = Pick; + +export const TagGroupContextProvider = TagGroupContext.Provider; + +export const useTagGroupContext_unstable = () => React.useContext(TagGroupContext) ?? tagGroupContextDefaultValue; From ab98a5f6a7eddad1eb611d9f840db5f295ac37e5 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Wed, 17 May 2023 15:00:22 +0200 Subject: [PATCH 05/12] new dismiss handling --- .../src/components/Tag/Tag.types.ts | 3 -- .../src/components/Tag/renderTag.tsx | 2 +- .../react-tags/src/components/Tag/useTag.tsx | 18 +++------- .../components/TagButton/renderTagButton.tsx | 2 +- .../src/components/TagButton/useTagButton.tsx | 18 +++------- .../src/components/TagGroup/TagGroup.tsx | 11 +++--- .../src/components/TagGroup/TagGroup.types.ts | 18 +++++++--- .../components/TagGroup/renderTagGroup.tsx | 4 +-- .../src/components/TagGroup/useTagGroup.ts | 24 ++++++------- .../src/contexts/TagGroupContext.tsx | 2 +- .../TagGroup/TagGroupDefault.stories.tsx | 8 ++--- .../TagGroup/TagGroupDismiss.stories.tsx | 34 +++++++++++++++++++ .../stories/TagGroup/index.stories.tsx | 1 + 13 files changed, 84 insertions(+), 61 deletions(-) create mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx diff --git a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts index 1070fcc2fe18df..7e352401577596 100644 --- a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts +++ b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts @@ -1,4 +1,3 @@ -import * as React from 'react'; import { AvatarSize, AvatarShape } from '@fluentui/react-avatar'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; @@ -39,7 +38,6 @@ export type TagProps = ComponentProps> & { appearance?: 'filled-darker' | 'filled-lighter' | 'tint' | 'outline'; disabled?: boolean; dismissible?: boolean; - onDismiss?: (e: React.MouseEvent | React.KeyboardEvent) => void; shape?: 'rounded' | 'circular'; size?: 'extra-small' | 'small' | 'medium'; }; @@ -52,6 +50,5 @@ export type TagState = ComponentState & Pick & { avatarSize: AvatarSize | undefined; avatarShape: AvatarShape | undefined; - dismissed: boolean; } >; diff --git a/packages/react-components/react-tags/src/components/Tag/renderTag.tsx b/packages/react-components/react-tags/src/components/Tag/renderTag.tsx index 6d13ef45970c31..b9cc3224c941d6 100644 --- a/packages/react-components/react-tags/src/components/Tag/renderTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/renderTag.tsx @@ -13,7 +13,7 @@ import { AvatarContextProvider } from '@fluentui/react-avatar'; export const renderTag_unstable = (state: TagState, contextValues: TagContextValues) => { const { slots, slotProps } = getSlotsNext(state); - return state.dismissed ? null : ( + return ( {slots.media && ( diff --git a/packages/react-components/react-tags/src/components/Tag/useTag.tsx b/packages/react-components/react-tags/src/components/Tag/useTag.tsx index 0fd85eb4adc6ab..25a250e4d79bba 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/useTag.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useEventCallback } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand, useEventCallback, useId } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagProps, TagState } from './Tag.types'; import { Delete, Backspace } from '@fluentui/keyboard-keys'; @@ -34,24 +34,17 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T appearance = 'filled-lighter', disabled = false, dismissible = false, - onDismiss, shape = 'rounded', size = 'medium', } = props; - const [dismissed, setDismissed] = React.useState(false); - - const dismiss = React.useCallback(() => { - setDismissed(true); - handleTagDismiss(); - }, [handleTagDismiss]); + const id = useId('fui-Tag', props.id); const handleClick = useEventCallback( (ev: React.MouseEvent) => { props.onClick?.(ev); if (!ev.defaultPrevented) { - onDismiss?.(ev); - dismiss(); + handleTagDismiss?.(ev, id); } }, ); @@ -60,8 +53,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T (ev: React.KeyboardEvent) => { props?.onKeyDown?.(ev); if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { - onDismiss?.(ev); - dismiss(); + handleTagDismiss?.(ev, id); } }, ); @@ -71,7 +63,6 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T avatarShape: tagAvatarShapeMap[shape], avatarSize: tagAvatarSizeMap[size], disabled, - dismissed, dismissible, shape, size, @@ -88,6 +79,7 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T root: getNativeElementProps('button', { ref, ...props, + id, onClick: handleClick, onKeyDown: handleKeyDown, }), diff --git a/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx index bcf31270ffe473..d8ebda08cd235f 100644 --- a/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/renderTagButton.tsx @@ -13,7 +13,7 @@ import { AvatarContextProvider } from '@fluentui/react-avatar'; export const renderTagButton_unstable = (state: TagButtonState, contextValues: TagButtonContextValues) => { const { slots, slotProps } = getSlotsNext(state); - return state.dismissed ? null : ( + return ( {slots.content && ( diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx index c5bb1e19eeb0e8..7b5bc7096cfb54 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useEventCallback } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand, useEventCallback, useId } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagButtonProps, TagButtonState } from './TagButton.types'; import { useARIAButtonShorthand } from '@fluentui/react-aria'; @@ -35,24 +35,17 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref { - setDismissed(true); - handleTagDismiss(); - }, [handleTagDismiss]); + const id = useId('fui-Tag', props.id); const onDismissButtonClick = useEventCallback( (ev: React.MouseEvent) => { props.onClick?.(ev); if (!ev.defaultPrevented) { - onDismiss?.(ev); - dismiss(); + handleTagDismiss?.(ev, id); } }, ); @@ -61,8 +54,7 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref) => { props?.onKeyDown?.(ev); if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { - onDismiss?.(ev); - dismiss(); + handleTagDismiss?.(ev, id); } }, ); @@ -81,7 +73,6 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref = React.forwardRef((props, ref) => { - const state = useTagGroup_unstable(props, ref); +export const TagGroup: ForwardRefComponent & ((props: TagGroupProps) => JSX.Element) = + React.forwardRef((props, ref) => { + const state = useTagGroup_unstable(props, ref); - useTagGroupStyles_unstable(state); + useTagGroupStyles_unstable(state); - return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); -}); + return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); + }) as ForwardRefComponent & ((props: TagGroupProps) => JSX.Element); TagGroup.displayName = 'TagGroup'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index dc9b90b3a2642a..25e62bda00e87d 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -1,16 +1,26 @@ +import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { TagProps } from '../Tag/Tag.types'; export type TagGroupSlots = { root: Slot<'div'>; }; +export type ItemRenderFunction = (item: TItem) => React.ReactNode; + /** * TagGroup Props */ -export type TagGroupProps = ComponentProps; +export type TagGroupProps = Omit, 'children'> & { + items?: Array; + onDismiss?: (e: React.MouseEvent | React.KeyboardEvent, dismissedTagIds: string[]) => void; + children: React.ReactNode | ItemRenderFunction; +}; + /** * State used in rendering TagGroup */ -export type TagGroupState = ComponentState & { - handleTagDismiss: () => void; -}; +export type TagGroupState = ComponentState & + Required> & { + handleTagDismiss: (e: React.MouseEvent | React.KeyboardEvent, id: string) => void; + }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx index 7d7f9df326f11d..d4ec1061f28ccd 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; +import { getSlotsNext } from '@fluentui/react-utilities'; import type { TagGroupState, TagGroupSlots } from './TagGroup.types'; import { TagGroupContextProvider, TagGroupContextValue } from '../../contexts/TagGroupContext'; @@ -7,7 +7,7 @@ import { TagGroupContextProvider, TagGroupContextValue } from '../../contexts/Ta * Render the final JSX of TagGroup */ export const renderTagGroup_unstable = (state: TagGroupState, contextValue: TagGroupContextValue) => { - const { slots, slotProps } = getSlots(state); + const { slots, slotProps } = getSlotsNext(state); return ( diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts index c86a302ccb0ca8..e5de619da43307 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts @@ -1,8 +1,6 @@ import * as React from 'react'; -import { getNativeElementProps, useMergedRefs } from '@fluentui/react-utilities'; +import { getNativeElementProps, useMergedRefs, useEventCallback } from '@fluentui/react-utilities'; import type { TagGroupProps, TagGroupState } from './TagGroup.types'; -import { useFocusFinders } from '@fluentui/react-tabster'; -import { tagButtonClassNames } from '../TagButton/useTagButtonStyles.styles'; /** * Create the state required to render TagGroup. @@ -14,17 +12,15 @@ import { tagButtonClassNames } from '../TagButton/useTagButtonStyles.styles'; * @param ref - reference to root HTMLElement of TagGroup */ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref): TagGroupState => { - const { findLastFocusable, findPrevFocusable } = useFocusFinders(); const innerRef = React.useRef(null); - const handleTagDismiss = React.useCallback(() => { - const lastFocusable = findLastFocusable(innerRef.current as HTMLElement); - if (lastFocusable && lastFocusable.classList.contains(tagButtonClassNames.dismissButton)) { - findPrevFocusable(lastFocusable)?.focus(); - } else { - lastFocusable?.focus(); - } - }, [findLastFocusable, findPrevFocusable]); + const { items = [], onDismiss, children } = props; + + const handleTagDismiss = useEventCallback((e: React.MouseEvent | React.KeyboardEvent, id: string) => { + onDismiss?.(e, [id]); + + // TODO set focus after tag dismiss + }); return { components: { @@ -32,9 +28,11 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref children(item)) : children, + // TODO aria attributes }), + items, handleTagDismiss, }; }; diff --git a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx index ffa5c47785ceb5..1f859abf9a4204 100644 --- a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx +++ b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx @@ -10,7 +10,7 @@ const tagGroupContextDefaultValue: TagGroupContextValue = { /** * Context shared between TagGroup and its children components */ -export type TagGroupContextValue = Pick; +export type TagGroupContextValue = Required>; export const TagGroupContextProvider = TagGroupContext.Provider; diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx index d8de6529606f14..a3c0e417126229 100644 --- a/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx +++ b/packages/react-components/react-tags/stories/TagGroup/TagGroupDefault.stories.tsx @@ -6,17 +6,15 @@ const Calendar3Day20Icon = bundleIcon(Calendar3Day20Filled, Calendar3Day20Regula export const Default = (props: Partial) => ( - }> + }> Tag 1 - }> + }> Tag 2 }> Tag 3 - - Tag 4 - + Tag 4 ); diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx new file mode 100644 index 00000000000000..8f6480a640efb6 --- /dev/null +++ b/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { TagGroup, Tag, TagButton, TagButtonProps, TagProps } from '@fluentui/react-tags'; + +export const Dismiss = () => { + const defaultItems = [ + { id: '1', children: 'Tag 1' }, + { id: '2', children: 'Tag 2' }, + { id: 'tagButton-foo', children: 'Foo' }, + { id: 'tagButton-bar', children: 'Bar' }, + ]; + + const [items, setItems] = React.useState(defaultItems); + + const removeItem = (_e: React.MouseEvent | React.KeyboardEvent, ids: string[]) => { + setItems(prevItems => prevItems.filter(item => ids[0] !== item.id)); + }; + + const isTagButton = (item: TagProps | TagButtonProps): item is TagButtonProps => !!item.id?.startsWith('tagButton'); + + return ( + items={items} onDismiss={removeItem}> + {item => (isTagButton(item) ? : )} + + ); +}; + +Dismiss.storyName = 'Dismiss'; +Dismiss.parameters = { + docs: { + description: { + story: 'A TagGroup contains a collection of Tag/TagButton that can be dismissed', + }, + }, +}; diff --git a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx index a09a024b7bbc7a..7183ed91671f64 100644 --- a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx +++ b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx @@ -4,6 +4,7 @@ import descriptionMd from './TagGroupDescription.md'; import bestPracticesMd from './TagGroupBestPractices.md'; export { Default } from './TagGroupDefault.stories'; +export { Dismiss } from './TagGroupDismiss.stories'; export default { title: 'Preview Components/Tag/TagGroup', From f9b4fcd1e3ff5f3220c9e32d3a5d2226f9a413c1 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Wed, 17 May 2023 15:46:19 +0200 Subject: [PATCH 06/12] add size --- .../src/components/Tag/Tag.types.ts | 4 +- .../react-tags/src/components/Tag/useTag.tsx | 4 +- .../src/components/Tag/useTagStyles.styles.ts | 11 +++++ .../src/components/TagButton/useTagButton.tsx | 4 +- .../TagButton/useTagButtonStyles.styles.ts | 11 +++++ .../src/components/TagGroup/TagGroup.types.ts | 7 +-- .../src/components/TagGroup/useTagGroup.ts | 9 ++-- .../TagGroup/useTagGroupContextValue.ts | 4 +- .../TagGroup/useTagGroupStyles.styles.ts | 10 ++++- .../src/contexts/TagGroupContext.tsx | 3 +- .../TagGroup/TagGroupSizes.stories.tsx | 44 +++++++++++++++++++ .../stories/TagGroup/index.stories.tsx | 1 + 12 files changed, 97 insertions(+), 15 deletions(-) create mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupSizes.stories.tsx diff --git a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts index 7e352401577596..c22b1bfe4d09cc 100644 --- a/packages/react-components/react-tags/src/components/Tag/Tag.types.ts +++ b/packages/react-components/react-tags/src/components/Tag/Tag.types.ts @@ -1,6 +1,8 @@ import { AvatarSize, AvatarShape } from '@fluentui/react-avatar'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +export type TagSize = 'extra-small' | 'small' | 'medium'; + export type TagContextValues = { avatar: { size?: AvatarSize; @@ -39,7 +41,7 @@ export type TagProps = ComponentProps> & { disabled?: boolean; dismissible?: boolean; shape?: 'rounded' | 'circular'; - size?: 'extra-small' | 'small' | 'medium'; + size?: TagSize; }; /** diff --git a/packages/react-components/react-tags/src/components/Tag/useTag.tsx b/packages/react-components/react-tags/src/components/Tag/useTag.tsx index 25a250e4d79bba..5bbc519582e092 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/useTag.tsx @@ -28,14 +28,14 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of Tag */ export const useTag_unstable = (props: TagProps, ref: React.Ref): TagState => { - const { handleTagDismiss } = useTagGroupContext_unstable(); + const { handleTagDismiss, size: contextSize } = useTagGroupContext_unstable(); const { appearance = 'filled-lighter', disabled = false, dismissible = false, shape = 'rounded', - size = 'medium', + size = contextSize, } = props; const id = useId('fui-Tag', props.id); diff --git a/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts b/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts index d4f165eab0d1a1..16f9f351744513 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts +++ b/packages/react-components/react-tags/src/components/Tag/useTagStyles.styles.ts @@ -122,6 +122,14 @@ const useTagStyles = makeStyles({ // TODO add additional classes for fill/outline appearance, different sizes, and state }); + +const useSmallTagStyles = makeStyles({ + root: { + height: '24px', + }, + // TODO add additional styles for sizes +}); + /** * Apply styling to the Tag slots based on the state */ @@ -129,6 +137,7 @@ export const useTagStyles_unstable = (state: TagState): TagState => { const baseStyles = useTagBaseStyles(); const resetButtonStyles = useResetButtonStyles(); const styles = useTagStyles(); + const smallStyles = useSmallTagStyles(); state.root.className = mergeClasses( tagClassNames.root, @@ -139,6 +148,8 @@ export const useTagStyles_unstable = (state: TagState): TagState => { !state.media && !state.icon && styles.rootWithoutMedia, !state.dismissIcon && styles.rootWithoutDismiss, + state.size === 'small' && smallStyles.root, + state.root.className, ); diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx index 7b5bc7096cfb54..cb8edcaf2636a3 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx @@ -29,14 +29,14 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of TagButton */ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref): TagButtonState => { - const { handleTagDismiss } = useTagGroupContext_unstable(); + const { handleTagDismiss, size: contextSize } = useTagGroupContext_unstable(); const { appearance = 'filled-lighter', disabled = false, dismissible = false, shape = 'rounded', - size = 'medium', + size = contextSize, } = props; const id = useId('fui-Tag', props.id); diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts b/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts index 8b1c980ccf91c8..486c9bd2948448 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButtonStyles.styles.ts @@ -95,6 +95,13 @@ const useStyles = makeStyles({ // TODO add additional classes for fill/outline appearance, different sizes, and state }); +const useSmallTagButtonStyles = makeStyles({ + root: { + height: '24px', + }, + // TODO add additional styles for sizes +}); + /** * Apply styling to the TagButton slots based on the state */ @@ -102,11 +109,15 @@ export const useTagButtonStyles_unstable = (state: TagButtonState): TagButtonSta const baseStyles = useTagBaseStyles(); const resetButtonStyles = useResetButtonStyles(); const styles = useStyles(); + const smallStyles = useSmallTagButtonStyles(); state.root.className = mergeClasses( tagButtonClassNames.root, styles.root, state.shape === 'circular' && styles.rootCircular, + + state.size === 'small' && smallStyles.root, + state.root.className, ); if (state.content) { diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index 25e62bda00e87d..3f33023790ccc1 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { TagProps } from '../Tag/Tag.types'; +import { TagProps, TagSize } from '../Tag/Tag.types'; export type TagGroupSlots = { root: Slot<'div'>; @@ -12,15 +12,16 @@ export type ItemRenderFunction = (item: TItem) => React.ReactN * TagGroup Props */ export type TagGroupProps = Omit, 'children'> & { + children: React.ReactNode | ItemRenderFunction; items?: Array; onDismiss?: (e: React.MouseEvent | React.KeyboardEvent, dismissedTagIds: string[]) => void; - children: React.ReactNode | ItemRenderFunction; + size?: TagSize; }; /** * State used in rendering TagGroup */ export type TagGroupState = ComponentState & - Required> & { + Required> & { handleTagDismiss: (e: React.MouseEvent | React.KeyboardEvent, id: string) => void; }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts index e5de619da43307..2d7f3d5317d6a9 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts @@ -14,7 +14,7 @@ import type { TagGroupProps, TagGroupState } from './TagGroup.types'; export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref): TagGroupState => { const innerRef = React.useRef(null); - const { items = [], onDismiss, children } = props; + const { children, onDismiss, items = [], size = 'medium' } = props; const handleTagDismiss = useEventCallback((e: React.MouseEvent | React.KeyboardEvent, id: string) => { onDismiss?.(e, [id]); @@ -23,16 +23,19 @@ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref children(item)) : children, // TODO aria attributes }), - items, - handleTagDismiss, }; }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts index 4b66871908adff..847f025d6f3e96 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts @@ -3,6 +3,6 @@ import type { TagGroupContextValue } from '../../contexts/TagGroupContext'; import type { TagGroupState } from './TagGroup.types'; export function useTagGroupContextValue_unstable(state: TagGroupState): TagGroupContextValue { - const { handleTagDismiss } = state; - return React.useMemo(() => ({ handleTagDismiss }), [handleTagDismiss]); + const { handleTagDismiss, size } = state; + return React.useMemo(() => ({ handleTagDismiss, size }), [handleTagDismiss, size]); } diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts index b11cfed72ea9b4..384730cbf6e5c7 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupStyles.styles.ts @@ -15,6 +15,9 @@ const useStyles = makeStyles({ display: 'inline-flex', columnGap: tokens.spacingHorizontalS, }, + rootSmall: { + columnGap: tokens.spacingHorizontalSNudge, + }, }); /** @@ -22,7 +25,12 @@ const useStyles = makeStyles({ */ export const useTagGroupStyles_unstable = (state: TagGroupState): TagGroupState => { const styles = useStyles(); - state.root.className = mergeClasses(tagGroupClassNames.root, styles.root, state.root.className); + state.root.className = mergeClasses( + tagGroupClassNames.root, + styles.root, + state.size === 'small' && styles.rootSmall, + state.root.className, + ); return state; }; diff --git a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx index 1f859abf9a4204..170ccfcc3c14ce 100644 --- a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx +++ b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx @@ -5,12 +5,13 @@ export const TagGroupContext = React.createContext null, + size: 'medium', }; /** * Context shared between TagGroup and its children components */ -export type TagGroupContextValue = Required>; +export type TagGroupContextValue = Required>; export const TagGroupContextProvider = TagGroupContext.Provider; diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupSizes.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/TagGroupSizes.stories.tsx new file mode 100644 index 00000000000000..a1c66a444c89c6 --- /dev/null +++ b/packages/react-components/react-tags/stories/TagGroup/TagGroupSizes.stories.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; +import { TagGroup, Tag, TagButton } from '@fluentui/react-tags'; +import { Avatar, makeStyles } from '@fluentui/react-components'; +import { TagSize } from '../../src/Tag'; + +const useContainerStyles = makeStyles({ + root: { + display: 'flex', + flexDirection: 'column', + rowGap: '10px', + }, +}); + +export const Sizes = () => { + const containerStyles = useContainerStyles(); + // TODO add one more size + const sizes: TagSize[] = ['small', 'medium']; + return ( +
+ {sizes.map(size => ( +
+ {`${size}: `} + + } shape="circular"> + Tag 1 + + } shape="circular"> + Tag 2 + + +
+ ))} +
+ ); +}; + +Sizes.storyName = 'Sizes'; +Sizes.parameters = { + docs: { + description: { + story: 'A TagGroup supports different sizes', + }, + }, +}; diff --git a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx index 7183ed91671f64..09880bb5bd4975 100644 --- a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx +++ b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx @@ -5,6 +5,7 @@ import bestPracticesMd from './TagGroupBestPractices.md'; export { Default } from './TagGroupDefault.stories'; export { Dismiss } from './TagGroupDismiss.stories'; +export { Sizes } from './TagGroupSizes.stories'; export default { title: 'Preview Components/Tag/TagGroup', From cfbc28a7204490eae164fa54d30ce7812264e334 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Wed, 17 May 2023 15:57:03 +0200 Subject: [PATCH 07/12] remove dismiss change --- .../react-tags/etc/react-tags.api.md | 36 +++++++++++-- .../react-components/react-tags/package.json | 1 - .../src/components/Tag/Tag.test.tsx | 32 +----------- .../react-tags/src/components/Tag/useTag.tsx | 28 +--------- .../components/TagButton/TagButton.test.tsx | 32 ------------ .../src/components/TagButton/useTagButton.tsx | 51 ++++--------------- .../src/components/TagGroup/TagGroup.tsx | 12 ++--- .../src/components/TagGroup/TagGroup.types.ts | 15 ++---- .../src/components/TagGroup/useTagGroup.ts | 19 ++----- .../TagGroup/useTagGroupContextValue.ts | 4 +- .../src/contexts/TagGroupContext.tsx | 3 +- .../TagGroup/TagGroupDismiss.stories.tsx | 34 ------------- .../stories/TagGroup/index.stories.tsx | 1 - 13 files changed, 58 insertions(+), 210 deletions(-) delete mode 100644 packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx diff --git a/packages/react-components/react-tags/etc/react-tags.api.md b/packages/react-components/react-tags/etc/react-tags.api.md index dfd6e1cc4e1b37..a2398a3b5d1cec 100644 --- a/packages/react-components/react-tags/etc/react-tags.api.md +++ b/packages/react-components/react-tags/etc/react-tags.api.md @@ -17,10 +17,13 @@ import { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public -export const renderTag_unstable: (state: TagState, contextValues: TagContextValues) => JSX.Element | null; +export const renderTag_unstable: (state: TagState, contextValues: TagContextValues) => JSX.Element; // @public -export const renderTagButton_unstable: (state: TagButtonState, contextValues: TagButtonContextValues) => JSX.Element | null; +export const renderTagButton_unstable: (state: TagButtonState, contextValues: TagButtonContextValues) => JSX.Element; + +// @public +export const renderTagGroup_unstable: (state: TagGroupState, contextValue: TagGroupContextValue) => JSX.Element; // @public export const Tag: ForwardRefComponent; @@ -47,14 +50,32 @@ export type TagButtonState = ComponentState & Omit; +// @public +export const TagGroup: ForwardRefComponent; + +// @public (undocumented) +export const tagGroupClassNames: SlotClassNames; + +// @public +export type TagGroupProps = ComponentProps & { + size?: TagSize; +}; + +// @public (undocumented) +export type TagGroupSlots = { + root: Slot<'div'>; +}; + +// @public +export type TagGroupState = ComponentState & Required>; + // @public export type TagProps = ComponentProps> & { appearance?: 'filled-darker' | 'filled-lighter' | 'tint' | 'outline'; disabled?: boolean; dismissible?: boolean; - onDismiss?: (e: React_2.MouseEvent | React_2.KeyboardEvent) => void; shape?: 'rounded' | 'circular'; - size?: 'extra-small' | 'small' | 'medium'; + size?: TagSize; }; // @public (undocumented) @@ -71,7 +92,6 @@ export type TagSlots = { export type TagState = ComponentState & Required & { avatarSize: AvatarSize | undefined; avatarShape: AvatarShape | undefined; - dismissed: boolean; }>; // @public @@ -83,6 +103,12 @@ export const useTagButton_unstable: (props: TagButtonProps, ref: React_2.Ref TagButtonState; +// @public +export const useTagGroup_unstable: (props: TagGroupProps, ref: React_2.Ref) => TagGroupState; + +// @public +export const useTagGroupStyles_unstable: (state: TagGroupState) => TagGroupState; + // @public export const useTagStyles_unstable: (state: TagState) => TagState; diff --git a/packages/react-components/react-tags/package.json b/packages/react-components/react-tags/package.json index 24f18e74ab125f..28e55548c911dd 100644 --- a/packages/react-components/react-tags/package.json +++ b/packages/react-components/react-tags/package.json @@ -34,7 +34,6 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { - "@fluentui/keyboard-keys": "^9.0.3", "@fluentui/react-aria": "^9.3.19", "@fluentui/react-avatar": "^9.5.0", "@fluentui/react-icons": "^2.0.196", diff --git a/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx b/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx index d35b3c004850eb..174ddfaad88b70 100644 --- a/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx +++ b/packages/react-components/react-tags/src/components/Tag/Tag.test.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Tag } from './Tag'; import { isConformant } from '../../testing/isConformant'; import { TagProps } from './Tag.types'; -import { render, fireEvent } from '@testing-library/react'; +import { render } from '@testing-library/react'; import { tagClassNames } from './useTagStyles.styles'; const requiredProps: TagProps = { @@ -18,40 +18,10 @@ describe('Tag', () => { Component: Tag, displayName: 'Tag', requiredProps, - disabledTests: [ - // onDismiss: A second (data) argument is not needed - 'consistent-callback-args', - ], }); it('should render root as a button', () => { const { getByRole } = render(Tag); expect(getByRole('button').className.includes(tagClassNames.root)).toBe(true); }); - - it('should invoke onDismiss on click', () => { - const onDismiss = jest.fn(); - const { getByRole } = render( - - Tag - , - ); - - fireEvent.click(getByRole('button')); - - expect(onDismiss).toHaveBeenCalledTimes(1); - }); - - it('should invoke onDismiss on delete keyDown', () => { - const onDismiss = jest.fn(); - const { getByRole } = render( - - Tag - , - ); - - fireEvent.keyDown(getByRole('button'), { key: 'Delete' }); - - expect(onDismiss).toHaveBeenCalledTimes(1); - }); }); diff --git a/packages/react-components/react-tags/src/components/Tag/useTag.tsx b/packages/react-components/react-tags/src/components/Tag/useTag.tsx index 5bbc519582e092..d899ebadb96e41 100644 --- a/packages/react-components/react-tags/src/components/Tag/useTag.tsx +++ b/packages/react-components/react-tags/src/components/Tag/useTag.tsx @@ -1,8 +1,7 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useEventCallback, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagProps, TagState } from './Tag.types'; -import { Delete, Backspace } from '@fluentui/keyboard-keys'; import { useTagGroupContext_unstable } from '../../contexts/TagGroupContext'; const tagAvatarSizeMap = { @@ -28,7 +27,7 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of Tag */ export const useTag_unstable = (props: TagProps, ref: React.Ref): TagState => { - const { handleTagDismiss, size: contextSize } = useTagGroupContext_unstable(); + const { size: contextSize } = useTagGroupContext_unstable(); const { appearance = 'filled-lighter', @@ -38,26 +37,6 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T size = contextSize, } = props; - const id = useId('fui-Tag', props.id); - - const handleClick = useEventCallback( - (ev: React.MouseEvent) => { - props.onClick?.(ev); - if (!ev.defaultPrevented) { - handleTagDismiss?.(ev, id); - } - }, - ); - - const handleKeyDown = useEventCallback( - (ev: React.KeyboardEvent) => { - props?.onKeyDown?.(ev); - if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { - handleTagDismiss?.(ev, id); - } - }, - ); - return { appearance, avatarShape: tagAvatarShapeMap[shape], @@ -79,9 +58,6 @@ export const useTag_unstable = (props: TagProps, ref: React.Ref): T root: getNativeElementProps('button', { ref, ...props, - id, - onClick: handleClick, - onKeyDown: handleKeyDown, }), media: resolveShorthand(props.media), diff --git a/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx b/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx index 0e83fc67dd4b30..003e20c001ac91 100644 --- a/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/TagButton.test.tsx @@ -1,7 +1,5 @@ -import * as React from 'react'; import { TagButton } from './TagButton'; import { isConformant } from '../../testing/isConformant'; -import { render, fireEvent } from '@testing-library/react'; const requiredProps = { media: 'media', @@ -16,35 +14,5 @@ describe('TagButton', () => { Component: TagButton, displayName: 'TagButton', requiredProps, - disabledTests: [ - // onDismiss: A second (data) argument is not needed - 'consistent-callback-args', - ], - }); - - it('should invoke onDismiss on dismiss button click', () => { - const onDismiss = jest.fn(); - const { getByTestId } = render( - }> - Tag - , - ); - - fireEvent.click(getByTestId('foo')); - - expect(onDismiss).toHaveBeenCalledTimes(1); - }); - - it('should invoke onDismiss on dismiss button delete keyDown', () => { - const onDismiss = jest.fn(); - const { getByTestId } = render( - }> - Tag - , - ); - - fireEvent.keyDown(getByTestId('foo'), { key: 'Delete' }); - - expect(onDismiss).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx index cb8edcaf2636a3..352b01ff6cec3c 100644 --- a/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx +++ b/packages/react-components/react-tags/src/components/TagButton/useTagButton.tsx @@ -1,9 +1,8 @@ import * as React from 'react'; -import { getNativeElementProps, resolveShorthand, useEventCallback, useId } from '@fluentui/react-utilities'; +import { getNativeElementProps, resolveShorthand } from '@fluentui/react-utilities'; import { DismissRegular, bundleIcon, DismissFilled } from '@fluentui/react-icons'; import type { TagButtonProps, TagButtonState } from './TagButton.types'; import { useARIAButtonShorthand } from '@fluentui/react-aria'; -import { Delete, Backspace } from '@fluentui/keyboard-keys'; import { useTagGroupContext_unstable } from '../../contexts/TagGroupContext'; const tagButtonAvatarSizeMap = { @@ -29,7 +28,7 @@ const DismissIcon = bundleIcon(DismissFilled, DismissRegular); * @param ref - reference to root HTMLElement of TagButton */ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref): TagButtonState => { - const { handleTagDismiss, size: contextSize } = useTagGroupContext_unstable(); + const { size: contextSize } = useTagGroupContext_unstable(); const { appearance = 'filled-lighter', @@ -39,35 +38,6 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref) => { - props.onClick?.(ev); - if (!ev.defaultPrevented) { - handleTagDismiss?.(ev, id); - } - }, - ); - - const onDismissButtonKeyDown = useEventCallback( - (ev: React.KeyboardEvent) => { - props?.onKeyDown?.(ev); - if (!ev.defaultPrevented && (ev.key === Delete || ev.key === Backspace)) { - handleTagDismiss?.(ev, id); - } - }, - ); - - const dismissButtonShorthand = useARIAButtonShorthand(props.dismissButton, { - required: props.dismissible, - defaultProps: { - disabled, - type: 'button', - children: , - }, - }); - return { appearance, avatarShape: tagButtonAvatarShapeMap[shape], @@ -90,7 +60,6 @@ export const useTagButton_unstable = (props: TagButtonProps, ref: React.Ref, + }, + }), }; }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx index 0b434798509fc8..7d1faeb4075214 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx @@ -9,13 +9,11 @@ import { useTagGroupContextValue_unstable } from './useTagGroupContextValue'; /** * TagGroup component - TODO: add more docs */ -export const TagGroup: ForwardRefComponent & ((props: TagGroupProps) => JSX.Element) = - React.forwardRef((props, ref) => { - const state = useTagGroup_unstable(props, ref); +export const TagGroup: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useTagGroup_unstable(props, ref); - useTagGroupStyles_unstable(state); - - return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); - }) as ForwardRefComponent & ((props: TagGroupProps) => JSX.Element); + useTagGroupStyles_unstable(state); + return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); +}); TagGroup.displayName = 'TagGroup'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index 3f33023790ccc1..a2e7d99fd4f8d1 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -1,27 +1,18 @@ -import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { TagProps, TagSize } from '../Tag/Tag.types'; +import { TagSize } from '../Tag/Tag.types'; export type TagGroupSlots = { root: Slot<'div'>; }; -export type ItemRenderFunction = (item: TItem) => React.ReactNode; - /** * TagGroup Props */ -export type TagGroupProps = Omit, 'children'> & { - children: React.ReactNode | ItemRenderFunction; - items?: Array; - onDismiss?: (e: React.MouseEvent | React.KeyboardEvent, dismissedTagIds: string[]) => void; +export type TagGroupProps = ComponentProps & { size?: TagSize; }; /** * State used in rendering TagGroup */ -export type TagGroupState = ComponentState & - Required> & { - handleTagDismiss: (e: React.MouseEvent | React.KeyboardEvent, id: string) => void; - }; +export type TagGroupState = ComponentState & Required>; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts index 2d7f3d5317d6a9..3666ddbe33f8dd 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroup.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { getNativeElementProps, useMergedRefs, useEventCallback } from '@fluentui/react-utilities'; +import { getNativeElementProps } from '@fluentui/react-utilities'; import type { TagGroupProps, TagGroupState } from './TagGroup.types'; /** @@ -12,29 +12,16 @@ import type { TagGroupProps, TagGroupState } from './TagGroup.types'; * @param ref - reference to root HTMLElement of TagGroup */ export const useTagGroup_unstable = (props: TagGroupProps, ref: React.Ref): TagGroupState => { - const innerRef = React.useRef(null); - - const { children, onDismiss, items = [], size = 'medium' } = props; - - const handleTagDismiss = useEventCallback((e: React.MouseEvent | React.KeyboardEvent, id: string) => { - onDismiss?.(e, [id]); - - // TODO set focus after tag dismiss - }); + const { size = 'medium' } = props; return { - handleTagDismiss, - items, size, - components: { root: 'div', }, - root: getNativeElementProps('div', { - ref: useMergedRefs(ref, innerRef), + ref, ...props, - children: typeof children === 'function' ? items.map(item => children(item)) : children, // TODO aria attributes }), }; diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts index 847f025d6f3e96..8663d331c8f123 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts @@ -3,6 +3,6 @@ import type { TagGroupContextValue } from '../../contexts/TagGroupContext'; import type { TagGroupState } from './TagGroup.types'; export function useTagGroupContextValue_unstable(state: TagGroupState): TagGroupContextValue { - const { handleTagDismiss, size } = state; - return React.useMemo(() => ({ handleTagDismiss, size }), [handleTagDismiss, size]); + const { size } = state; + return React.useMemo(() => ({ size }), [size]); } diff --git a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx index 170ccfcc3c14ce..85a893fb4a9e3b 100644 --- a/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx +++ b/packages/react-components/react-tags/src/contexts/TagGroupContext.tsx @@ -4,14 +4,13 @@ import { TagGroupState } from '../components/TagGroup/index'; export const TagGroupContext = React.createContext(undefined); const tagGroupContextDefaultValue: TagGroupContextValue = { - handleTagDismiss: () => null, size: 'medium', }; /** * Context shared between TagGroup and its children components */ -export type TagGroupContextValue = Required>; +export type TagGroupContextValue = Required>; export const TagGroupContextProvider = TagGroupContext.Provider; diff --git a/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx deleted file mode 100644 index 8f6480a640efb6..00000000000000 --- a/packages/react-components/react-tags/stories/TagGroup/TagGroupDismiss.stories.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import * as React from 'react'; -import { TagGroup, Tag, TagButton, TagButtonProps, TagProps } from '@fluentui/react-tags'; - -export const Dismiss = () => { - const defaultItems = [ - { id: '1', children: 'Tag 1' }, - { id: '2', children: 'Tag 2' }, - { id: 'tagButton-foo', children: 'Foo' }, - { id: 'tagButton-bar', children: 'Bar' }, - ]; - - const [items, setItems] = React.useState(defaultItems); - - const removeItem = (_e: React.MouseEvent | React.KeyboardEvent, ids: string[]) => { - setItems(prevItems => prevItems.filter(item => ids[0] !== item.id)); - }; - - const isTagButton = (item: TagProps | TagButtonProps): item is TagButtonProps => !!item.id?.startsWith('tagButton'); - - return ( - items={items} onDismiss={removeItem}> - {item => (isTagButton(item) ? : )} - - ); -}; - -Dismiss.storyName = 'Dismiss'; -Dismiss.parameters = { - docs: { - description: { - story: 'A TagGroup contains a collection of Tag/TagButton that can be dismissed', - }, - }, -}; diff --git a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx index 09880bb5bd4975..58aa049b067af6 100644 --- a/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx +++ b/packages/react-components/react-tags/stories/TagGroup/index.stories.tsx @@ -4,7 +4,6 @@ import descriptionMd from './TagGroupDescription.md'; import bestPracticesMd from './TagGroupBestPractices.md'; export { Default } from './TagGroupDefault.stories'; -export { Dismiss } from './TagGroupDismiss.stories'; export { Sizes } from './TagGroupSizes.stories'; export default { From c05c35d116d67c1ec23ad858181d5687bf9df27e Mon Sep 17 00:00:00 2001 From: Amber Date: Thu, 18 May 2023 10:19:12 +0200 Subject: [PATCH 08/12] Update packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx Co-authored-by: Bernardo Sunderhus --- .../react-tags/src/components/TagGroup/renderTagGroup.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx index d4ec1061f28ccd..096817f6e237da 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx @@ -1,4 +1,6 @@ -import * as React from 'react'; +/** @jsxRuntime classic */ +/** @jsx createElement */ +import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; import type { TagGroupState, TagGroupSlots } from './TagGroup.types'; import { TagGroupContextProvider, TagGroupContextValue } from '../../contexts/TagGroupContext'; From 342b236bc86dd1654924e8638ea4006a322b7d3c Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Thu, 18 May 2023 10:26:07 +0200 Subject: [PATCH 09/12] TagGroupContextValue to TagGroupContextValuess --- .../react-tags/src/components/TagGroup/TagGroup.tsx | 4 ++-- .../react-tags/src/components/TagGroup/TagGroup.types.ts | 5 +++++ .../react-tags/src/components/TagGroup/renderTagGroup.tsx | 8 ++++---- .../src/components/TagGroup/useTagGroupContextValue.ts | 8 -------- .../src/components/TagGroup/useTagGroupContextValues.ts | 7 +++++++ 5 files changed, 18 insertions(+), 14 deletions(-) delete mode 100644 packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts create mode 100644 packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx index 7d1faeb4075214..763c28eafad0c2 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.tsx @@ -4,7 +4,7 @@ import { renderTagGroup_unstable } from './renderTagGroup'; import { useTagGroupStyles_unstable } from './useTagGroupStyles.styles'; import type { TagGroupProps } from './TagGroup.types'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; -import { useTagGroupContextValue_unstable } from './useTagGroupContextValue'; +import { useTagGroupContextValues_unstable } from './useTagGroupContextValues'; /** * TagGroup component - TODO: add more docs @@ -13,7 +13,7 @@ export const TagGroup: ForwardRefComponent = React.forwardRef((pr const state = useTagGroup_unstable(props, ref); useTagGroupStyles_unstable(state); - return renderTagGroup_unstable(state, useTagGroupContextValue_unstable(state)); + return renderTagGroup_unstable(state, useTagGroupContextValues_unstable(state)); }); TagGroup.displayName = 'TagGroup'; diff --git a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts index a2e7d99fd4f8d1..5bcf24c00691a9 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/TagGroup.types.ts @@ -1,5 +1,10 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; import { TagSize } from '../Tag/Tag.types'; +import { TagGroupContextValue } from '../../contexts/TagGroupContext'; + +export type TagGroupContextValues = { + tagGroup: TagGroupContextValue; +}; export type TagGroupSlots = { root: Slot<'div'>; diff --git a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx index 096817f6e237da..c49c2e386c0d06 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx +++ b/packages/react-components/react-tags/src/components/TagGroup/renderTagGroup.tsx @@ -2,17 +2,17 @@ /** @jsx createElement */ import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; -import type { TagGroupState, TagGroupSlots } from './TagGroup.types'; -import { TagGroupContextProvider, TagGroupContextValue } from '../../contexts/TagGroupContext'; +import type { TagGroupState, TagGroupSlots, TagGroupContextValues } from './TagGroup.types'; +import { TagGroupContextProvider } from '../../contexts/TagGroupContext'; /** * Render the final JSX of TagGroup */ -export const renderTagGroup_unstable = (state: TagGroupState, contextValue: TagGroupContextValue) => { +export const renderTagGroup_unstable = (state: TagGroupState, contextValue: TagGroupContextValues) => { const { slots, slotProps } = getSlotsNext(state); return ( - + ); diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts deleted file mode 100644 index 8663d331c8f123..00000000000000 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValue.ts +++ /dev/null @@ -1,8 +0,0 @@ -import * as React from 'react'; -import type { TagGroupContextValue } from '../../contexts/TagGroupContext'; -import type { TagGroupState } from './TagGroup.types'; - -export function useTagGroupContextValue_unstable(state: TagGroupState): TagGroupContextValue { - const { size } = state; - return React.useMemo(() => ({ size }), [size]); -} diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts new file mode 100644 index 00000000000000..efeebfb7564bdd --- /dev/null +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts @@ -0,0 +1,7 @@ +import * as React from 'react'; +import type { TagGroupContextValues, TagGroupState } from './TagGroup.types'; + +export function useTagGroupContextValues_unstable(state: TagGroupState): TagGroupContextValues { + const { size } = state; + return React.useMemo(() => ({ tagGroup: { size } }), [size]); +} From 2b9911b3530c3be65c4d71be4cb98a3fe08d211c Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Thu, 18 May 2023 10:28:23 +0200 Subject: [PATCH 10/12] api --- packages/react-components/react-tags/etc/react-tags.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-tags/etc/react-tags.api.md b/packages/react-components/react-tags/etc/react-tags.api.md index a2398a3b5d1cec..d0a1cca902c48c 100644 --- a/packages/react-components/react-tags/etc/react-tags.api.md +++ b/packages/react-components/react-tags/etc/react-tags.api.md @@ -23,7 +23,7 @@ export const renderTag_unstable: (state: TagState, contextValues: TagContextValu export const renderTagButton_unstable: (state: TagButtonState, contextValues: TagButtonContextValues) => JSX.Element; // @public -export const renderTagGroup_unstable: (state: TagGroupState, contextValue: TagGroupContextValue) => JSX.Element; +export const renderTagGroup_unstable: (state: TagGroupState, contextValue: TagGroupContextValues) => JSX.Element; // @public export const Tag: ForwardRefComponent; From 24f1dd2ecb2ec9043bf496474d2b8082792ce706 Mon Sep 17 00:00:00 2001 From: Amber Date: Thu, 18 May 2023 10:52:57 +0200 Subject: [PATCH 11/12] Update packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts Co-authored-by: Bernardo Sunderhus --- .../src/components/TagGroup/useTagGroupContextValues.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts index efeebfb7564bdd..8bb95c005d1025 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts @@ -3,5 +3,5 @@ import type { TagGroupContextValues, TagGroupState } from './TagGroup.types'; export function useTagGroupContextValues_unstable(state: TagGroupState): TagGroupContextValues { const { size } = state; - return React.useMemo(() => ({ tagGroup: { size } }), [size]); + return {tagGroup: React.useMemo(() => ({ size }), [size])}; } From 56767eb8512043844b89fb3e894b41dbfe391fa9 Mon Sep 17 00:00:00 2001 From: YuanboXue-Amber Date: Thu, 18 May 2023 10:56:14 +0200 Subject: [PATCH 12/12] prettier after merge suggestion --- .../src/components/TagGroup/useTagGroupContextValues.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts index 8bb95c005d1025..76e89d4bfc3f4e 100644 --- a/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts +++ b/packages/react-components/react-tags/src/components/TagGroup/useTagGroupContextValues.ts @@ -3,5 +3,5 @@ import type { TagGroupContextValues, TagGroupState } from './TagGroup.types'; export function useTagGroupContextValues_unstable(state: TagGroupState): TagGroupContextValues { const { size } = state; - return {tagGroup: React.useMemo(() => ({ size }), [size])}; + return { tagGroup: React.useMemo(() => ({ size }), [size]) }; }