feat(react-headless-components-preview): add swatch picker controls - #36535
feat(react-headless-components-preview): add swatch picker controls#36535Dmytro Kirpa (dmytrokirpa) wants to merge 6 commits into
Conversation
e5c2da1 to
2454615
Compare
2454615 to
ad48178
Compare
ad48178 to
184eb5f
Compare
1703cf4 to
697f3cb
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 46 out of 47 changed files in this pull request and generated no new comments.
Suppressed comments (3)
packages/react-components/react-headless-components-preview/library/src/components/SwatchPicker/useSwatchPicker.ts:17
- The documented state-attribute contract is not implemented here.
useSwatchPickerBase_unstabledoes not emitdata-layoutordata-focus-mode, and this wrapper only adds navigation props; similarly, the direct Color/Empty wrappers never emit the advertiseddata-selected/data-disabledattributes. Consumers therefore cannot use the styling API promised by the PR andSwatchPickerDescription.md. Please decorate each affected base state, extend the corresponding state types, and assert these attributes in the component tests.
change/@fluentui-react-headless-components-preview-931596e2-bc46-4837-a7cc-db12b9edd5cd.json:3 - Beachball comments should omit the conventional-commit prefix because
packageNameon line 4 already supplies the package context. Keep the release-note text as the user-facing change description only.
"comment": "feat: add swatch picker components",
packages/react-components/react-headless-components-preview/library/src/hooks/useGridNavigation/useGridNavigation.ts:201
- Navigation keys are only canceled when a destination is found. At a non-circular boundary, in a single-cell grid, or when the target column has no enabled cell, ArrowUp/ArrowDown/Home/End retain their browser default and can scroll the page while focus remains in the grid. Cancel recognized navigation keys once a valid current cell is found, regardless of whether focus moves.
697f3cb to
ae8dc79
Compare
|
Pull request demo site: URL |
|
|
||
| if (focusMode === 'arrow') { | ||
| // eslint-disable-next-line react-hooks/immutability | ||
| baseState.root['focusgroup'] = baseState.isGrid ? 'grid' : 'radiogroup'; |
There was a problem hiding this comment.
depends on microsoft/polyfills#64, if that won't be merged we need to revert the grid part and replace it with Tabster hook
| @@ -0,0 +1,54 @@ | |||
| export { | |||
| // SwatchPicker | |||
There was a problem hiding this comment.
these comments are not useful, let's omit them
|
|
||
| Use `ColorSwatch`, `ImageSwatch`, and `EmptySwatch` as children of `SwatchPicker`. Give the picker | ||
| an accessible name and give each swatch an accessible name. The headless components expose | ||
| `data-layout`, `data-focus-mode`, `data-selected`, and `data-disabled` for styling behavior. |
There was a problem hiding this comment.
aren't we missing data-layout and data-focus-mode?
| ], | ||
| ]; | ||
|
|
||
| export const Grid = (): React.ReactNode => { |
There was a problem hiding this comment.
I've tried in deployed SB and could not navigate with arrow keys in any axes, only by tabbing
There was a problem hiding this comment.
| ); | ||
| }); | ||
|
|
||
| it('uses focusgroup for arrow navigation in a row', () => { |
| import type { | ||
| ImageSwatchBaseProps, | ||
| ImageSwatchBaseState, | ||
| ImageSwatchSlots as ImageSwatchBaseSlots, | ||
| } from '@fluentui/react-swatch-picker'; | ||
|
|
||
| export type ImageSwatchProps = ImageSwatchBaseProps; |
There was a problem hiding this comment.
| import type { | |
| ImageSwatchBaseProps, | |
| ImageSwatchBaseState, | |
| ImageSwatchSlots as ImageSwatchBaseSlots, | |
| } from '@fluentui/react-swatch-picker'; | |
| export type ImageSwatchProps = ImageSwatchBaseProps; | |
| import type { | |
| ImageSwatchBaseState, | |
| ImageSwatchSlots as ImageSwatchBaseSlots, | |
| } from '@fluentui/react-swatch-picker'; | |
| export type { ImageSwatchBaseProps as ImageSwatchProps } from '@fluentui/react-swatch-picker'; | |
| red.focus(); | ||
| expect(yellow).toHaveAttribute('tabindex', '-1'); | ||
|
|
||
| fireEvent.keyDown(red, { key: 'ArrowRight' }); | ||
| expect(yellow).toHaveFocus(); | ||
|
|
||
| fireEvent.keyDown(yellow, { key: 'ArrowDown' }); | ||
| expect(violet).toHaveFocus(); | ||
|
|
||
| fireEvent.keyDown(violet, { key: 'ArrowRight' }); | ||
| expect(red).toHaveFocus(); | ||
|
|
||
| fireEvent.keyDown(red, { key: 'ArrowUp' }); | ||
| expect(green).toHaveFocus(); | ||
| }); |
There was a problem hiding this comment.
q: shouldn't we use userEvent here instead of fireEvent? userEvent dispatches the events like they would happen if a user interacted with the document
a9592be to
d438344
Compare

Motivation
Add an unstyled SwatchPicker family to
@fluentui/react-headless-components-preview. The controls reuse the state and interaction behavior extracted in #36534 while leaving layout and visual treatment to consumers.Changes
SwatchPicker,SwatchPickerRow,ColorSwatch,ImageSwatch, andEmptySwatchcomponents@fluentui/react-swatch-pickerbase hooks and shared contextswatch-pickerpackage entry pointReview guidance
Review this PR relative to
feat/swatch-picker-headless-base. The component layer intentionally adds no Griffel styles; story styles are consumer examples only.Validation
yarn nx run-many -t lint test type-check bundle-size -p react-headless-components-preview --nxBailPR stack
Depends on #36534 and should be merged last.