Skip to content

refactor(react-card): make base hooks tabster-free#36004

Open
dmytrokirpa wants to merge 2 commits intomicrosoft:masterfrom
dmytrokirpa:refactor/react-card-headless-base-hooks
Open

refactor(react-card): make base hooks tabster-free#36004
dmytrokirpa wants to merge 2 commits intomicrosoft:masterfrom
dmytrokirpa:refactor/react-card-headless-base-hooks

Conversation

@dmytrokirpa
Copy link
Copy Markdown
Contributor

Summary

Move @fluentui/react-tabster usage out of useCardBase_unstable and useCardSelectable into useCard_unstable. The base hooks no longer manage focusable-group attributes or the focus-aware selection-restriction predicate, so they can be consumed by headless component packages without pulling in @fluentui/react-tabster.

Changes

  • useCardBase_unstable no longer calls useFocusWithin / useFocusableGroup. It computes interactive purely from the presence of pointer/mouse event props.
  • useCardSelectable no longer calls useFocusFinders and no longer requires a card ref. It reads an optional shouldRestrictTriggerAction predicate from CardBaseProps. Events that target the auto-rendered checkbox / floating-action slot are always allowed (the predicate isn't called for them).
  • useCard_unstable now owns all focus management: it sets up useFocusWithin (merged with the user ref), builds a focus-aware restriction predicate via useFocusFinders, computes focusable-group attributes via useFocusableGroup, and applies them to state.root when the card isn't selectable/disabled and focusMode !== 'off'.
  • CardBaseProps exposes a new optional shouldRestrictTriggerAction field so consumers of the base hook can opt in to focus-aware restriction without a tabster dependency.

API change

-export type CardBaseProps = Omit<CardProps, 'appearance' | 'orientation' | 'size'>;
+export type CardBaseProps = Omit<CardProps, 'appearance' | 'orientation' | 'size'> & {
+    shouldRestrictTriggerAction?: (event: CardOnSelectionChangeEvent) => boolean;
+};

Verification

  • yarn nx run react-card:test — 88/88 pass, snapshots unchanged.
  • Behavior of the styled Card is preserved (focusable-group attributes, focus-within outline, focus-aware restriction).

Why

Prerequisite for adding a headless Card component to @fluentui/react-headless-components-preview (follow-up PR) that should not depend on @fluentui/react-tabster.

Move @fluentui/react-tabster usage out of useCardBase_unstable and
useCardSelectable into useCard_unstable. The base hooks no longer
manage focusable-group attributes or the focus-aware selection
restriction predicate.

useCardSelectable now reads an optional shouldRestrictTriggerAction
predicate from CardBaseProps; useCard_unstable provides one based on
useFocusFinders + useFocusWithin to preserve existing behavior. This
lets headless component packages consume the base hooks without
pulling in @fluentui/react-tabster.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-card
Card - All
105.222 kB
29.475 kB
105.213 kB
29.539 kB
-9 B
64 B
react-card
Card
97.84 kB
27.583 kB
97.831 kB
27.628 kB
-9 B
45 B
react-components
react-components: entire library
1.3 MB
324.941 kB
1.3 MB
325.021 kB
89 B
80 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-card
CardFooter
12.774 kB
5.117 kB
react-card
CardHeader
15.303 kB
5.976 kB
react-card
CardPreview
12.854 kB
5.252 kB
react-components
react-components: Button, FluentProvider & webLightTheme
70.397 kB
19.96 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
237.187 kB
68.891 kB
react-components
react-components: FluentProvider & webLightTheme
43.612 kB
14.022 kB
react-portal-compat
PortalCompatProvider
8.386 kB
2.624 kB
react-timepicker-compat
TimePicker
108.977 kB
36.038 kB
🤖 This report was generated against a90fad387b40614102e9bc9c93c822c2ab1e81ee

@github-actions
Copy link
Copy Markdown

Pull request demo site: URL

@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown

@github-actions github-actions Bot Apr 19, 2026

Choose a reason for hiding this comment

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

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Charts-DonutChart 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic.default.chromium.png 5581 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 7530 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic - RTL.default.chromium.png 5570 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.chromium.png 880 Changed
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 129 Changed
vr-tests-react-components/ProgressBar converged 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - Dark Mode.default.chromium.png 59 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness.default.chromium.png 27 Changed
vr-tests-react-components/ProgressBar converged.Indeterminate + thickness - High Contrast.default.chromium.png 114 Changed
vr-tests-react-components/TagPicker 3 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - Dark Mode.disabled input hover.chromium.png 658 Changed
vr-tests-react-components/TagPicker.disabled - RTL.chromium.png 635 Changed
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed

There were 3 duplicate changes discarded. Check the build logs for more information.

@dmytrokirpa dmytrokirpa requested a review from mainframev April 20, 2026 09:43
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.

1 participant