Skip to content

feat(react-headless-components-preview): add Dropdown component and stories#36101

Open
dmytrokirpa wants to merge 3 commits into
masterfrom
feat/headless-dropdown-stories
Open

feat(react-headless-components-preview): add Dropdown component and stories#36101
dmytrokirpa wants to merge 3 commits into
masterfrom
feat/headless-dropdown-stories

Conversation

@dmytrokirpa
Copy link
Copy Markdown
Contributor

@dmytrokirpa dmytrokirpa commented May 5, 2026

Summary

  • Adds a headless Dropdown component (with Listbox, Option, OptionGroup) to react-headless-components-preview
  • Uses popover attribute for Listbox (so no Portal needed)
  • Ports some jest/cypress adds some specific to headless tests
  • Ports 6 stories from react-combobox covering the behaviors that matter for headless usage: Default, Multiselect, Controlled, Grouped, ControllingOpenAndClose, Disabled

Stories

Story What it shows
Default Basic single-select with expand/clear icon toggle
Multiselect multiselect prop — comma-separated value, checkmarks on all options
Controlled value + selectedOptions + onOptionSelect fully controlled
Grouped OptionGroup with labels and separators
ControllingOpenAndClose open + onOpenChange driven by an external checkbox
Disabled Native disabled on the trigger button
Screen.Recording.2026-05-07.at.12.00.53.mov

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-headless-components-preview
react-headless-components-preview: entire library
107.894 kB
31.818 kB
127.872 kB
37.536 kB
19.978 kB
5.718 kB

🤖 This report was generated against 0403572a88a8fcd6a1dc7244fdfd21efe0787ec7

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Pull request demo site: URL

…tories

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@dmytrokirpa dmytrokirpa force-pushed the feat/headless-dropdown-stories branch from edf6790 to 1dab2d3 Compare May 7, 2026 09:35
@dmytrokirpa dmytrokirpa marked this pull request as ready for review May 7, 2026 09:49
@dmytrokirpa dmytrokirpa requested a review from a team as a code owner May 7, 2026 09:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new headless Dropdown API surface to @fluentui/react-headless-components-preview (built on @fluentui/react-combobox primitives), along with Storybook examples and Jest/Cypress coverage, and exposes it via a new package export entrypoint.

Changes:

  • Added headless Dropdown, Listbox, Option, and OptionGroup components (plus hooks/renderers/types) and a new ./dropdown export.
  • Implemented Listbox rendering via the HTML Popover API (popover + showPopover/hidePopover) and anchored positioning.
  • Added Storybook stories, CSS module styling for demos, and Jest/Cypress tests for key behaviors.

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/react-components/react-headless-components-preview/stories/src/Dropdown/index.stories.tsx Registers Dropdown stories and docs description.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownDefault.stories.tsx Default single-select story with clearable behavior.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownMultiselect.stories.tsx Multiselect story using headless Dropdown APIs.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownControlled.stories.tsx Controlled selection/value story.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownGrouped.stories.tsx Grouped options story using OptionGroup.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownControllingOpenAndClose.stories.tsx Controlled open/close story.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownDisabled.stories.tsx Disabled trigger story.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/DropdownDescription.md Component docs description shown in Storybook.
packages/react-components/react-headless-components-preview/stories/src/Dropdown/dropdown.module.css Demo styling and state-driven visuals via data attributes.
packages/react-components/react-headless-components-preview/library/src/dropdown.ts New public entrypoint re-exporting Dropdown-related APIs.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/useListboxSlot.ts Creates Listbox slot and syncs internal open with native popover visibility.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/useListboxPopupState.ts Shared state core for Dropdown/Combobox-like listbox popups (positioning + active-descendant wiring).
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/useDropdownContextValues.ts Bridges Dropdown state into combobox context values.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/useDropdown.ts Dropdown state construction (trigger slot, clear/expand slots, listbox slot wiring).
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/renderDropdown.tsx Renders Dropdown with context providers and conditional listbox rendering.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Dropdown.types.ts Public Dropdown types based on combobox base hook types plus data attrs.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Dropdown.tsx Public Dropdown component wrapper (hook + render + context values).
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/index.ts Barrel exports for Dropdown and subcomponents.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Dropdown.test.tsx Jest tests for basic rendering and data attributes.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Dropdown.cy.tsx Cypress tests for open/close, selection, keyboard nav, groups, etc.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/Listbox.tsx Headless Listbox wrapper component.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/useListbox.ts Listbox state hook (sets popover).
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/useListboxContextValues.ts Listbox context value derivation.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/renderListbox.tsx Re-exported listbox renderer.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/Listbox.types.ts Headless listbox types re-exported from combobox.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Listbox/index.ts Barrel exports for Listbox utilities/types.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Option/Option.tsx Headless Option wrapper component.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Option/useOption.ts Option hook adding data-disabled/data-selected.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Option/renderOption.tsx Re-exported option renderer.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Option/Option.types.ts Option types and data-attribute augmentation.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/Option/index.ts Barrel exports for Option utilities/types.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/OptionGroup/OptionGroup.tsx Headless OptionGroup wrapper component.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/OptionGroup/useOptionGroup.ts OptionGroup state hook wrapper.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/OptionGroup/renderOptionGroup.tsx Re-exported option group renderer.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/OptionGroup/OptionGroup.types.ts OptionGroup types re-exported from combobox.
packages/react-components/react-headless-components-preview/library/src/components/Dropdown/OptionGroup/index.ts Barrel exports for OptionGroup utilities/types.
packages/react-components/react-headless-components-preview/library/package.json Adds @fluentui/react-combobox dependency and ./dropdown export mapping.
packages/react-components/react-headless-components-preview/library/etc/dropdown.api.md API Extractor report for the new dropdown entrypoint.
packages/react-components/react-headless-components-preview/library/bundle-size/AllComponents.fixture.js Includes dropdown entrypoint in bundle-size fixture.
change/@fluentui-react-headless-components-preview-17c2b97d-c15d-45a5-a29a-1bcac5b709ad.json Beachball change file for the new feature.

Comment on lines +102 to +113
useIsomorphicLayoutEffect(() => {
const listboxElement = popoverRef.current;
if (!listboxElement) {
return;
}
if (open) {
if (!listboxElement.matches(':popover-open')) {
listboxElement.showPopover();
}
} else if (listboxElement.matches(':popover-open')) {
listboxElement.hidePopover();
}
Comment on lines +92 to +114
// Sync the native popover state with the internal `open` state. The internal ref captures the
// listbox DOM element so we can imperatively call `showPopover()` / `hidePopover()`.
const popoverRef = React.useRef<HTMLDivElement>(null);
const listboxRef = useMergedRefs(listboxSlot?.ref, ref, popoverRef);
if (listboxSlot) {
listboxSlot.ref = listboxRef;
listboxSlot.onMouseDown = onMouseDown;
listboxSlot.onClick = onClick;
}

useIsomorphicLayoutEffect(() => {
const listboxElement = popoverRef.current;
if (!listboxElement) {
return;
}
if (open) {
if (!listboxElement.matches(':popover-open')) {
listboxElement.showPopover();
}
} else if (listboxElement.matches(':popover-open')) {
listboxElement.hidePopover();
}
}, [open]);
Comment on lines +144 to +147
if (clearable && multiselect) {
// eslint-disable-next-line no-console
console.error(`[@fluentui/react-combobox] "clearable" prop is not supported in multiselect mode.`);
}
Comment on lines +36 to +41
<Option
className={styles.option}
key={option}
checkIcon={{ className: styles.checkIcon, children: <CheckmarkRegular /> }}
disabled={option === 'Ferret'}
>
@@ -0,0 +1 @@
A Dropdown is a selection component composed of a button and a list of options. The button displays the current selected item or a placeholder, and the list is visible on demand by clicking the button. Dropdowns are typically used in forms.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants