feat(react-headless-components-preview): add TagPicker#36285
feat(react-headless-components-preview): add TagPicker#36285mainframev wants to merge 2 commits into
Conversation
d818805 to
91c0f8c
Compare
📊 Bundle size report
🤖 This report was generated against 23c05d1de3c0bcb9e84e77d534cbc6df33a974a5 |
|
Pull request demo site: URL |
009b3f7 to
85e4ed8
Compare
85e4ed8 to
c78d12c
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new headless TagPicker surface to @fluentui/react-headless-components-preview, along with Storybook examples and API/package wiring so consumers can compose TagPicker behavior without bundled styles.
Changes:
- Introduces the headless TagPicker root + subcomponents (Control/Input/Button/Group/List/Option/OptionGroup) and re-exports the relevant base utilities (
useTagPickerFilter,useTagPickerContext_unstable). - Adds a full Storybook story set (default, filtering, grouping, single-select, no-popover, truncation, secondary action) and shared CSS-module styling/helpers.
- Updates package exports, dependencies, API report, bundle-size fixture, and adds a beachball change file.
Reviewed changes
Copilot reviewed 53 out of 53 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/utils.tsx | Shared story helpers (initials/media, option/tag render helpers). |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerDefault.stories.tsx | Baseline multiselect TagPicker story. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerFiltering.stories.tsx | Filtering story using useTagPickerFilter. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerGrouped.stories.tsx | Grouped options story using TagPickerOptionGroup. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerSingleSelect.stories.tsx | Single-select pattern story (external state control). |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerSecondaryAction.stories.tsx | Story demonstrating secondaryAction slot on the control. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerTruncatedText.stories.tsx | Story demonstrating CSS-driven truncation. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerNoPopover.stories.tsx | Story demonstrating noPopover + free-text entry. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/TagPickerDescription.md | Component docs page content for the TagPicker story set. |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/tag-picker.module.css | CSS module used by TagPicker stories (control/list/tag/option styling). |
| packages/react-components/react-headless-components-preview/stories/src/TagPicker/index.stories.tsx | Storybook index/metadata + exports for the TagPicker story group. |
| packages/react-components/react-headless-components-preview/library/src/tag-picker.ts | Public subpath entry point re-exporting headless TagPicker API. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/index.ts | Barrel exports for TagPicker and subcomponents. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPicker.types.ts | Headless TagPicker prop/type shaping (omits styled-only props). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPicker.tsx | Headless TagPicker root component wiring state + context + render. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/useTagPicker.ts | Headless TagPicker state composition (base hook + positioning refs). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/useTagPickerContextValues.ts | TagPicker context value assembly for subcomponents. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/renderTagPicker.tsx | Re-export of base renderTagPicker_unstable as renderTagPicker. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerControl/TagPickerControl.tsx | Headless control component wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerControl/useTagPickerControl.ts | Control state wrapper adding data-* flags. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerControl/renderTagPickerControl.tsx | Control renderer re-export wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerControl/TagPickerControl.types.ts | Control state typing incl. internal slots and data-* flags. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerControl/index.ts | Control barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerInput/TagPickerInput.tsx | Headless input component wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerInput/useTagPickerInput.ts | Input state wrapper + ArrowLeft/Backspace focus behavior. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerInput/renderTagPickerInput.tsx | Input renderer re-export wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerInput/TagPickerInput.types.ts | Input state typing incl. data-disabled. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerInput/index.ts | Input barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerButton/TagPickerButton.tsx | Headless button trigger component wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerButton/useTagPickerButton.ts | Button trigger wrapper adding data-disabled. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerButton/renderTagPickerButton.tsx | Button renderer implementation using slots. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerButton/TagPickerButton.types.ts | Button trigger state typing incl. data-disabled. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerButton/index.ts | Button trigger barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerGroup/TagPickerGroup.tsx | Headless selected-tags group wrapper (adds TagGroup context). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerGroup/useTagPickerGroup.ts | Group state wrapper (dismiss wiring + focusgroup attribute + data-*). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerGroup/renderTagPickerGroup.tsx | Group renderer re-export wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerGroup/TagPickerGroup.types.ts | Group state typing incl. focusgroup + data-disabled. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerGroup/index.ts | Group barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerList/TagPickerList.tsx | Headless list/popover wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerList/useTagPickerList.ts | List state wrapper (Listbox slot + refs + id/role wiring). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerList/renderTagPickerList.tsx | List renderer implementation using slots. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerList/TagPickerList.types.ts | List props/state typing. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerList/index.ts | List barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerOption/TagPickerOption.tsx | Headless option component wrapper. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerOption/useTagPickerOption.ts | Option wrapper adding option-class marker + media/secondary slots. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerOption/renderTagPickerOption.tsx | Option renderer (media + content + secondary). |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerOption/TagPickerOption.types.ts | Option props/state typing for extra slots. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPickerOption/index.ts | Option barrel exports. |
| packages/react-components/react-headless-components-preview/library/src/components/TagPicker/TagPicker.test.tsx | Unit coverage for core rendering/roles/classes/focusgroup/disabled behavior. |
| packages/react-components/react-headless-components-preview/library/package.json | Adds dependency on @fluentui/react-tag-picker and exports ./tag-picker. |
| packages/react-components/react-headless-components-preview/library/etc/tag-picker.api.md | API Extractor report for the new TagPicker subpath. |
| packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js | Ensures TagPicker is included in bundle-size checks. |
| change/@fluentui-react-headless-components-preview-7694d78b-9ac6-40a0-8490-31128315e61a.json | Beachball change file for publishing the new headless TagPicker surface. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
dmytrokirpa
left a comment
There was a problem hiding this comment.
left some comments, we also need to add some e2e tests
| export const renderTagPickerButton = (state: TagPickerButtonState): JSXElement => { | ||
| assertSlots<TagPickerButtonSlots>(state); | ||
|
|
||
| return <state.root />; |
There was a problem hiding this comment.
why do we have a custom render fn for this, can't we re-use the original one?
| * secondary action. | ||
| */ | ||
| export type TagPickerControlInternalSlots = { | ||
| aside?: NonNullable<Slot<'span'>>; |
There was a problem hiding this comment.
why do we need this, is it not exported from the tag-picker?
|
|
||
| const onKeyDown = useEventCallback((event: React.KeyboardEvent<HTMLInputElement>) => { | ||
| props.onKeyDown?.(event); | ||
| if ( |
There was a problem hiding this comment.
why do we have this here? isn't a base hook responsible for the logic?
| /** | ||
| * Moves focus to the last focusable element within the tag group. The focusgroup polyfill | ||
| * manages roving `tabindex` on the tags, so the items carry `tabindex="-1"`; they remain | ||
| * programmatically focusable, which is why we don't filter on `tabindex`. |
There was a problem hiding this comment.
does this work without polyfill (chrome/edge canary)?
| export const renderTagPickerList = (state: TagPickerListState): JSXElement => { | ||
| assertSlots<TagPickerListSlots>(state); | ||
|
|
||
| return <state.root />; |
There was a problem hiding this comment.
can we use the original render function?
| import { useTagPickerContextValues } from './useTagPickerContextValues'; | ||
| import type { TagPickerProps } from './TagPicker.types'; | ||
|
|
||
| export const TagPicker: ForwardRefComponent<TagPickerProps> = React.forwardRef((props, _ref) => { |
There was a problem hiding this comment.
why do we use forwardRef if the ref is ignored?
| TagPickerOnOptionSelectData, | ||
| } from '@fluentui/react-tag-picker'; | ||
|
|
||
| export type TagPickerProps = Omit<TagPickerPropsBase, 'inline' | 'size' | 'appearance' | 'mountNode'>; |
There was a problem hiding this comment.
shouldn't this be done in the component package?
| const { targetRef, containerRef } = usePositioning({ | ||
| position: 'below', | ||
| align: 'start', | ||
| offset: { crossAxis: 0, mainAxis: 2 }, |
There was a problem hiding this comment.
do we need these pre-defined?
| return { | ||
| ...baseState, | ||
| targetRef: targetRef as unknown as TagPickerState['targetRef'], | ||
| popoverRef: useMergedRefs(baseState.popoverRef, containerRef) as unknown as TagPickerState['popoverRef'], |
There was a problem hiding this comment.
why do we need type-casts for both targetRef and popoverRef?
| * Mirrors the styled package's `useTagPickerContextValues`, which is not part of the | ||
| * public API. Pure context wiring — no styles. | ||
| */ | ||
| export function useTagPickerContextValues(state: TagPickerState): TagPickerContextValues { |
There was a problem hiding this comment.
we should export the hook from the component package

Adds a headless TagPicker to @fluentui/react-headless-components-preview - a composite, style-free picker that lets users select tags from a filterable list of options
useTagPickerButtonBase_unstable, useTagPickerContext_unstable (from @fluentui/react-tag-picker), useTagGroupBase_unstable (@fluentui/react-tags), and the headless
Dropdown's useOption/useListboxSlot (over @fluentui/react-combobox).
v9.