Repository navigation
feat(ui): add appearance="buttons" to TabBar - #2020
MartinS-git wants to merge 4 commits into
Conversation
Add SecondaryTabs, SecondaryTab, and SecondaryTabPanel as a segmented-control tab strip for hierarchical navigation below primary TabNavigation. - Controlled and uncontrolled mode (activeTab / defaultTab / onTabChange) - Per-tab and global disabled state - Optional icons left and right of label - Full keyboard navigation per ARIA APG Tabs pattern: ArrowRight/Left with wrap-around, Home, End, skips disabled tabs - useId() for unique per-instance IDs — no collisions on pages with multiple SecondaryTabs instances - aria-controls / aria-labelledby relationship between tabs and panels - State marker classes: juno-secondary-tab-active, juno-secondary-tab-disabled - forwardRef + displayName on all three components - 14 CSS design tokens added to theme.css and global.css (LT + DT) - 36 tests covering rendering, controlled/uncontrolled, disabled, keyboard nav - Storybook stories under WIP/SecondaryTabs Signed-off-by: MartinS-git <info@eyepic.de>
Move SecondaryTab and SecondaryTabPanel from separate folders into SecondaryTabs/, matching the established single-folder convention. Rename SecondaryTab `icon` prop to `iconLeft` for consistency with `iconRight`. Add KnownIconsEnum-based select controls for iconLeft and iconRight in Storybook. Update all imports and re-exports in src/index.ts accordingly. Signed-off-by: MartinS-git <info@eyepic.de>
- Replace native disabled with aria-disabled on SecondaryTab so disabled tabs remain in the AT tree (NVDA/JAWS remove natively-disabled buttons) - Add handleClick guard that returns early when aria-disabled is true - Update keyboard-nav selector from [disabled] to [aria-disabled="true"] - Apply roving tabindex: active non-disabled tab gets tabIndex=0, all others -1 - Add aria-orientation="horizontal" on the tablist per ARIA APG - Wrap SecondaryTabs in forwardRef (SecondaryTab already had it) - Apply jn:text-theme-secondary-tab-disabled token on disabled tabs - Fix hover styles: only apply when not active AND not disabled - Add juno-secondary-tab-active class for active-state targeting in tests/CSS - Export useSecondaryTabsContext and SecondaryTabsContextType from package root - Update SecondaryTab.test: replace toBeDisabled() with aria-disabled and tabIndex checks - Update SecondaryTabs.test: replace toBeDisabled() with toHaveAttribute aria-disabled - Add props-forwarding test to SecondaryTabPanel.test - Remove dead Active story from SecondaryTab.stories, add Inactive story - Add docs descriptions to all stories across all three story files - Group all SecondaryTabs stories under WIP/SecondaryTabs/* sidebar folder - Update changeset with full A11y rationale Signed-off-by: MartinS-git <info@eyepic.de>
🦋 Changeset detectedLatest commit: 8ad6d86 The changes in this PR will be included in the next version bump. This PR includes changesets to release 9 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Signed-off-by: MartinS-git <info@eyepic.de>
|
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Disabled-state handling, form submission behavior, initial keyboard focus, and tab-panel accessibility require correction.
Review effort: Balanced
Findings: 5
Open (8)
Child disabled prop bypasses parent disabled state · New Button defaults to unintended form submission · New Active tabpanel is not keyboard focusable · New Uncontrolled tabs lack a keyboard entry point · New onTabChange fires for already-active tabs · New Accessibility note misstates disabled tab navigation · New Story tabs render outside the tablist · New SecondaryTabPanel story lacks a description · New
What changed in this PR
Adds an accessible SecondaryTabs component family to the UI design system.
Changes:
- Adds controlled/uncontrolled tabs, panels, keyboard navigation, and disabled states.
- Adds theme tokens and package exports.
- Adds Vitest coverage, Storybook stories, and release notes.
| File | Description |
|---|---|
theme.css |
Adds secondary-tab theme tokens. |
global.css |
Adds global theme token mappings. |
src/index.ts |
Exports the new public API. |
SecondaryTabs.component.tsx |
Implements tab context and container. |
SecondaryTab.component.tsx |
Implements tabs and keyboard navigation. |
SecondaryTabPanel.component.tsx |
Implements associated content panels. |
SecondaryTabs.test.tsx |
Tests container behavior. |
SecondaryTab.test.tsx |
Tests individual tabs. |
SecondaryTabPanel.test.tsx |
Tests panels and ref forwarding. |
SecondaryTabs.stories.tsx |
Demonstrates complete tab sets. |
SecondaryTab.stories.tsx |
Demonstrates tab states and icons. |
SecondaryTabPanel.stories.tsx |
Demonstrates panel usage. |
SecondaryTabs/index.ts |
Adds component-level exports. |
.changeset/secondary-tabs-component.md |
Documents the minor release. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
The implementation contradicts the PR scope and contains form, disabled-state, and keyboard-accessibility defects.
11 open findings
Implementation contradicts the advertised TabBar API change · New Unescaped tab values create invalid HTML IDs · New Unescaped values break aria-labelledby ID references · New onTabChange fires for already-active tabs Uncontrolled tabs lack a keyboard entry point Active tabpanel is not keyboard focusable Button defaults to unintended form submission Child disabled prop bypasses parent disabled state SecondaryTabPanel story lacks a description Story tabs render outside the tablist Accessibility note misstates disabled tab navigation
🧠 Review effort: Balanced


Summary
Implements
appearance="buttons"forTabBarandTabBarItem— a pill-style segmented control intended as a pre-defined filter UI forDataGridand similar contexts.Relates to #2028. Builds on #2019 (component rename/cleanup).
Changes:
TabBarAppearancetype extended:"main" | "content" | "buttons"TabBar: newtabBarButtonsStylescontainer (rounded pill, custom background viajn:bg-theme-tab-buttons)TabBarItem: newtabBarItemButtonStyles,tabBarItemButtonActiveStyles,tabBarItemButtonInactiveStyleswithjn:cursor-pointer; conditional application viaisButtonstheme.css/global.css: 6 new semantic tokens + LT/DT color values fortab-buttonsandtab-button-*TabBar.stories.tsx:ButtonsAppearancestory addedappearance="buttons"inTabBar.test.tsxandTabBarItem.test.tsxSecondaryTabscomponent family removed (replaced by this approach)Test plan
pnpm --filter @cloudoperators/juno-ui-components build— no TS errorsButtonsAppearancestory renders pill-style tabs in both light and dark themeDefaultandContentAppearancestories unaffected (no regression)