From d4ec30e00e1f08b41be8c82c31d2d7285f6d10d5 Mon Sep 17 00:00:00 2001 From: Dmytro Kirpa Date: Sun, 5 Apr 2026 15:47:23 +0200 Subject: [PATCH] feat(react-tag-picker): add base hooks for TagPicker components --- ...-c97b0cc0-e48a-4e22-986f-464ccc9476d3.json | 7 +++ .../library/etc/react-tag-picker.api.md | 48 +++++++++++++++++ .../react-tag-picker/library/package.json | 8 +-- .../react-tag-picker/library/src/TagPicker.ts | 9 +++- .../library/src/TagPickerButton.ts | 3 ++ .../library/src/TagPickerControl.ts | 3 ++ .../library/src/TagPickerGroup.ts | 9 +++- .../library/src/TagPickerInput.ts | 9 +++- .../components/TagPicker/TagPicker.types.ts | 10 ++++ .../library/src/components/TagPicker/index.ts | 4 +- .../src/components/TagPicker/useTagPicker.ts | 28 ++++++++-- .../TagPickerButton/TagPickerButton.types.ts | 10 ++++ .../src/components/TagPickerButton/index.ts | 10 +++- .../TagPickerButton/useTagPickerButton.tsx | 33 +++++++++--- .../TagPickerControl.types.ts | 10 ++++ .../src/components/TagPickerControl/index.ts | 4 +- .../TagPickerControl/useTagPickerControl.tsx | 53 ++++++++++++++----- .../TagPickerGroup/TagPickerGroup.types.ts | 14 ++++- .../src/components/TagPickerGroup/index.ts | 10 +++- .../TagPickerGroup/useTagPickerGroup.ts | 49 ++++++++++++----- .../TagPickerInput/TagPickerInput.types.ts | 10 ++++ .../src/components/TagPickerInput/index.ts | 10 +++- .../TagPickerInput/useTagPickerInput.tsx | 46 +++++++++++----- .../react-tag-picker/library/src/index.ts | 40 ++++++++++++-- yarn.lock | 22 ++++++++ 25 files changed, 384 insertions(+), 75 deletions(-) create mode 100644 change/@fluentui-react-tag-picker-c97b0cc0-e48a-4e22-986f-464ccc9476d3.json diff --git a/change/@fluentui-react-tag-picker-c97b0cc0-e48a-4e22-986f-464ccc9476d3.json b/change/@fluentui-react-tag-picker-c97b0cc0-e48a-4e22-986f-464ccc9476d3.json new file mode 100644 index 0000000000000..70fa20f0a8c93 --- /dev/null +++ b/change/@fluentui-react-tag-picker-c97b0cc0-e48a-4e22-986f-464ccc9476d3.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: add base hooks for TagPicker components", + "packageName": "@fluentui/react-tag-picker", + "email": "dmytrokirpa@microsoft.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 881255216a01f..f49e1f901f80d 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 @@ -28,6 +28,7 @@ import { OptionState } from '@fluentui/react-combobox'; import * as React_2 from 'react'; import { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; +import type { TagGroupBaseState } from '@fluentui/react-tags'; import { TagGroupContextValues } from '@fluentui/react-tags'; import type { TagGroupSlots } from '@fluentui/react-tags'; import type { TagGroupState } from '@fluentui/react-tags'; @@ -59,9 +60,21 @@ export const renderTagPickerOptionGroup: (state: TagPickerOptionGroupState) => J // @public export const TagPicker: React_2.FC; +// @public +export type TagPickerBaseProps = Omit; + +// @public +export type TagPickerBaseState = Omit; + // @public export const TagPickerButton: ForwardRefComponent; +// @public +export type TagPickerButtonBaseProps = Omit; + +// @public +export type TagPickerButtonBaseState = Omit; + // @public (undocumented) export const tagPickerButtonClassNames: SlotClassNames; @@ -110,6 +123,12 @@ export type TagPickerContextValues = { // @public export const TagPickerControl: ForwardRefComponent; +// @public +export type TagPickerControlBaseProps = TagPickerControlProps; + +// @public +export type TagPickerControlBaseState = Omit; + // @public (undocumented) export const tagPickerControlClassNames: SlotClassNames; @@ -132,6 +151,14 @@ export type TagPickerControlState = ComponentState; +// @public +export type TagPickerGroupBaseProps = TagPickerGroupProps; + +// @public +export type TagPickerGroupBaseState = TagGroupBaseState & { + hasSelectedOptions: boolean; +}; + // @public (undocumented) export const tagPickerGroupClassNames: SlotClassNames; @@ -149,6 +176,12 @@ export type TagPickerGroupState = TagGroupState & { // @public export const TagPickerInput: ForwardRefComponent; +// @public +export type TagPickerInputBaseProps = Omit; + +// @public +export type TagPickerInputBaseState = Omit; + // @public (undocumented) export const tagPickerInputClassNames: SlotClassNames; @@ -262,9 +295,15 @@ export type TagPickerState = ComponentState & Pick TagPickerState; +// @public +export const useTagPickerBase_unstable: (props: TagPickerBaseProps) => TagPickerBaseState; + // @public export const useTagPickerButton_unstable: (props: TagPickerButtonProps, ref: React_2.Ref) => TagPickerButtonState; +// @public +export const useTagPickerButtonBase_unstable: (props: TagPickerButtonBaseProps, ref: React_2.Ref) => TagPickerButtonBaseState; + // @public export const useTagPickerButtonStyles_unstable: (state: TagPickerButtonState) => TagPickerButtonState; @@ -274,6 +313,9 @@ export const useTagPickerContext_unstable: (selector: ContextSelector) => TagPickerControlState; +// @public +export const useTagPickerControlBase_unstable: (props: TagPickerControlBaseProps, ref: React_2.Ref) => TagPickerControlBaseState; + // @public export const useTagPickerControlStyles_unstable: (state: TagPickerControlState) => TagPickerControlState; @@ -283,12 +325,18 @@ export function useTagPickerFilter({ filter: filterOverride, noOptionsElement, r // @public export const useTagPickerGroup_unstable: (props: TagPickerGroupProps, ref: React_2.Ref) => TagPickerGroupState; +// @public +export const useTagPickerGroupBase_unstable: (props: TagPickerGroupBaseProps, ref: React_2.Ref) => TagPickerGroupBaseState; + // @public export const useTagPickerGroupStyles_unstable: (state: TagPickerGroupState) => TagPickerGroupState; // @public export const useTagPickerInput_unstable: (propsArg: TagPickerInputProps, ref: React_2.Ref) => TagPickerInputState; +// @public +export const useTagPickerInputBase_unstable: (propsArg: TagPickerInputBaseProps, ref: React_2.Ref) => TagPickerInputBaseState; + // @public export const useTagPickerInputStyles_unstable: (state: TagPickerInputState) => TagPickerInputState; diff --git a/packages/react-components/react-tag-picker/library/package.json b/packages/react-components/react-tag-picker/library/package.json index 171411cc1a15a..2ab201a574cf6 100644 --- a/packages/react-components/react-tag-picker/library/package.json +++ b/packages/react-components/react-tag-picker/library/package.json @@ -1,6 +1,6 @@ { "name": "@fluentui/react-tag-picker", - "version": "9.8.5", + "version": "9.8.2", "description": "FluentUI TagPicker component", "main": "lib-commonjs/index.js", "module": "lib/index.js", @@ -26,12 +26,12 @@ "@fluentui/react-tabster": "^9.26.13", "@fluentui/react-aria": "^9.17.10", "@fluentui/react-icons": "^2.0.245", - "@fluentui/react-combobox": "^9.17.0", - "@fluentui/react-tags": "^9.8.0", + "@fluentui/react-combobox": "^9.16.18", + "@fluentui/react-tags": "^9.7.17", "@fluentui/react-context-selector": "^9.2.15", "@fluentui/react-positioning": "^9.22.0", "@fluentui/keyboard-keys": "^9.0.8", - "@fluentui/react-field": "^9.5.0", + "@fluentui/react-field": "^9.4.16", "@griffel/react": "^1.5.32", "@swc/helpers": "^0.5.1" }, diff --git a/packages/react-components/react-tag-picker/library/src/TagPicker.ts b/packages/react-components/react-tag-picker/library/src/TagPicker.ts index 7dd02b0703a7e..cf417b6cd0be3 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPicker.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPicker.ts @@ -1,4 +1,6 @@ export type { + TagPickerBaseProps, + TagPickerBaseState, TagPickerContextValues, TagPickerOnOpenChangeData, TagPickerOnOptionSelectData, @@ -7,4 +9,9 @@ export type { TagPickerSlots, TagPickerState, } from './components/TagPicker/index'; -export { TagPicker, renderTagPicker_unstable, useTagPicker_unstable } from './components/TagPicker/index'; +export { + TagPicker, + renderTagPicker_unstable, + useTagPickerBase_unstable, + useTagPicker_unstable, +} from './components/TagPicker/index'; diff --git a/packages/react-components/react-tag-picker/library/src/TagPickerButton.ts b/packages/react-components/react-tag-picker/library/src/TagPickerButton.ts index 394270c955448..3f099bcd44338 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPickerButton.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPickerButton.ts @@ -1,4 +1,6 @@ export type { + TagPickerButtonBaseProps, + TagPickerButtonBaseState, TagPickerButtonProps, TagPickerButtonSlots, TagPickerButtonState, @@ -8,5 +10,6 @@ export { renderTagPickerButton_unstable, tagPickerButtonClassNames, useTagPickerButtonStyles_unstable, + useTagPickerButtonBase_unstable, useTagPickerButton_unstable, } from './components/TagPickerButton/index'; diff --git a/packages/react-components/react-tag-picker/library/src/TagPickerControl.ts b/packages/react-components/react-tag-picker/library/src/TagPickerControl.ts index 8387c04ee7b74..ee011fdd827e8 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPickerControl.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPickerControl.ts @@ -1,4 +1,6 @@ export type { + TagPickerControlBaseProps, + TagPickerControlBaseState, TagPickerControlCSSProperties, TagPickerControlInternalSlots, TagPickerControlProps, @@ -12,5 +14,6 @@ export { tagPickerControlAsideWidthToken, tagPickerControlClassNames, useTagPickerControlStyles_unstable, + useTagPickerControlBase_unstable, useTagPickerControl_unstable, } from './components/TagPickerControl/index'; diff --git a/packages/react-components/react-tag-picker/library/src/TagPickerGroup.ts b/packages/react-components/react-tag-picker/library/src/TagPickerGroup.ts index f757dfbc520e8..190fb3576a7bf 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPickerGroup.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPickerGroup.ts @@ -1,8 +1,15 @@ -export type { TagPickerGroupProps, TagPickerGroupSlots, TagPickerGroupState } from './components/TagPickerGroup/index'; +export type { + TagPickerGroupBaseProps, + TagPickerGroupBaseState, + TagPickerGroupProps, + TagPickerGroupSlots, + TagPickerGroupState, +} from './components/TagPickerGroup/index'; export { TagPickerGroup, renderTagPickerGroup_unstable, tagPickerGroupClassNames, useTagPickerGroupStyles_unstable, + useTagPickerGroupBase_unstable, useTagPickerGroup_unstable, } from './components/TagPickerGroup/index'; diff --git a/packages/react-components/react-tag-picker/library/src/TagPickerInput.ts b/packages/react-components/react-tag-picker/library/src/TagPickerInput.ts index e9d85a10c5480..381904707a864 100644 --- a/packages/react-components/react-tag-picker/library/src/TagPickerInput.ts +++ b/packages/react-components/react-tag-picker/library/src/TagPickerInput.ts @@ -1,8 +1,15 @@ -export type { TagPickerInputProps, TagPickerInputSlots, TagPickerInputState } from './components/TagPickerInput/index'; +export type { + TagPickerInputBaseProps, + TagPickerInputBaseState, + TagPickerInputProps, + TagPickerInputSlots, + TagPickerInputState, +} from './components/TagPickerInput/index'; export { TagPickerInput, renderTagPickerInput_unstable, tagPickerInputClassNames, useTagPickerInputStyles_unstable, + useTagPickerInputBase_unstable, useTagPickerInput_unstable, } from './components/TagPickerInput/index'; 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 a570846248686..8619777a9f865 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 @@ -106,3 +106,13 @@ export type TagPickerContextValues = { activeDescendant: ActiveDescendantContextValue; listbox: ListboxContextValue; }; + +/** + * TagPicker Base Props - omits design-only props + */ +export type TagPickerBaseProps = Omit; + +/** + * TagPicker Base State - omits design-only state + */ +export type TagPickerBaseState = Omit; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts index 641b7759a38a5..7190fe1f15671 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPicker/index.ts @@ -1,5 +1,7 @@ export { TagPicker } from './TagPicker'; export type { + TagPickerBaseProps, + TagPickerBaseState, TagPickerContextValues, TagPickerOnOpenChangeData, TagPickerOnOptionSelectData, @@ -9,4 +11,4 @@ export type { TagPickerState, } from './TagPicker.types'; export { renderTagPicker_unstable } from './renderTagPicker'; -export { useTagPicker_unstable } from './useTagPicker'; +export { useTagPickerBase_unstable, useTagPicker_unstable } from './useTagPicker'; 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 a4469cf77fda6..6b6de82af087f 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 @@ -3,13 +3,19 @@ import * as React from 'react'; import { elementContains, useEventCallback, useId, useMergedRefs } from '@fluentui/react-utilities'; import type { + TagPickerBaseProps, + TagPickerBaseState, TagPickerOnOpenChangeData, TagPickerOnOptionSelectData, TagPickerProps, TagPickerState, } from './TagPicker.types'; import { optionClassNames } from '@fluentui/react-combobox'; -import { PositioningShorthandValue, resolvePositioningShorthand, usePositioning } from '@fluentui/react-positioning'; +import { + type PositioningShorthandValue, + resolvePositioningShorthand, + usePositioning, +} from '@fluentui/react-positioning'; import { useActiveDescendant } from '@fluentui/react-aria'; import { useComboboxBaseState } from '@fluentui/react-combobox'; @@ -25,11 +31,27 @@ const fallbackPositions: PositioningShorthandValue[] = ['above', 'after', 'after * @param props - props from this instance of Picker */ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => { + const { size = 'medium', appearance = 'outline', ...baseProps } = props; + const state = useTagPickerBase_unstable(baseProps); + + return { + ...state, + size, + appearance, + }; +}; + +/** + * Create the base state required to render TagPicker, without design-only props. + * + * @param props - props from this instance of TagPicker (without size, appearance) + */ +export const useTagPickerBase_unstable = (props: TagPickerBaseProps): TagPickerBaseState => { const popoverId = useId('picker-listbox'); const triggerInnerRef = React.useRef(null); const secondaryActionRef = React.useRef(null); const tagPickerGroupRef = React.useRef(null); - const { positioning, size = 'medium', inline = false, noPopover = false, disableAutoFocus } = props; + const { positioning, inline = false, noPopover = false, disableAutoFocus } = props; const { targetRef, containerRef } = usePositioning({ position: 'below' as const, @@ -86,7 +108,6 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => secondaryActionRef, tagPickerGroupRef, targetRef, - size, inline, open: comboboxState.open, mountNode: comboboxState.mountNode, @@ -94,7 +115,6 @@ export const useTagPicker_unstable = (props: TagPickerProps): TagPickerState => comboboxState.onOptionClick(event); comboboxState.setOpen(event, false); }), - appearance: comboboxState.appearance, clearSelection: comboboxState.clearSelection, getOptionById: comboboxState.getOptionById, getOptionsMatchingValue: comboboxState.getOptionsMatchingValue, diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/TagPickerButton.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/TagPickerButton.types.ts index 12dc5d0b7a5e2..d641498aabd8c 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/TagPickerButton.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/TagPickerButton.types.ts @@ -21,3 +21,13 @@ export type TagPickerButtonState = ComponentState & Pick & { hasSelectedOption: boolean; }; + +/** + * TagPickerButton Base Props - omits design-only props + */ +export type TagPickerButtonBaseProps = Omit; + +/** + * TagPickerButton Base State - omits design-only state + */ +export type TagPickerButtonBaseState = Omit; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/index.ts index f2c5fc367d462..5946d29ca9575 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/index.ts @@ -1,5 +1,11 @@ export { TagPickerButton } from './TagPickerButton'; -export type { TagPickerButtonProps, TagPickerButtonSlots, TagPickerButtonState } from './TagPickerButton.types'; +export type { + TagPickerButtonBaseProps, + TagPickerButtonBaseState, + TagPickerButtonProps, + TagPickerButtonSlots, + TagPickerButtonState, +} from './TagPickerButton.types'; export { renderTagPickerButton_unstable } from './renderTagPickerButton'; -export { useTagPickerButton_unstable } from './useTagPickerButton'; +export { useTagPickerButtonBase_unstable, useTagPickerButton_unstable } from './useTagPickerButton'; export { tagPickerButtonClassNames, useTagPickerButtonStyles_unstable } from './useTagPickerButtonStyles.styles'; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/useTagPickerButton.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/useTagPickerButton.tsx index 83fea872073d8..d14a07a9f004c 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/useTagPickerButton.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerButton/useTagPickerButton.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import { useActiveDescendantContext } from '@fluentui/react-aria'; -import type { TagPickerButtonProps, TagPickerButtonState } from './TagPickerButton.types'; +import type { + TagPickerButtonBaseProps, + TagPickerButtonBaseState, + TagPickerButtonProps, + TagPickerButtonState, +} from './TagPickerButton.types'; import { useTagPickerContext_unstable } from '../../contexts/TagPickerContext'; import { useButtonTriggerSlot } from '@fluentui/react-combobox'; @@ -19,6 +24,23 @@ export const useTagPickerButton_unstable = ( props: TagPickerButtonProps, ref: React.Ref, ): TagPickerButtonState => { + const size = useTagPickerContext_unstable(ctx => ctx.size); + return { + ...useTagPickerButtonBase_unstable(props, ref), + size, + }; +}; + +/** + * Create the base state required to render TagPickerButton, without design-only props. + * + * @param props - props from this instance of PickerButton (without size, appearance) + * @param ref - reference to root HTMLButtonElement of PickerButton + */ +export const useTagPickerButtonBase_unstable = ( + props: TagPickerButtonBaseProps, + ref: React.Ref, +): TagPickerButtonBaseState => { const { controller: activeDescendantController } = useActiveDescendantContext(); const triggerRef = useTagPickerContext_unstable(ctx => ctx.triggerRef); const open = useTagPickerContext_unstable(ctx => ctx.open); @@ -39,7 +61,7 @@ export const useTagPickerButton_unstable = ( tabIndex: 0, children: value || - // @ts-expect-error - FIXME: TS2339: Property 'placeholder' does not exist on type 'TagPickerButtonProps' + // @ts-expect-error - FIXME: TS2339: Property 'placeholder' does not exist on type 'TagPickerButtonBaseProps' props.placeholder, 'aria-controls': open ? popoverId : undefined, ref, @@ -54,16 +76,11 @@ export const useTagPickerButton_unstable = ( }, }); - const size = useTagPickerContext_unstable(ctx => ctx.size); - - const state: TagPickerButtonState = { + return { components: { root: 'button', }, root, - size, hasSelectedOption, }; - - return state; }; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/TagPickerControl.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/TagPickerControl.types.ts index a53f92449a1eb..858d48de4d8b4 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/TagPickerControl.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/TagPickerControl.types.ts @@ -32,3 +32,13 @@ export type TagPickerControlState = ComponentState & { invalid: boolean; }; + +/** + * TagPickerControl Base Props - same as TagPickerControlProps (no design-only own props) + */ +export type TagPickerControlBaseProps = TagPickerControlProps; + +/** + * TagPickerControl Base State - omits design-only state sourced from context + */ +export type TagPickerControlBaseState = Omit; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/index.ts index aed19e5ed8110..7cfb36ceef3ea 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/index.ts @@ -1,5 +1,7 @@ export { TagPickerControl } from './TagPickerControl'; export type { + TagPickerControlBaseProps, + TagPickerControlBaseState, TagPickerControlCSSProperties, TagPickerControlInternalSlots, TagPickerControlProps, @@ -7,7 +9,7 @@ export type { TagPickerControlState, } from './TagPickerControl.types'; export { renderTagPickerControl_unstable } from './renderTagPickerControl'; -export { useTagPickerControl_unstable } from './useTagPickerControl'; +export { useTagPickerControlBase_unstable, useTagPickerControl_unstable } from './useTagPickerControl'; export { iconSizes, tagPickerControlAsideWidthToken, diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx index 5f74969d674ad..64627dba00cbd 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerControl/useTagPickerControl.tsx @@ -12,7 +12,12 @@ import { useMergedRefs, } from '@fluentui/react-utilities'; import { useFluent_unstable } from '@fluentui/react-shared-contexts'; -import type { TagPickerControlProps, TagPickerControlState } from './TagPickerControl.types'; +import type { + TagPickerControlBaseProps, + TagPickerControlBaseState, + TagPickerControlProps, + TagPickerControlState, +} from './TagPickerControl.types'; import { useTagPickerContext_unstable } from '../../contexts/TagPickerContext'; import { ChevronDownRegular } from '@fluentui/react-icons'; import { useResizeObserverRef } from '../../utils/useResizeObserverRef'; @@ -21,18 +26,15 @@ import { useFieldContext_unstable } from '@fluentui/react-field'; import { useExpandLabel } from '../../utils/useExpandLabel'; /** - * Create the state required to render PickerControl. - * - * The returned state can be modified with hooks such as usePickerControlStyles_unstable, - * before being passed to renderPickerControl_unstable. + * Create the base state required to render TagPickerControl, without design-only props. * * @param props - props from this instance of PickerControl * @param ref - reference to root HTMLDivElement of PickerControl */ -export const useTagPickerControl_unstable = ( - props: TagPickerControlProps, +export const useTagPickerControlBase_unstable = ( + props: TagPickerControlBaseProps, ref: React.Ref, -): TagPickerControlState => { +): TagPickerControlBaseState => { const targetRef = useTagPickerContext_unstable(ctx => ctx.targetRef); const triggerRef = useTagPickerContext_unstable(ctx => ctx.triggerRef); const tagPickerGroupRef = useTagPickerContext_unstable(ctx => ctx.tagPickerGroupRef); @@ -40,8 +42,6 @@ export const useTagPickerControl_unstable = ( const popoverId = useTagPickerContext_unstable(ctx => ctx.popoverId); const setOpen = useTagPickerContext_unstable(ctx => ctx.setOpen); const secondaryInnerActionRef = useTagPickerContext_unstable(ctx => ctx.secondaryActionRef); - const size = useTagPickerContext_unstable(ctx => ctx.size); - const appearance = useTagPickerContext_unstable(ctx => ctx.appearance); const disabled = useTagPickerContext_unstable(ctx => ctx.disabled); const invalid = useFieldContext_unstable()?.validationState === 'error'; const noPopover = useTagPickerContext_unstable(ctx => ctx.noPopover ?? false); @@ -67,7 +67,6 @@ export const useTagPickerControl_unstable = ( defaultProps: { 'aria-expanded': open, 'aria-disabled': disabled ? 'true' : undefined, - children: , role: 'button', }, elementType: 'span', @@ -115,7 +114,7 @@ export const useTagPickerControl_unstable = ( } }); - const state: TagPickerControlState = { + const state: TagPickerControlBaseState = { components: { root: 'div', expandIcon: 'span', @@ -134,8 +133,6 @@ export const useTagPickerControl_unstable = ( aside, expandIcon, secondaryAction, - size, - appearance, disabled, invalid, }; @@ -155,3 +152,31 @@ export const useTagPickerControl_unstable = ( return state; }; + +/** + * Create the state required to render PickerControl. + * + * The returned state can be modified with hooks such as usePickerControlStyles_unstable, + * before being passed to renderPickerControl_unstable. + * + * @param props - props from this instance of PickerControl + * @param ref - reference to root HTMLDivElement of PickerControl + */ +export const useTagPickerControl_unstable = ( + props: TagPickerControlProps, + ref: React.Ref, +): TagPickerControlState => { + const size = useTagPickerContext_unstable(ctx => ctx.size); + const appearance = useTagPickerContext_unstable(ctx => ctx.appearance); + const baseState = useTagPickerControlBase_unstable(props, ref); + + return { + ...baseState, + size, + appearance, + // Add design default icon for expandIcon + expandIcon: baseState.expandIcon + ? { ...baseState.expandIcon, children: baseState.expandIcon.children ?? } + : baseState.expandIcon, + }; +}; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/TagPickerGroup.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/TagPickerGroup.types.ts index 885a1ded71b86..cfac6f31f3c4e 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/TagPickerGroup.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/TagPickerGroup.types.ts @@ -1,4 +1,4 @@ -import type { TagGroupSlots, TagGroupState } from '@fluentui/react-tags'; +import type { TagGroupBaseState, TagGroupSlots, TagGroupState } from '@fluentui/react-tags'; import type { ComponentProps } from '@fluentui/react-utilities'; export type TagPickerGroupSlots = TagGroupSlots; @@ -14,3 +14,15 @@ export type TagPickerGroupProps = ComponentProps; export type TagPickerGroupState = TagGroupState & { hasSelectedOptions: boolean; }; + +/** + * TagPickerGroup Base Props - same as TagPickerGroupProps (no design-only own props) + */ +export type TagPickerGroupBaseProps = TagPickerGroupProps; + +/** + * TagPickerGroup Base State - omits design-only state sourced from TagPicker context + */ +export type TagPickerGroupBaseState = TagGroupBaseState & { + hasSelectedOptions: boolean; +}; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/index.ts index f779ab992a19c..36f35c7e81d39 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/index.ts @@ -1,5 +1,11 @@ export { TagPickerGroup } from './TagPickerGroup'; -export type { TagPickerGroupProps, TagPickerGroupSlots, TagPickerGroupState } from './TagPickerGroup.types'; +export type { + TagPickerGroupBaseProps, + TagPickerGroupBaseState, + TagPickerGroupProps, + TagPickerGroupSlots, + TagPickerGroupState, +} from './TagPickerGroup.types'; export { renderTagPickerGroup_unstable } from './renderTagPickerGroup'; -export { useTagPickerGroup_unstable } from './useTagPickerGroup'; +export { useTagPickerGroupBase_unstable, useTagPickerGroup_unstable } from './useTagPickerGroup'; export { tagPickerGroupClassNames, useTagPickerGroupStyles_unstable } from './useTagPickerGroupStyles.styles'; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/useTagPickerGroup.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/useTagPickerGroup.ts index 29601b0aa3da8..f480713c097bc 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/useTagPickerGroup.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerGroup/useTagPickerGroup.ts @@ -1,8 +1,13 @@ 'use client'; import * as React from 'react'; -import type { TagPickerGroupProps, TagPickerGroupState } from './TagPickerGroup.types'; -import { useTagGroup_unstable } from '@fluentui/react-tags'; +import type { + TagPickerGroupBaseProps, + TagPickerGroupBaseState, + TagPickerGroupProps, + TagPickerGroupState, +} from './TagPickerGroup.types'; +import { useTagGroupBase_unstable } from '@fluentui/react-tags'; import { useTagPickerContext_unstable } from '../../contexts/TagPickerContext'; import { isHTMLElement, useEventCallback, useMergedRefs } from '@fluentui/react-utilities'; import { tagPickerAppearanceToTagAppearance, tagPickerSizeToTagSize } from '../../utils/tagPicker2Tag'; @@ -10,25 +15,20 @@ import { useArrowNavigationGroup } from '@fluentui/react-tabster'; import { ArrowRight } from '@fluentui/keyboard-keys'; /** - * Create the state required to render TagPickerGroup. - * - * The returned state can be modified with hooks such as usePickerTagGroupStyles_unstable, - * before being passed to renderPickerTagGroup_unstable. + * Create the base state required to render TagPickerGroup, without design-only props. * * @param props - props from this instance of TagPickerGroup * @param ref - reference to root HTMLDivElement of TagPickerGroup */ -export const useTagPickerGroup_unstable = ( - props: TagPickerGroupProps, +export const useTagPickerGroupBase_unstable = ( + props: TagPickerGroupBaseProps, ref: React.Ref, -): TagPickerGroupState => { +): TagPickerGroupBaseState => { const hasSelectedOptions = useTagPickerContext_unstable(ctx => ctx.selectedOptions.length > 0); const hasOneSelectedOption = useTagPickerContext_unstable(ctx => ctx.selectedOptions.length === 1); const triggerRef = useTagPickerContext_unstable(ctx => ctx.triggerRef); const tagPickerGroupRef = useTagPickerContext_unstable(ctx => ctx.tagPickerGroupRef); const selectOption = useTagPickerContext_unstable(ctx => ctx.selectOption); - const size = useTagPickerContext_unstable(ctx => tagPickerSizeToTagSize(ctx.size)); - const appearance = useTagPickerContext_unstable(ctx => ctx.appearance); const disabled = useTagPickerContext_unstable(ctx => ctx.disabled); const arrowNavigationProps = useArrowNavigationGroup({ @@ -37,14 +37,12 @@ export const useTagPickerGroup_unstable = ( memorizeCurrent: true, }); - const state = useTagGroup_unstable( + const state = useTagGroupBase_unstable( { role: 'listbox', disabled, ...props, ...arrowNavigationProps, - size, - appearance: tagPickerAppearanceToTagAppearance(appearance), dismissible: true, onKeyDown: useEventCallback(event => { props.onKeyDown?.(event); @@ -73,3 +71,26 @@ export const useTagPickerGroup_unstable = ( hasSelectedOptions, }; }; + +/** + * Create the state required to render TagPickerGroup. + * + * The returned state can be modified with hooks such as usePickerTagGroupStyles_unstable, + * before being passed to renderPickerTagGroup_unstable. + * + * @param props - props from this instance of TagPickerGroup + * @param ref - reference to root HTMLDivElement of TagPickerGroup + */ +export const useTagPickerGroup_unstable = ( + props: TagPickerGroupProps, + ref: React.Ref, +): TagPickerGroupState => { + const size = useTagPickerContext_unstable(ctx => tagPickerSizeToTagSize(ctx.size)); + const appearance = useTagPickerContext_unstable(ctx => ctx.appearance); + + return { + ...useTagPickerGroupBase_unstable(props, ref), + size, + appearance: tagPickerAppearanceToTagAppearance(appearance), + }; +}; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/TagPickerInput.types.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/TagPickerInput.types.ts index a94a810f0372b..838179b806d65 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/TagPickerInput.types.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/TagPickerInput.types.ts @@ -23,3 +23,13 @@ export type TagPickerInputProps = Omit< */ export type TagPickerInputState = ComponentState & Pick; + +/** + * TagPickerInput Base Props - omits design-only props + */ +export type TagPickerInputBaseProps = Omit; + +/** + * TagPickerInput Base State - omits design-only state + */ +export type TagPickerInputBaseState = Omit; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/index.ts b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/index.ts index fadff01d521e8..64fd59bcc3016 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/index.ts +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/index.ts @@ -1,5 +1,11 @@ export { TagPickerInput } from './TagPickerInput'; -export type { TagPickerInputProps, TagPickerInputSlots, TagPickerInputState } from './TagPickerInput.types'; +export type { + TagPickerInputBaseProps, + TagPickerInputBaseState, + TagPickerInputProps, + TagPickerInputSlots, + TagPickerInputState, +} from './TagPickerInput.types'; export { renderTagPickerInput_unstable } from './renderTagPickerInput'; -export { useTagPickerInput_unstable } from './useTagPickerInput'; +export { useTagPickerInputBase_unstable, useTagPickerInput_unstable } from './useTagPickerInput'; export { tagPickerInputClassNames, useTagPickerInputStyles_unstable } from './useTagPickerInputStyles.styles'; diff --git a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx index 0e9a78911400b..1c0a100d7aaa1 100644 --- a/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx +++ b/packages/react-components/react-tag-picker/library/src/components/TagPickerInput/useTagPickerInput.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import type { TagPickerInputProps, TagPickerInputState } from './TagPickerInput.types'; +import type { + TagPickerInputBaseProps, + TagPickerInputBaseState, + TagPickerInputProps, + TagPickerInputState, +} from './TagPickerInput.types'; import { useActiveDescendantContext } from '@fluentui/react-aria'; import { useTagPickerContext_unstable } from '../../contexts/TagPickerContext'; import { @@ -18,25 +23,21 @@ import { tagPickerInputCSSRules } from '../../utils/tokens'; import { useFocusFinders } from '@fluentui/react-tabster'; /** - * Create the state required to render TagPickerInput. - * - * The returned state can be modified with hooks such as useTagPickerInputStyles_unstable, - * before being passed to renderTagPickerInput_unstable. + * Create the base state required to render TagPickerInput, without design-only props. * - * @param props - props from this instance of TagPickerInput - * @param ref - reference to root HTMLDivElement of TagPickerInput + * @param propsArg - props from this instance of TagPickerInput (without appearance) + * @param ref - reference to root HTMLInputElement of TagPickerInput */ -export const useTagPickerInput_unstable = ( - propsArg: TagPickerInputProps, +export const useTagPickerInputBase_unstable = ( + propsArg: TagPickerInputBaseProps, ref: React.Ref, -): TagPickerInputState => { +): TagPickerInputBaseState => { const props = useFieldControlProps_unstable(propsArg, { supportsLabelFor: true, supportsRequired: true, supportsSize: true, }); const { controller: activeDescendantController } = useActiveDescendantContext(); - const size = useTagPickerContext_unstable(ctx => ctx.size); const contextDisabled = useTagPickerContext_unstable(ctx => ctx.disabled); const tagPickerGroupRef = useTagPickerContext_unstable(ctx => ctx.tagPickerGroupRef); @@ -128,16 +129,33 @@ export const useTagPickerInput_unstable = ( }, ); - const state: TagPickerInputState = { + return { components: { root: 'input', }, root, disabled, - size, }; +}; - return state; +/** + * Create the state required to render TagPickerInput. + * + * The returned state can be modified with hooks such as useTagPickerInputStyles_unstable, + * before being passed to renderTagPickerInput_unstable. + * + * @param propsArg - props from this instance of TagPickerInput + * @param ref - reference to root HTMLInputElement of TagPickerInput + */ +export const useTagPickerInput_unstable = ( + propsArg: TagPickerInputProps, + ref: React.Ref, +): TagPickerInputState => { + const size = useTagPickerContext_unstable(ctx => ctx.size); + return { + ...useTagPickerInputBase_unstable(propsArg, ref), + size, + }; }; /** diff --git a/packages/react-components/react-tag-picker/library/src/index.ts b/packages/react-components/react-tag-picker/library/src/index.ts index 68e8e6f000f3a..c657d56c7db7f 100644 --- a/packages/react-components/react-tag-picker/library/src/index.ts +++ b/packages/react-components/react-tag-picker/library/src/index.ts @@ -1,5 +1,7 @@ -export { TagPicker, renderTagPicker_unstable, useTagPicker_unstable } from './TagPicker'; +export { TagPicker, renderTagPicker_unstable, useTagPickerBase_unstable, useTagPicker_unstable } from './TagPicker'; export type { + TagPickerBaseProps, + TagPickerBaseState, TagPickerContextValues, TagPickerProps, TagPickerSlots, @@ -13,9 +15,16 @@ export { tagPickerInputClassNames, renderTagPickerInput_unstable, useTagPickerInputStyles_unstable, + useTagPickerInputBase_unstable, useTagPickerInput_unstable, } from './TagPickerInput'; -export type { TagPickerInputProps, TagPickerInputSlots, TagPickerInputState } from './TagPickerInput'; +export type { + TagPickerInputBaseProps, + TagPickerInputBaseState, + TagPickerInputProps, + TagPickerInputSlots, + TagPickerInputState, +} from './TagPickerInput'; export { TagPickerList, tagPickerListClassNames, @@ -29,17 +38,31 @@ export { tagPickerButtonClassNames, renderTagPickerButton_unstable, useTagPickerButtonStyles_unstable, + useTagPickerButtonBase_unstable, useTagPickerButton_unstable, } from './TagPickerButton'; -export type { TagPickerButtonProps, TagPickerButtonSlots, TagPickerButtonState } from './TagPickerButton'; +export type { + TagPickerButtonBaseProps, + TagPickerButtonBaseState, + TagPickerButtonProps, + TagPickerButtonSlots, + TagPickerButtonState, +} from './TagPickerButton'; export { TagPickerControl, tagPickerControlClassNames, renderTagPickerControl_unstable, useTagPickerControlStyles_unstable, + useTagPickerControlBase_unstable, useTagPickerControl_unstable, } from './TagPickerControl'; -export type { TagPickerControlProps, TagPickerControlSlots, TagPickerControlState } from './TagPickerControl'; +export type { + TagPickerControlBaseProps, + TagPickerControlBaseState, + TagPickerControlProps, + TagPickerControlSlots, + TagPickerControlState, +} from './TagPickerControl'; export { TagPickerOption, tagPickerOptionClassNames, @@ -53,9 +76,16 @@ export { tagPickerGroupClassNames, renderTagPickerGroup_unstable, useTagPickerGroupStyles_unstable, + useTagPickerGroupBase_unstable, useTagPickerGroup_unstable, } from './TagPickerGroup'; -export type { TagPickerGroupProps, TagPickerGroupSlots, TagPickerGroupState } from './TagPickerGroup'; +export type { + TagPickerGroupBaseProps, + TagPickerGroupBaseState, + TagPickerGroupProps, + TagPickerGroupSlots, + TagPickerGroupState, +} from './TagPickerGroup'; export { TagPickerOptionGroup, diff --git a/yarn.lock b/yarn.lock index 2a0964696b419..addd6d498533f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1948,6 +1948,28 @@ lodash "^4.17.15" prop-types "^15.7.2" +"@fluentui/react-tag-picker@^9.8.5": + version "9.8.5" + resolved "https://registry.yarnpkg.com/@fluentui/react-tag-picker/-/react-tag-picker-9.8.5.tgz#7c94947047e953cb936bd2c5460140b9c433419b" + integrity sha512-uhZUWDdg7zmQNjb1/5YI3l6agSDg/yFFaYZDH4eQDOmKIm35jAT2GmEMZVomZZVW/dDhZpezfMWZA5r442cZYQ== + dependencies: + "@fluentui/keyboard-keys" "^9.0.8" + "@fluentui/react-aria" "^9.17.10" + "@fluentui/react-combobox" "^9.17.0" + "@fluentui/react-context-selector" "^9.2.15" + "@fluentui/react-field" "^9.5.0" + "@fluentui/react-icons" "^2.0.245" + "@fluentui/react-jsx-runtime" "^9.4.1" + "@fluentui/react-portal" "^9.8.11" + "@fluentui/react-positioning" "^9.22.0" + "@fluentui/react-shared-contexts" "^9.26.2" + "@fluentui/react-tabster" "^9.26.13" + "@fluentui/react-tags" "^9.8.0" + "@fluentui/react-theme" "^9.2.1" + "@fluentui/react-utilities" "^9.26.2" + "@griffel/react" "^1.5.32" + "@swc/helpers" "^0.5.1" + "@fluentui/state@^0.66.5": version "0.66.5" resolved "https://registry.yarnpkg.com/@fluentui/state/-/state-0.66.5.tgz#e7abc25d52610736b598e375d2078fec8ab1de29"