Skip to content

feat(ui-kit-system): segment group with animated indicator, switch motion (#572 part 2) - #575

Open
omridevk wants to merge 1 commit into
mainfrom
issue-572-ui-kit-controls
Open

feat(ui-kit-system): segment group with animated indicator, switch motion (#572 part 2)#575
omridevk wants to merge 1 commit into
mainfrom
issue-572-ui-kit-controls

Conversation

@omridevk

Copy link
Copy Markdown
Contributor

Part 2 of the widget-settings lane (#572), scoped to packages/ui-kit-system. Independent of part 1.

SegmentGroup

New primitive built on Ark's segment group, following the package's existing conventions (module-level class constants, splitProps(props, ['class']), Object.assign({}, Ark, {...}) so every unstyled Ark part still passes through).

The selected indicator is a real layout animation, not a re-render. zag publishes the selected item's rect as --left / --top / --width / --height and reads --transition-duration and --transition-timing-function back out (@zag-js/radio-group/dist/radio-group.connect.mjs:198-210) — the same mechanism family as the tabs indicator. The indicator slides at 200ms on var(--chat-ease-expo), and motion-reduce:[--transition-duration:0.01ms] collapses it to a snap.

Root defaults orientation to horizontal; zag's own default is vertical, which is wrong for a segmented control. Vertical is still available and styled.

One primitive, not two

The spec calls for both a SegmentGroup and a RadioGroup. They are the same component. Ark's SegmentGroup and RadioGroup are both backed by the @zag-js/radio-group machine — same anatomy, same radio semantics, same WAI-ARIA radio keyboard support, same Indicator part; SegmentGroup.RootProvider even takes a UseRadioGroupReturn. A second file would have differed only in its class strings.

The scheme tiles the spec describes are a radio-semantics group with custom item rendering: SegmentGroup.Item takes arbitrary children, and the indicator auto-sizes to whichever item is selected, so tiles get the sliding selection for free. SegmentGroup.ItemControl (the radio dot) passes through unstyled on purpose — styling it would fight the segmented look, and a tile consumer wants its own mark.

Switch thumb

The dispatch was to add thumb motion only if it did not already animate. It looked like it did (translate-x-0.5 to translate-x-3.5 under trans-btn). It did not.

UnoCSS wind4 compiles translate-x-* to the translate property (computed: translate: 14px, transform: none), and trans-btn transitions transform, background-color, border-color, color. translate is not in that list, so the thumb jumped while the track colour faded underneath it. The thumb now transitions translate at 140ms on var(--chat-ease-expo), with motion-reduce:[transition-property:background-color] killing the travel while keeping the colour change.

Before/after frames sampled 50ms after the click: previously the thumb was already hard against the right edge; now it is genuinely mid-travel.

Tests and stories

Five browser tests (test/segment-group.browser.test.tsx, real Chromium): radiogroup role and accessible name, click selection, arrow-key selection with focus, disabled segment, and the indicator staying withheld until something is selected. Native role locators, web-first assertions, no test-ids, no polling, no CSS-value assertions — the visual and motion claims are carried by driven-flow screenshots instead.

Four stories: Default, Scheme (the auto/light/dark scheme axis with icons), Vertical, Disabled.

Notes

  • Items and the indicator use rounded-chat-surface-sm (4px) rather than the spec's 3px inner radius. --chat-radius-chip (3px) is defined in ui-kit-chat's token sheet, not ui-kit-system's, and no existing ui-kit-system component references it — using it here would make the base package depend on a downstream stylesheet and silently collapse to 0 wherever only ui-kit-system/tokens.css is loaded. Worth adding a 3px surface token if the mockup needs the tighter inner corner.
  • Segment items are min-h-8 (32px), consistent with Select.Item.

Observations, not addressed here

  • No trans-* shortcut in packages/uno-preset/src/motion.ts carries a motion-reduce variant, unlike every anim-* shortcut. Every consumer of trans-btn, trans-lift, trans-pop-in keeps sliding under prefers-reduced-motion. Fixed locally on the switch thumb; the systemic fix belongs in the preset.
  • trans-btn transitions transform while wind4 emits translate, so any other component pairing trans-btn with a translate-* utility is also silently not animating. Worth a sweep.
  • apps/storybook/.storybook/storybook.css has a global @media (prefers-reduced-motion: reduce) { * { transition-duration: 0.01ms !important } }. Any reduced-motion evidence captured in Storybook snaps whether or not the component asks to, so the screenshots above neutralize that rule before sampling.

Refs #572

🤖 Generated with Claude Code

…tion

Adds a SegmentGroup primitive built on Ark's segment group. Its selected
indicator is a real layout animation: zag publishes the selected item's rect
as --left/--top/--width/--height and reads --transition-duration and
--transition-timing-function back out, so the indicator slides between
segments the same way the tabs indicator does. Under prefers-reduced-motion
the duration collapses and the indicator snaps.

The switch thumb moved with `translate`, which trans-btn never listed among
its transitioned properties, so the thumb jumped. It now transitions
`translate` and snaps under reduced motion.

Refs #572

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@omridevk, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

Wait for the limit to reset, then comment @coderabbitai review or push new commits to the PR.

An organization admin can change what happens after included review limits in Billing.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d49ddf2-9eab-475b-a5d2-645bba091c81

📥 Commits

Reviewing files that changed from the base of the PR and between e1a0c8b and 60ed1c0.

📒 Files selected for processing (6)
  • .changeset/wild-pandas-smile.md
  • packages/ui-kit-system/src/index.tsx
  • packages/ui-kit-system/src/segment-group.stories.tsx
  • packages/ui-kit-system/src/segment-group.tsx
  • packages/ui-kit-system/src/switch.tsx
  • packages/ui-kit-system/test/segment-group.browser.test.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant