feat(Tabs): add trigger orientation and overflow handling#6488
feat(Tabs): add trigger orientation and overflow handling#6488mikenewbon wants to merge 10 commits into
Conversation
- `triggerOrientation: 'horizontal' | 'vertical'` stacks the trigger icon/avatar above the label. - `overflow: 'scroll' | 'wrap' | 'collapse'` handles tab lists that exceed their container. `wrap` enables flex wrapping (the indicator is hidden since a single-track indicator can't represent multiple rows). `collapse` measures triggers via ResizeObserver and tucks overflowing items into a "More" dropdown; works for both horizontal and vertical orientations. - Adds `moreLabel`/`moreIcon` props (defaults: "More" / `appConfig.ui.icons.ellipsis`) and a `more` slot that overrides the trigger content while keeping the dropdown behavior. Defaults preserve existing behavior: `triggerOrientation` defaults to `horizontal` and `overflow` is unset.
…ndling - Introduced `triggerOrientation` and `overflow` props for better tab management. - Added a new `manyItems` array to demonstrate multiple tab items with icons and content. - Updated the template to include new select options for `triggerOrientation` and `overflow`.
- Enhanced the tab component to support a custom indicator that adapts based on overflow settings. - Updated the `wrap` overflow option to allow for visible indicators and added styles for better visual feedback. - Refactored the logic for managing overflow items and custom indicators, ensuring proper visibility and positioning. - Adjusted the template to conditionally render the custom indicator based on the overflow state.
- Updated the tab component's class bindings to include additional conditions for measuring and collapse states. - Removed the specific class for the collapse trigger in the theme configuration, simplifying the styling logic. - Enhanced the visual responsiveness of the tab triggers when measuring and in collapse mode.
- Introduced new examples demonstrating the `overflow` prop with options for `collapse`, `scroll`, and `wrap`. - Added an example for `triggerOrientation` to showcase vertical tab layouts. - Updated documentation to include new examples and usage details for the `overflow` and `trigger-orientation` props.
Move the collapse overflow dropdown outside TabsList to satisfy tablist ARIA child requirements, add a tablist theme slot for scroll/wrap, fix data-slot on the More default content, and document class/ui props with variant and collapse accessibility tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Refactor the class bindings for the More trigger to utilize the ui.more method for better consistency. Adjust the tablist styling to include min-h-0 for improved layout handling. Update snapshots to reflect these changes in the Tabs component's rendering.
📝 WalkthroughWalkthroughAdds trigger-orientation and overflow props to UTabs with theme variants and compound rules, implements measurement-driven collapse/wrap behavior and a custom positioned indicator, exposes a "More" dropdown and a 'more' slot, updates emits typing, expands tests (including accessibility), adds documentation and examples, and wires interactive controls in the playground. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
src/theme/tabs.tsParsing error: Unexpected token { 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/runtime/components/Tabs.vue (1)
396-399: 💤 Low valueRedundant
setupCustomIndicator()call causes unnecessary observer churn.When
activeValue,isOverflowActive, orvisibleCountchanges, only the indicator position needs updating—the ResizeObserver was already set up byonMountedor the props watch. Currently, every tab selection disconnects and reconnects the observer, andupdateCustomIndicator()is called twice (once insidesetupCustomIndicator()at line 353, and again explicitly).Suggested simplification
watch([activeValue, isOverflowActive, visibleCount], () => { - setupCustomIndicator() updateCustomIndicator() })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/runtime/components/Tabs.vue` around lines 396 - 399, The watch on [activeValue, isOverflowActive, visibleCount] should not call setupCustomIndicator() because that re-installs the ResizeObserver unnecessarily; instead remove the setupCustomIndicator() call and only invoke updateCustomIndicator() from that watcher so we only reposition the indicator on value/overflow/visibility changes and rely on onMounted and the props watch to initialize the observer via setupCustomIndicator(). Ensure the watcher references the same reactive symbols (activeValue, isOverflowActive, visibleCount) and leave setupCustomIndicator() calls in onMounted and the props-related watch unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/content/docs/2.components/tabs.md`:
- Around line 258-285: The new overflow section uses inconsistent/setext-style
headings and component-example blocks causing markdownlint MD003/MD001; update
the three headings ("Scroll", "Wrap", "Collapse") to consistent ATX-style (e.g.,
"### Scroll", "### Wrap", "### Collapse") and ensure they follow the correct
heading level sequence used in this doc, and if lint still flags, convert the
offending ::component-example blocks (names 'tabs-overflow-scroll-example',
'tabs-overflow-wrap-example', 'tabs-overflow-collapse-example') to the same
directive syntax used elsewhere in the file so the `---` metadata isn’t parsed
as setext headings.
---
Nitpick comments:
In `@src/runtime/components/Tabs.vue`:
- Around line 396-399: The watch on [activeValue, isOverflowActive,
visibleCount] should not call setupCustomIndicator() because that re-installs
the ResizeObserver unnecessarily; instead remove the setupCustomIndicator() call
and only invoke updateCustomIndicator() from that watcher so we only reposition
the indicator on value/overflow/visibility changes and rely on onMounted and the
props watch to initialize the observer via setupCustomIndicator(). Ensure the
watcher references the same reactive symbols (activeValue, isOverflowActive,
visibleCount) and leave setupCustomIndicator() calls in onMounted and the
props-related watch unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3d8e0b0b-f884-42c8-b624-3d7ce201ea05
⛔ Files ignored due to path filters (2)
test/components/__snapshots__/Tabs-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Tabs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (9)
docs/app/components/content/examples/tabs/TabsOverflowCollapseExample.vuedocs/app/components/content/examples/tabs/TabsOverflowScrollExample.vuedocs/app/components/content/examples/tabs/TabsOverflowWrapExample.vuedocs/app/components/content/examples/tabs/TabsTriggerOrientationExample.vuedocs/content/docs/2.components/tabs.mdplaygrounds/nuxt/app/pages/components/tabs.vuesrc/runtime/components/Tabs.vuesrc/theme/tabs.tstest/components/Tabs.spec.ts
Normalize overflow docs headings to ATX style with inline component examples, and stop reconnecting the custom indicator ResizeObserver on every tab selection. Co-authored-by: Cursor <cursoragent@cursor.com>
commit: |
Eliminated the sections detailing the `class` and `ui` props from the Tabs documentation to streamline content and focus on relevant examples. This change enhances clarity and reduces redundancy in the documentation.
Move the scroll overflow handler to the outer list slot so the absolute indicator scrolls with the tabs while preserving the list padding that gives the indicator its proper inset-y height. Reverts the tablistRef indicator detour and restores the v4 single-indicator template. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/theme/tabs.ts (1)
175-180: 💤 Low valueConsider adding
orientation: 'horizontal'for consistency.While the component already defaults
orientationto'horizontal'viawithDefaults, including it indefaultVariantswould make the theme configuration more self-documenting and align with howtriggerOrientationis handled (defaulted in both places).📝 Optional addition
defaultVariants: { color: 'primary', variant: 'pill', size: 'md', + orientation: 'horizontal', triggerOrientation: 'horizontal' }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/theme/tabs.ts` around lines 175 - 180, Add orientation: 'horizontal' to the defaultVariants object in the tabs theme so the theme explicitly documents the same default applied via withDefaults; update the defaultVariants block (which currently contains color, variant, size, triggerOrientation) to include orientation: 'horizontal' to match the withDefaults setting and keep triggerOrientation handled consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/theme/tabs.ts`:
- Around line 175-180: Add orientation: 'horizontal' to the defaultVariants
object in the tabs theme so the theme explicitly documents the same default
applied via withDefaults; update the defaultVariants block (which currently
contains color, variant, size, triggerOrientation) to include orientation:
'horizontal' to match the withDefaults setting and keep triggerOrientation
handled consistently.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d09322b7-f414-4a85-93d5-a448a7cb1852
⛔ Files ignored due to path filters (2)
test/components/__snapshots__/Tabs-vue.spec.ts.snapis excluded by!**/*.snaptest/components/__snapshots__/Tabs.spec.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (1)
src/theme/tabs.ts
🔗 Linked issue
Resolves #6487
❓ Type of change
📚 Description
Adds two new props to
UTabsfor richer tab list layouts:trigger-orientation— stack icon/avatar above the label (vertical), with theme defaults tuned for compact stacked labels (text-[10px]/3, tighter gap/padding).overflow— handle tabs that don't fit:scroll— scroll along the list axiswrap— wrap onto multiple lines (custom 2D indicator)collapse— hide overflow behind a More dropdown (more-label,more-icon,#moreslot)Also adds
#list-leading/#list-trailingslots, splits the list into outerlist+ innertablistwrappers (so the More dropdown sits outsiderole="tablist"for accessibility), and uses a unified custom indicator in wrap/collapse modes so the active pill animates smoothly between visible tabs and More.Includes docs examples, playground controls, and expanded tests/snapshots.
📝 Checklist