From 979c4da9c5ccf1b1c49f72e01df7f151d0842b2e Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Wed, 31 Jul 2024 16:07:10 +0200 Subject: [PATCH] feature: introduces noPopover property to TagPicker --- ...-f6c70a8c-6231-41fe-baa5-ce7d1e65417a.json | 7 ++ .../library/etc/react-tag-picker.api.md | 1 + .../components/TagPicker/TagPicker.types.ts | 7 ++ .../src/components/TagPicker/useTagPicker.ts | 16 +++-- .../TagPicker/TagPickerNoPopover.stories.tsx | 65 +++++++++++++++++++ .../stories/src/TagPicker/index.stories.tsx | 1 + 6 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 change/@fluentui-react-tag-picker-f6c70a8c-6231-41fe-baa5-ce7d1e65417a.json create mode 100644 packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx diff --git a/change/@fluentui-react-tag-picker-f6c70a8c-6231-41fe-baa5-ce7d1e65417a.json b/change/@fluentui-react-tag-picker-f6c70a8c-6231-41fe-baa5-ce7d1e65417a.json new file mode 100644 index 00000000000000..efd2c4f2d387be --- /dev/null +++ b/change/@fluentui-react-tag-picker-f6c70a8c-6231-41fe-baa5-ce7d1e65417a.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feature: introduces noPopover property to TagPicker", + "packageName": "@fluentui/react-tag-picker", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md index bcaae6880404a3..609c8d2392ddf1 100644 --- a/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md +++ b/packages/react-components/react-tag-picker/library/etc/react-tag-picker.api.md @@ -217,6 +217,7 @@ export type TagPickerOptionState = ComponentState & Pick & Pick & Pick, 'size' | 'appearance'> & { + noPopover?: boolean; onOpenChange?: EventHandler; onOptionSelect?: EventHandler; children: [JSX.Element, JSX.Element] | JSX.Element; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts index 1f2c9f3a075cac..c6996c9cc23c09 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/TagPicker.types.ts @@ -33,6 +33,13 @@ export type TagPickerProps = ComponentProps & 'positioning' | 'disabled' | 'defaultOpen' | 'selectedOptions' | 'defaultSelectedOptions' | 'open' > & Pick, 'size' | 'appearance'> & { + /** + * By default, when a single children is provided, the TagPicker will assume that the children + * is a popover. By setting this prop to true, the children will be treated as a trigger instead. + * + * @default false + */ + noPopover?: boolean; onOpenChange?: EventHandler; onOptionSelect?: EventHandler; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts index c68d4060ea2e12..004e2acd5fe242 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/useTagPicker.ts @@ -27,7 +27,7 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => const triggerInnerRef = React.useRef(null); const secondaryActionRef = React.useRef(null); const tagPickerGroupRef = React.useRef(null); - const { positioning, size = 'medium', inline = false } = props; + const { positioning, size = 'medium', inline = false, noPopover = false } = props; const { targetRef, containerRef } = usePositioning({ position: 'below' as const, @@ -69,7 +69,7 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => size: 'medium', }); - const { trigger, popover } = childrenToTriggerAndPopover(props.children); + const { trigger, popover } = childrenToTriggerAndPopover(props.children, noPopover); return { activeDescendantController, @@ -105,18 +105,18 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => }; }; -const childrenToTriggerAndPopover = (children?: React.ReactNode) => { +const childrenToTriggerAndPopover = (children: React.ReactNode, noPopover: boolean) => { const childrenArray = React.Children.toArray(children) as React.ReactElement[]; if (process.env.NODE_ENV !== 'production') { if (childrenArray.length === 0) { // eslint-disable-next-line no-console - console.warn('Picker must contain at least one child'); + console.warn('TagPicker must contain at least one child'); } if (childrenArray.length > 2) { // eslint-disable-next-line no-console - console.warn('Picker must contain at most two children'); + console.warn('TagPicker must contain at most two children'); } } @@ -126,7 +126,11 @@ const childrenToTriggerAndPopover = (children?: React.ReactNode) => { trigger = childrenArray[0]; popover = childrenArray[1]; } else if (childrenArray.length === 1) { - popover = childrenArray[0]; + if (noPopover) { + trigger = childrenArray[0]; + } else { + popover = childrenArray[0]; + } } return { trigger, popover }; }; diff --git a/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx new file mode 100644 index 00000000000000..271067a445a904 --- /dev/null +++ b/packages/react-components/react-tag-picker/stories/src/TagPicker/TagPickerNoPopover.stories.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import { + TagPicker, + TagPickerInput, + TagPickerControl, + TagPickerProps, + TagPickerGroup, +} from '@fluentui/react-components'; +import { Tag, Avatar, Field } from '@fluentui/react-components'; + +export const NoPopover = () => { + const [selectedOptions, setSelectedOptions] = React.useState([]); + + const onOptionSelect: TagPickerProps['onOptionSelect'] = (_, data) => { + setSelectedOptions(data.selectedOptions); + }; + const handleChange = (event: React.ChangeEvent) => { + setInputValue(event.currentTarget.value); + }; + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key === 'Enter' && inputValue) { + setInputValue(''); + setSelectedOptions(curr => [...curr, inputValue]); + } + }; + + const [inputValue, setInputValue] = React.useState(''); + + return ( + + + + + {selectedOptions.map((option, index) => ( + } + value={option} + > + {option} + + ))} + + + + + + ); +}; + +NoPopover.parameters = { + docs: { + description: { + story: ` +You can use the \`TagPicker\` without the popover with the list of options by providing the \`noPopover\` property. This is useful when you want to allow users to input their own tags. All you have to do is control the \`TagPickerInput\` value and handle the \`onKeyDown\` event to add the tag to the \`TagPicker\` when the user presses the Enter key. + `, + }, + }, +}; diff --git a/packages/react-components/react-tag-picker/stories/src/TagPicker/index.stories.tsx b/packages/react-components/react-tag-picker/stories/src/TagPicker/index.stories.tsx index c7e391e95bc08a..97b263c6324bee 100644 --- a/packages/react-components/react-tag-picker/stories/src/TagPicker/index.stories.tsx +++ b/packages/react-components/react-tag-picker/stories/src/TagPicker/index.stories.tsx @@ -23,6 +23,7 @@ export { SecondaryAction } from './TagPickerSecondaryAction.stories'; export { Grouped } from './TagPickerGrouped.stories'; export { TruncatedText } from './TagPickerTruncatedText.stories'; export { SingleSelect } from './TagPickerSingleSelect.stories'; +export { NoPopover } from './TagPickerNoPopover.stories'; export default { title: 'Components/TagPicker',