@maltjoy/mcp-server 0.4.0 → 0.6.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +30 -3
- package/dist/doc/VJoyAdminBanner/guideline.md +284 -0
- package/dist/doc/VJoyAvailability/guideline.md +352 -0
- package/dist/doc/VJoyAvatar/guideline.md +192 -0
- package/dist/doc/VJoyAvatarsList/guideline.md +328 -0
- package/dist/doc/VJoyBadge/guideline.md +169 -0
- package/dist/doc/VJoyBadgeLevel/guideline.md +166 -0
- package/dist/doc/VJoyBlockSkeleton/guideline.md +150 -0
- package/dist/doc/VJoyBottomSheet/guideline.md +345 -0
- package/dist/doc/VJoyBottomSheetTrigger/guideline.md +156 -0
- package/dist/doc/VJoyButton/guideline.md +371 -0
- package/dist/doc/VJoyButton/metadata.json +0 -16
- package/dist/doc/VJoyButtonCard/guideline.md +384 -0
- package/dist/doc/VJoyCheckbox/guideline.md +317 -0
- package/dist/doc/VJoyCheckboxCard/guideline.md +381 -0
- package/dist/doc/VJoyCheckboxGroup/guideline.md +363 -0
- package/dist/doc/VJoyCheckboxGroup/metadata.json +7 -7
- package/dist/doc/VJoyCollapse/guideline.md +362 -0
- package/dist/doc/VJoyCollapseItem/guideline.md +394 -0
- package/dist/doc/VJoyCompanyAvatar/guideline.md +162 -0
- package/dist/doc/VJoyDialog/guideline.md +425 -0
- package/dist/doc/VJoyDialogTrigger/guideline.md +186 -0
- package/dist/doc/VJoyDot/guideline.md +177 -0
- package/dist/doc/VJoyDrawer/guideline.md +392 -0
- package/dist/doc/VJoyDrawerTrigger/guideline.md +145 -0
- package/dist/doc/VJoyDropdown/guideline.md +393 -0
- package/dist/doc/VJoyDropdown/metadata.json +6 -6
- package/dist/doc/VJoyDropdownList/guideline.md +396 -0
- package/dist/doc/VJoyDropdownList/metadata.json +7 -7
- package/dist/doc/VJoyDropzone/guideline.md +390 -0
- package/dist/doc/VJoyDropzone/metadata.json +36 -1
- package/dist/doc/VJoyFilterBar/guideline.md +419 -0
- package/dist/doc/VJoyFilterBarButton/guideline.md +359 -0
- package/dist/doc/VJoyFloatingActionsBar/guideline.md +372 -0
- package/dist/doc/VJoyFormError/guideline.md +325 -0
- package/dist/doc/VJoyFormError/metadata.json +0 -14
- package/dist/doc/VJoyFormFieldSkeleton/guideline.md +159 -0
- package/dist/doc/VJoyFormGroup/guideline.md +285 -0
- package/dist/doc/VJoyFunnel/guideline.md +349 -0
- package/dist/doc/VJoyFunnelFooter/guideline.md +178 -0
- package/dist/doc/VJoyFunnelHeader/guideline.md +204 -0
- package/dist/doc/VJoyHighlight/guideline.md +169 -0
- package/dist/doc/VJoyIcon/JoyIcon.types.ts +1 -1
- package/dist/doc/VJoyIcon/guideline.md +168 -0
- package/dist/doc/VJoyIcon/metadata.json +0 -1
- package/dist/doc/VJoyIconButton/guideline.md +169 -0
- package/dist/doc/VJoyIconButton/metadata.json +1 -1
- package/dist/doc/VJoyIndicator/guideline.md +168 -0
- package/dist/doc/VJoyIndicators/guideline.md +183 -0
- package/dist/doc/VJoyInput/JoyInput.types.ts +1 -6
- package/dist/doc/VJoyInput/guideline.md +401 -0
- package/dist/doc/VJoyInput/metadata.json +5 -14
- package/dist/doc/VJoyInputDigit/guideline.md +381 -0
- package/dist/doc/VJoyInputNumber/guideline.md +414 -0
- package/dist/doc/VJoyInputNumber/metadata.json +22 -17
- package/dist/doc/VJoyInputPassword/guideline.md +379 -0
- package/dist/doc/VJoyInputPassword/metadata.json +0 -14
- package/dist/doc/VJoyLabel/guideline.md +200 -0
- package/dist/doc/VJoyLink/guideline.md +360 -0
- package/dist/doc/VJoyListItem/guideline.md +335 -0
- package/dist/doc/VJoyMenu/guideline.md +304 -0
- package/dist/doc/VJoyMenu/metadata.json +21 -21
- package/dist/doc/VJoyMenuItem/guideline.md +352 -0
- package/dist/doc/VJoyPagination/guideline.md +355 -0
- package/dist/doc/VJoyPanel/guideline.md +238 -0
- package/dist/doc/VJoyPanelSection/guideline.md +160 -0
- package/dist/doc/VJoyPasswordRequirement/guideline.md +336 -0
- package/dist/doc/VJoyPasswordRequirement/metadata.json +1 -1
- package/dist/doc/VJoyProductTour/JoyProductTour.types.ts +0 -1
- package/dist/doc/VJoyProductTour/guideline.md +455 -0
- package/dist/doc/VJoyProductTour/metadata.json +1 -1
- package/dist/doc/VJoyProductTourTrigger/guideline.md +167 -0
- package/dist/doc/VJoyProgressBar/guideline.md +348 -0
- package/dist/doc/VJoyRadio/guideline.md +346 -0
- package/dist/doc/VJoyRadioGroup/guideline.md +331 -0
- package/dist/doc/VJoyRatingStars/guideline.md +306 -0
- package/dist/doc/VJoyScreenLoader/guideline.md +172 -0
- package/dist/doc/VJoySelect/guideline.md +396 -0
- package/dist/doc/VJoySelectableItem/guideline.md +351 -0
- package/dist/doc/VJoySelectableItemGroup/guideline.md +370 -0
- package/dist/doc/VJoySeparator/guideline.md +161 -0
- package/dist/doc/VJoySnackbar/guideline.md +402 -0
- package/dist/doc/VJoySpinner/guideline.md +168 -0
- package/dist/doc/VJoyStep/guideline.md +182 -0
- package/dist/doc/VJoyStepper/guideline.md +390 -0
- package/dist/doc/VJoyTab/guideline.md +298 -0
- package/dist/doc/VJoyTabs/guideline.md +353 -0
- package/dist/doc/VJoyTag/guideline.md +226 -0
- package/dist/doc/VJoyTagsInput/guideline.md +387 -0
- package/dist/doc/VJoyTagsList/guideline.md +311 -0
- package/dist/doc/VJoyTagsList/metadata.json +27 -27
- package/dist/doc/VJoyTemplate/guideline.md +366 -0
- package/dist/doc/VJoyText/guideline.md +175 -0
- package/dist/doc/VJoyText/metadata.json +1 -1
- package/dist/doc/VJoyTextarea/guideline.md +378 -0
- package/dist/doc/VJoyTitleBrand/guideline.md +155 -0
- package/dist/doc/VJoyToggle/guideline.md +327 -0
- package/dist/doc/VJoyTooltip/guideline.md +322 -0
- package/dist/doc/VJoyUserCard/guideline.md +268 -0
- package/dist/doc/VJoyUserCard/metadata.json +3 -17
- package/dist/doc/VJoyWalkthrough/guideline.md +351 -0
- package/dist/doc/VJoyWalkthroughTrigger/guideline.md +171 -0
- package/dist/doc/VJoyWrapper/guideline.md +173 -0
- package/dist/doc/css-classes.json +1 -15
- package/dist/doc/design-tokens.json +1 -0
- package/dist/doc/index.json +20 -30
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/tools/get-component-guideline.d.ts +30 -0
- package/dist/tools/get-component-guideline.d.ts.map +1 -0
- package/dist/tools/get-component-guideline.js +51 -0
- package/dist/tools/get-component-guideline.js.map +1 -0
- package/dist/tools/get-documentation-page.d.ts +22 -0
- package/dist/tools/get-documentation-page.d.ts.map +1 -0
- package/dist/tools/get-documentation-page.js +21 -0
- package/dist/tools/get-documentation-page.js.map +1 -0
- package/dist/tools/list-documentation-pages.d.ts +22 -0
- package/dist/tools/list-documentation-pages.d.ts.map +1 -0
- package/dist/tools/list-documentation-pages.js +17 -0
- package/dist/tools/list-documentation-pages.js.map +1 -0
- package/dist/tools/search-documentation.d.ts +41 -0
- package/dist/tools/search-documentation.d.ts.map +1 -0
- package/dist/tools/search-documentation.js +25 -0
- package/dist/tools/search-documentation.js.map +1 -0
- package/dist/zeroheight-parser.d.ts +27 -0
- package/dist/zeroheight-parser.d.ts.map +1 -0
- package/dist/zeroheight-parser.js +77 -0
- package/dist/zeroheight-parser.js.map +1 -0
- package/package.json +10 -10
- package/dist/doc/VJoyMultiCheckbox/metadata.json +0 -332
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyTab
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyTab.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Tab
|
|
10
|
+
|
|
11
|
+
Use **`VJoyTab`** as a **child of `VJoyTabs`** to define one switchable panel — its tab-button label, optional notification badge, optional link behavior, and the panel content shown when that tab is selected.
|
|
12
|
+
|
|
13
|
+
`VJoyTab` does not render or manage the tab list by itself. `VJoyTabs` reads each child’s props and slots, builds the tab buttons (`TabButton`), and provides selection context so only the active panel is visible.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyTab` from `@maltjoy/core-vue` (always inside `VJoyTabs`) |
|
|
18
|
+
| **Design** | [ZeroHeight — Tabs](https://zeroheight.com/6bf479565/) |
|
|
19
|
+
| **Storybook** | [VJoyTabs docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-navigation-vjoytabs--docs) (`VJoyTab` is a subcomponent) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyTab.dsds.json`](./JoyTab.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyTab")` · parent: `joy_get_component_info("VJoyTabs")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use `VJoyTab` for **each peer section** inside a `VJoyTabs` group — for example one tab per settings category, dashboard view, or profile subsection on the same page.
|
|
28
|
+
|
|
29
|
+
Each instance declares the panel’s identity (`tab`), how its button should read (`label` or `header` slot), optional count badge (`notifications`), optional external navigation (`href`), and the panel body (default slot).
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| The tab list + selection orchestration | `VJoyTabs` — `VJoyTab` is only the child panel primitive |
|
|
36
|
+
| A standalone tab button or panel without `VJoyTabs` | Not supported — always compose `VJoyTabs` + `VJoyTab` |
|
|
37
|
+
| Sequential steps with progress | `VJoyStepper` + `VJoyStep` |
|
|
38
|
+
| Collapsible sections with visible headers | `VJoyCollapse` + `VJoyCollapseItem` |
|
|
39
|
+
| Mutually exclusive form options | `VJoyRadioGroup` |
|
|
40
|
+
| Primary app navigation | App shell patterns (`VJoyMenu`, sidebar, header nav) |
|
|
41
|
+
| Custom tab markup / ad-hoc CSS for the same job | Still use `VJoyTabs` + `VJoyTab` + Joy tokens |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
`VJoyTab` renders a **`role="tabpanel"`** container. It injects `VJoyTabs` context to know whether it is selected, hides itself when inactive (`display: none`), and exposes stable `id` / `aria-labelledby` hooks that match the tab button `VJoyTabs` builds from this child’s props.
|
|
48
|
+
|
|
49
|
+
The tab **button** is not part of `VJoyTab`’s template — `VJoyTabs` renders `TabButton` using this child’s `tab`, `label`, `href`, `notifications`, and `header` slot.
|
|
50
|
+
|
|
51
|
+
### Anatomy
|
|
52
|
+
|
|
53
|
+
| Part | Required? | What it is |
|
|
54
|
+
| --- | --- | --- |
|
|
55
|
+
| Tab id (`tab`) | Yes | Unique string or number — selection key and ARIA id suffix |
|
|
56
|
+
| Tab label | Yes (one of) | `label` prop **or** meaningful content in the `header` slot (wired by `VJoyTabs`) |
|
|
57
|
+
| Tab panel | Yes | Default slot — main content for this section |
|
|
58
|
+
| `header` slot | No | Custom tab-button content (icon + text, etc.) rendered in the tab list |
|
|
59
|
+
| Notification badge | No | Pink `VJoyDot` count beside the label when `notifications` is a number (including `0`) |
|
|
60
|
+
| Link tab | No | When `href` is set, the tab control is an `<a>` and does not change local selection on click |
|
|
61
|
+
|
|
62
|
+
### Default values
|
|
63
|
+
|
|
64
|
+
- `tab` is **required** — no default.
|
|
65
|
+
- `label`, `href`, and `notification` default to **unset**.
|
|
66
|
+
- Panel visibility follows parent `selectedTab` — there is no `open` / `selected` prop on `VJoyTab`.
|
|
67
|
+
- Selected panel gets `margin-top: var(--joy-core-spacing-9)`; inactive panels are hidden.
|
|
68
|
+
|
|
69
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
70
|
+
|
|
71
|
+
### Composition
|
|
72
|
+
|
|
73
|
+
```vue
|
|
74
|
+
<VJoyTabs :selected-tab="activeTab" @update:selected-tab="activeTab = $event">
|
|
75
|
+
<VJoyTab tab="overview" label="Overview">…</VJoyTab>
|
|
76
|
+
<VJoyTab tab="details" label="Details">…</VJoyTab>
|
|
77
|
+
</VJoyTabs>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Place every `VJoyTab` in the **`VJoyTabs` default slot** (including via `v-for`).
|
|
81
|
+
- Bind `selectedTab` on the parent and handle `update:selected-tab` for controlled selection.
|
|
82
|
+
- Use the **`header` slot** when `label` alone is not enough (icon + text). Still provide a clear accessible name.
|
|
83
|
+
- Set **`href`** only when activation should navigate away; local panels should omit it.
|
|
84
|
+
|
|
85
|
+
> **API note:** `VJoyTab.vue` declares `notification` (boolean), but `VJoyTabs` reads **`notifications` (number)** for badge counts (see Storybook and tests). Prefer **`notifications`** until props and MCP align. A boolean `notification` prop does not drive the badge today.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## States
|
|
90
|
+
|
|
91
|
+
| State | What the user sees / can do |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| Selected | Panel visible (`joy-tab__selected`); tab button shows active underline and bold label (via parent) |
|
|
94
|
+
| Inactive | Panel hidden (`display: none`, `aria-hidden="true"`); tab button in default style |
|
|
95
|
+
| Hover / focus-visible | On the tab **button** (parent) — subtle background; don’t strip focus styles in product CSS |
|
|
96
|
+
| Link tab (`href`) | Tab control is an anchor; click navigates; local `selectedTab` is not updated on click |
|
|
97
|
+
| Notifications | Pink `VJoyDot` beside the label when `notifications` is a number |
|
|
98
|
+
|
|
99
|
+
There is **no disabled tab API** on `VJoyTab`. Hide unavailable sections, explain in the panel, or choose another pattern.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Best practices
|
|
104
|
+
|
|
105
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
106
|
+
|
|
107
|
+
### Structure / identity
|
|
108
|
+
|
|
109
|
+
- **MUST** use `VJoyTab` only inside **`VJoyTabs`** — never as a standalone panel primitive.
|
|
110
|
+
- **MUST** give every `VJoyTab` a **unique**, stable **`tab`** id across renders.
|
|
111
|
+
- **MUST** ensure the parent’s `selectedTab` matches an existing `tab` id on mount.
|
|
112
|
+
- **SHOULD** put the default / most-used section first in the tab order.
|
|
113
|
+
- **MUST NOT** use `VJoyTab` for unrelated workflows that belong on separate routes.
|
|
114
|
+
|
|
115
|
+
### Props and slots
|
|
116
|
+
|
|
117
|
+
- **MUST** supply a tab name via **`label`** or a meaningful **`header` slot** — icon-only headers still need an accessible name.
|
|
118
|
+
- **MUST** use **`notifications` (number)** for count badges — not the undocumented boolean `notification` prop.
|
|
119
|
+
- **SHOULD** use `href` only when the tab truly navigates away (external URL or another route).
|
|
120
|
+
- **MUST NOT** invent props (`variant`, `size`, `disabled`, `selected`, …) on `VJoyTab`.
|
|
121
|
+
|
|
122
|
+
### Implementation
|
|
123
|
+
|
|
124
|
+
- **MUST** use only documented props from Joy MCP / `VJoyTab.vue` / Storybook.
|
|
125
|
+
- **MUST NOT** override `.joy-tab` display rules or tab-button chrome (`.joy-tab-button`) in product CSS.
|
|
126
|
+
- **SHOULD** use `v-for` on `VJoyTab` when tabs are data-driven; `VJoyTabs` unwraps a single fragment child.
|
|
127
|
+
|
|
128
|
+
### Do / don’t
|
|
129
|
+
|
|
130
|
+
| Do | Don’t |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| One `VJoyTab` per panel inside `VJoyTabs` | Standalone `VJoyTab` without a parent |
|
|
133
|
+
| Unique `tab` ids | Duplicate ids or unstable array indexes as ids when order changes |
|
|
134
|
+
| `label` or `header` for every tab button | Empty tab buttons with no accessible name |
|
|
135
|
+
| `notifications` as a number for counts | Boolean `notification` for badges |
|
|
136
|
+
| `href` for real navigation | `href` on tabs that only swap local panels |
|
|
137
|
+
| Panel content in the default slot | Putting panel content outside `VJoyTab` |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Accessibility
|
|
142
|
+
|
|
143
|
+
Behaviors (what must happen), not slogans.
|
|
144
|
+
|
|
145
|
+
| Behavior | Expectation |
|
|
146
|
+
| --- | --- |
|
|
147
|
+
| Role | Panel root is `role="tabpanel"` |
|
|
148
|
+
| Visibility | Inactive panels use `aria-hidden="true"`; only the selected panel is shown |
|
|
149
|
+
| Association | Panel `id="joy-tab-panel-{tab}"`; `aria-labelledby="joy-tab-{tab}"` points to the tab button id built by `VJoyTabs` |
|
|
150
|
+
| Naming | Tab name from `label` or `header` slot content on the corresponding button |
|
|
151
|
+
| Keyboard | Tab-button activation is handled by `TabButton` (**Enter** on focused button); arrow-key roving is not built into the stock primitive |
|
|
152
|
+
| Focus | Preserve visible `:focus-visible` on tab buttons — don’t remove focus rings in product CSS |
|
|
153
|
+
| Links | `href` tabs are real anchors — destination and context must be clear |
|
|
154
|
+
| Notifications | Count badges supplement the label; the label still names the section |
|
|
155
|
+
|
|
156
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Examples
|
|
161
|
+
|
|
162
|
+
```vue
|
|
163
|
+
<script setup lang="ts">
|
|
164
|
+
import { ref } from 'vue'
|
|
165
|
+
import { VJoyTabs, VJoyTab, VJoyIcon } from '@maltjoy/core-vue'
|
|
166
|
+
|
|
167
|
+
const activeTab = ref('overview')
|
|
168
|
+
|
|
169
|
+
const sections = [
|
|
170
|
+
{ id: 'overview', label: 'Overview', content: 'Overview content' },
|
|
171
|
+
{ id: 'details', label: 'Details', content: 'Details content' },
|
|
172
|
+
]
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<template>
|
|
176
|
+
<!-- Basic panels -->
|
|
177
|
+
<VJoyTabs
|
|
178
|
+
:selected-tab="activeTab"
|
|
179
|
+
@update:selected-tab="activeTab = $event"
|
|
180
|
+
>
|
|
181
|
+
<VJoyTab tab="overview" label="Overview">
|
|
182
|
+
Overview content
|
|
183
|
+
</VJoyTab>
|
|
184
|
+
<VJoyTab tab="details" label="Details">
|
|
185
|
+
Details content
|
|
186
|
+
</VJoyTab>
|
|
187
|
+
<VJoyTab tab="activity" label="Activity" :notifications="3">
|
|
188
|
+
Activity content
|
|
189
|
+
</VJoyTab>
|
|
190
|
+
</VJoyTabs>
|
|
191
|
+
|
|
192
|
+
<!-- Custom header slot -->
|
|
193
|
+
<VJoyTabs selected-tab="alerts">
|
|
194
|
+
<VJoyTab tab="alerts" label="Alerts">
|
|
195
|
+
<template #header>
|
|
196
|
+
Alerts
|
|
197
|
+
<VJoyIcon name="bell" color="info" />
|
|
198
|
+
</template>
|
|
199
|
+
Alert content
|
|
200
|
+
</VJoyTab>
|
|
201
|
+
<VJoyTab tab="archive" label="Archive">
|
|
202
|
+
Archive content
|
|
203
|
+
</VJoyTab>
|
|
204
|
+
</VJoyTabs>
|
|
205
|
+
|
|
206
|
+
<!-- Link-style tab (no local panel content required) -->
|
|
207
|
+
<VJoyTabs selected-tab="local">
|
|
208
|
+
<VJoyTab tab="help" label="Help center" href="https://help.example.com" />
|
|
209
|
+
<VJoyTab tab="local" label="On this page">
|
|
210
|
+
Local panel content
|
|
211
|
+
</VJoyTab>
|
|
212
|
+
</VJoyTabs>
|
|
213
|
+
|
|
214
|
+
<!-- Data-driven tabs -->
|
|
215
|
+
<VJoyTabs :selected-tab="activeTab" @update:selected-tab="activeTab = $event">
|
|
216
|
+
<VJoyTab
|
|
217
|
+
v-for="section in sections"
|
|
218
|
+
:key="section.id"
|
|
219
|
+
:tab="section.id"
|
|
220
|
+
:label="section.label"
|
|
221
|
+
>
|
|
222
|
+
{{ section.content }}
|
|
223
|
+
</VJoyTab>
|
|
224
|
+
</VJoyTabs>
|
|
225
|
+
</template>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyTabs` and `VJoyTab` are already global.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Related
|
|
233
|
+
|
|
234
|
+
| Component | Use when |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| `VJoyTabs` | Required parent — tab list, selection, scroll, and `update:selected-tab` |
|
|
237
|
+
| `VJoyStepper` | Sequential steps with progress, not peer panels |
|
|
238
|
+
| `VJoyCollapse` / `VJoyCollapseItem` | Expand/collapse sections with always-visible headers |
|
|
239
|
+
| `VJoyLink` | Inline navigation inside panel content |
|
|
240
|
+
| `VJoyDot` | Notification count on tab buttons (used internally via `notifications`) |
|
|
241
|
+
| `VJoyIcon` | Icons inside the `header` slot |
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## For agents
|
|
246
|
+
|
|
247
|
+
Maps to `agentDocumentBlocks` in [`JoyTab.dsds.json`](./JoyTab.dsds.json). Humans can skip this section.
|
|
248
|
+
|
|
249
|
+
### Pick the right component
|
|
250
|
+
|
|
251
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
252
|
+
| --- | --- | --- |
|
|
253
|
+
| One panel in a tabbed surface | `VJoyTab` inside `VJoyTabs` | Standalone `VJoyTab` |
|
|
254
|
+
| The full tabbed UI | `VJoyTabs` + `VJoyTab` children | Hand-rolled `role="tab"` markup |
|
|
255
|
+
| Multi-step wizard | `VJoyStepper` | `VJoyTab` for funnel steps |
|
|
256
|
+
| Navigate away on tab click | `VJoyTab` with `href` | Local panel + fake link button |
|
|
257
|
+
| Form mutually exclusive options | `VJoyRadioGroup` | `VJoyTab` inside a single field |
|
|
258
|
+
|
|
259
|
+
### Hard rules
|
|
260
|
+
|
|
261
|
+
1. **MUST** call `joy_get_component_info("VJoyTab")` and `joy_get_component_info("VJoyTabs")` (or read Storybook) before generating props.
|
|
262
|
+
2. **MUST** always emit **`VJoyTab`** inside **`VJoyTabs`** default slot — one child per panel.
|
|
263
|
+
3. **MUST** set **`tab`** (unique id) on every `VJoyTab`; parent binds **`selectedTab`** / **`@update:selected-tab`**.
|
|
264
|
+
4. **MUST** use **`notifications` (number)** for badge counts — not `notification` (boolean).
|
|
265
|
+
5. **MUST NOT** invent `variant`, `size`, `disabled`, `selected`, or other undocumented props.
|
|
266
|
+
6. **MUST NOT** override `.joy-tab` or `.joy-tab-button` display or colors in product CSS.
|
|
267
|
+
7. **SHOULD** use `label` unless the `header` slot is required (icon + text).
|
|
268
|
+
8. **SHOULD** set `href` only when navigation is intentional.
|
|
269
|
+
|
|
270
|
+
### Checklist
|
|
271
|
+
|
|
272
|
+
- [ ] `VJoyTabs` parent present — not standalone `VJoyTab`
|
|
273
|
+
- [ ] Unique `tab` id on every `VJoyTab`
|
|
274
|
+
- [ ] Parent `selectedTab` matches an existing `tab` id; handle `update:selected-tab`
|
|
275
|
+
- [ ] `label` or accessible `header` slot on every tab
|
|
276
|
+
- [ ] Props from MCP / Storybook only — no invented enums
|
|
277
|
+
- [ ] `notifications` is a number when a badge is needed
|
|
278
|
+
- [ ] `href` only when navigation is intentional
|
|
279
|
+
- [ ] No custom tab / panel chrome CSS
|
|
280
|
+
- [ ] Panel content in the default slot
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## DSDS mapping
|
|
285
|
+
|
|
286
|
+
This Markdown mirrors [`JoyTab.dsds.json`](./JoyTab.dsds.json) (DSDS **0.15.2**).
|
|
287
|
+
|
|
288
|
+
| Section here | DSDS block |
|
|
289
|
+
| --- | --- |
|
|
290
|
+
| When to use / not | `use-cases` |
|
|
291
|
+
| Anatomy | `anatomy` |
|
|
292
|
+
| States | `states` |
|
|
293
|
+
| Best practices | `guidelines` |
|
|
294
|
+
| Accessibility | `accessibility` |
|
|
295
|
+
| Examples | `sections` |
|
|
296
|
+
| For agents | `agentDocumentBlocks` |
|
|
297
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
298
|
+
| Parent composition | `relationships` in JSON (with `VJoyTabs`) |
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyTabs
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyTabs.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Tabs
|
|
10
|
+
|
|
11
|
+
Use **`VJoyTabs`** when related content on the same surface should be split into switchable panels — for example profile sections, settings categories, or dashboard views — without leaving the page.
|
|
12
|
+
|
|
13
|
+
It composes **`VJoyTab`** children for each panel, manages the selected tab, supports notification badges, custom tab headers, horizontal scroll when labels overflow, and optional link-style tabs via `href`.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyTabs` + `VJoyTab` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [ZeroHeight — Tabs](https://zeroheight.com/6bf479565/) |
|
|
19
|
+
| **Storybook** | [VJoyTabs docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-navigation-vjoytabs--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyTabs.dsds.json`](./JoyTabs.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyTabs")` · `joy_get_component_info("VJoyTab")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use tabs when users need to move between **peer sections of content** on one screen — each section is substantial enough to deserve its own panel, but not important enough to be a separate route.
|
|
28
|
+
|
|
29
|
+
Tabs work well for organizing articles vs. videos, overview vs. details, or settings groups that share the same page context. Notification badges can highlight unseen items inside a section.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| A linear multi-step flow with progress | `VJoyStepper` |
|
|
36
|
+
| Primary app or site navigation | App shell patterns (`VJoyMenu`, sidebar, header nav) |
|
|
37
|
+
| Mutually exclusive choices inside a form | `VJoyRadioGroup` or `VJoySegmentedControl` (if available) |
|
|
38
|
+
| Inline navigation inside body copy | `VJoyLink` |
|
|
39
|
+
| Filtering a single list without separate panels | Filters, chips, or sort controls |
|
|
40
|
+
| More than ~5–7 peer sections on desktop | Rework IA — split across pages, use a menu, or nest content |
|
|
41
|
+
| Custom tab markup / ad-hoc CSS for the same job | Still use `VJoyTabs` + `VJoyTab` + Joy tokens |
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
|
|
47
|
+
`VJoyTabs` renders a **tab list** from its `VJoyTab` children, shows one panel at a time, and coordinates selection through a provided context. Tab buttons are built internally (`TabButton`); panel content comes from each `VJoyTab` default slot.
|
|
48
|
+
|
|
49
|
+
### Anatomy
|
|
50
|
+
|
|
51
|
+
| Part | Required? | What it is |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Tab list wrapper | Yes | Scrollable row of tab buttons (`role="tablist"`) |
|
|
54
|
+
| Tab button | Yes (per tab) | `<button type="button">` or `<a>` when `href` is set (`role="tab"`) |
|
|
55
|
+
| Tab label | Yes (per tab) | Text from `label` or the `header` slot on `VJoyTab` |
|
|
56
|
+
| Notification badge | No | `VJoyDot` count beside the label when `notifications` is a number |
|
|
57
|
+
| Tab panel | Yes (per tab) | Content area (`role="tabpanel"`) — only the selected panel is visible |
|
|
58
|
+
| Scroll fade | No | Left/right gradient when tab labels overflow horizontally |
|
|
59
|
+
|
|
60
|
+
### Default values
|
|
61
|
+
|
|
62
|
+
- `selectedTab` on `VJoyTabs` has **no default** — set it to the `tab` id of the initially active panel.
|
|
63
|
+
- Each `VJoyTab` **must** have a unique `tab` id (string or number).
|
|
64
|
+
- `label` on `VJoyTab` is optional when the `header` slot supplies the button content.
|
|
65
|
+
- `--tabs-buttons-wrapper-min-width` defaults to **`250px`**; `--tabs-buttons-list-width` defaults to **`auto`**.
|
|
66
|
+
|
|
67
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
68
|
+
|
|
69
|
+
### Composition
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<VJoyTabs :selected-tab="activeTab" @update:selected-tab="activeTab = $event">
|
|
73
|
+
<VJoyTab tab="overview" label="Overview">…</VJoyTab>
|
|
74
|
+
<VJoyTab tab="details" label="Details">…</VJoyTab>
|
|
75
|
+
</VJoyTabs>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **`VJoyTab`** children go in the `VJoyTabs` default slot (including via `v-for`).
|
|
79
|
+
- Bind `selectedTab` and listen for `update:selected-tab` for controlled selection (same pattern as `v-model:selected-tab`).
|
|
80
|
+
- When a `VJoyTab` has `href`, its control renders as a link and **does not** change the local selected tab on click.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Variants
|
|
85
|
+
|
|
86
|
+
`VJoyTabs` has **no variant or emphasis prop**. Visual treatment is fixed: neutral inactive tabs, primary-colored underline and bold label on the selected tab, hover/focus background on tab buttons.
|
|
87
|
+
|
|
88
|
+
Do not simulate “primary / secondary tabs” with custom CSS — use the component as shipped.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Sizes
|
|
93
|
+
|
|
94
|
+
`VJoyTabs` has **no size prop**. Tab button typography uses `--joy-font-size-primary-400` (smaller on viewports ≤ 767px). Control density with layout and the CSS custom properties below — **do not hardcode font sizes or tab heights** in product CSS.
|
|
95
|
+
|
|
96
|
+
| Mechanism | Use when |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| Default layout | Most product surfaces |
|
|
99
|
+
| `--tabs-buttons-list-width` | Force a minimum scroll width on narrow containers (e.g. side panels) |
|
|
100
|
+
| `--tabs-buttons-wrapper-min-width` | Adjust the scroll area floor (default 250px; avoid going far above ~300px per design notes) |
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## States
|
|
105
|
+
|
|
106
|
+
| State | What the user sees / can do |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| Default (inactive tab) | Neutral label; panel hidden (`display: none`) |
|
|
109
|
+
| Selected | Bold primary label, active underline, panel visible |
|
|
110
|
+
| Hover / focus-visible | Subtle background on the tab button |
|
|
111
|
+
| Link tab (`href` set) | Anchor styled like other tabs; navigates on activation |
|
|
112
|
+
| Notifications | Pink `VJoyDot` beside the label when `notifications` is a number (including `0`) |
|
|
113
|
+
| Scroll overflow | Horizontal scroll on the tab row; fade gradients at start/end when more tabs are off-screen |
|
|
114
|
+
|
|
115
|
+
There is **no disabled tab API**. If a section is unavailable, hide the tab, explain in the panel, or choose a different pattern.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Best practices
|
|
120
|
+
|
|
121
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
122
|
+
|
|
123
|
+
### Structure / selection
|
|
124
|
+
|
|
125
|
+
- **MUST** give every `VJoyTab` a **unique** `tab` id stable across renders.
|
|
126
|
+
- **MUST** set `selectedTab` to a valid `tab` id when the component mounts.
|
|
127
|
+
- **SHOULD** keep tab count modest (roughly **2–5** on desktop; up to ~7 only when labels stay scannable).
|
|
128
|
+
- **SHOULD** order tabs by importance or natural reading order — put the default / most-used panel first.
|
|
129
|
+
- **MUST NOT** use tabs for unrelated workflows that belong on separate routes.
|
|
130
|
+
|
|
131
|
+
### Implementation
|
|
132
|
+
|
|
133
|
+
- **MUST** compose `VJoyTabs` with `VJoyTab` children — not free-form buttons + divs.
|
|
134
|
+
- **MUST** use only documented props (Joy MCP / Storybook). No `variant`, `size`, or `disabled` props exist.
|
|
135
|
+
- **MUST** style with Joy tokens and documented CSS variables — no custom `.joy-tab-button` chrome overrides.
|
|
136
|
+
- **MUST NOT** invent props on `VJoyTabs` or `VJoyTab`.
|
|
137
|
+
- **SHOULD** use `v-for` on `VJoyTab` when tabs are data-driven; `VJoyTabs` unwraps a single fragment child.
|
|
138
|
+
|
|
139
|
+
### Link tabs
|
|
140
|
+
|
|
141
|
+
- **SHOULD** use `href` on `VJoyTab` only when the tab truly navigates away (external URL or another route).
|
|
142
|
+
- **MUST NOT** mix link tabs and local panels without a clear UX reason — prefer all local or all navigational.
|
|
143
|
+
|
|
144
|
+
### Do / don’t
|
|
145
|
+
|
|
146
|
+
| Do | Don’t |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| Parallel, short tab labels | Long sentences or inconsistent grammar across tabs |
|
|
149
|
+
| `update:selected-tab` for controlled state | Mutating selection only inside panel content |
|
|
150
|
+
| `header` slot for icons + label together | Icon-only tabs with no accessible name |
|
|
151
|
+
| Scroll-friendly labels on narrow layouts | Ten equal-priority tabs across one row |
|
|
152
|
+
| `notifications` for actionable counts | Decorative dots without meaning |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Content
|
|
157
|
+
|
|
158
|
+
Tab labels are navigation — they name **where** the user is going within the surface, not what they will do there.
|
|
159
|
+
|
|
160
|
+
### Label formula
|
|
161
|
+
|
|
162
|
+
**Short noun phrase** (or tight noun + qualifier), **parallel across tabs**:
|
|
163
|
+
|
|
164
|
+
- ✅ Overview · Details · Activity
|
|
165
|
+
- ✅ Profile · Billing · Security
|
|
166
|
+
- ✅ All · Unread (12)
|
|
167
|
+
|
|
168
|
+
Use the same part of speech and level of detail for every tab in the set.
|
|
169
|
+
|
|
170
|
+
### Writing rules
|
|
171
|
+
|
|
172
|
+
| Strength | Rule |
|
|
173
|
+
| --- | --- |
|
|
174
|
+
| **MUST** | Keep labels scannable — aim for **≤ 20 characters**; never truncate with ellipsis |
|
|
175
|
+
| **MUST** | Use **sentence case** (or product locale rules) consistently across the set |
|
|
176
|
+
| **MUST** | Make each label distinct — no two tabs that differ only by punctuation |
|
|
177
|
+
| **MUST NOT** | Use verbs alone as tab titles — ❌ `View`, `Manage` (save verbs for buttons inside panels) |
|
|
178
|
+
| **MUST NOT** | Repeat the page title in every tab — ❌ `Project settings — General` / `Project settings — Team` → ✅ `General` / `Team` |
|
|
179
|
+
| **SHOULD** | Prefer **2–4 words** maximum |
|
|
180
|
+
| **SHOULD** | Put counts in `notifications` or inline in the `header` slot — ✅ `Messages` + badge `3` |
|
|
181
|
+
| **SHOULD** | Name the content type, not the UI — ✅ `Invoices` · ❌ `Tab 2` |
|
|
182
|
+
| **SHOULD** (FR) | Keep articles when grammar requires them — _Mon profil_, _Facturation_ |
|
|
183
|
+
|
|
184
|
+
### Patterns
|
|
185
|
+
|
|
186
|
+
**Settings / profile** — Group by domain (`Account`, `Notifications`, `Privacy`). Default tab = the section users open most often.
|
|
187
|
+
|
|
188
|
+
**Dashboard sections** — Name the artifact (`Pipeline`, `Performance`, `Team`). Avoid marketing copy in tab labels.
|
|
189
|
+
|
|
190
|
+
**Workflow stages with alerts** — Use `notifications` for items needing attention inside a stage; keep the label itself short (`Reviews`, `Pending`).
|
|
191
|
+
|
|
192
|
+
**Custom headers** — When using the `header` slot (icon + text), still provide a clear accessible name; don’t rely on icon shape alone.
|
|
193
|
+
|
|
194
|
+
**Link tabs** — Label should describe the destination (`Help center`, `Open in admin`), not an in-page section.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Accessibility
|
|
199
|
+
|
|
200
|
+
Behaviors (what must happen), not slogans.
|
|
201
|
+
|
|
202
|
+
| Behavior | Expectation |
|
|
203
|
+
| --- | --- |
|
|
204
|
+
| Roles | Tab list (`role="tablist"`), tabs (`role="tab"`), panels (`role="tabpanel"`) |
|
|
205
|
+
| Naming | Tab name from `label` or `header` slot content |
|
|
206
|
+
| Selection | `aria-selected="true"` on the active tab; inactive panels use `aria-hidden="true"` |
|
|
207
|
+
| Association | Panel `aria-labelledby` points to `id="joy-tab-{tab}"`; panel `id="joy-tab-panel-{tab}"` |
|
|
208
|
+
| Keyboard | **Enter** activates a focused tab button; implement arrow-key roving tabindex in product only if you extend the primitive — the stock component does not add Left/Right handlers |
|
|
209
|
+
| Focus | Preserve visible `:focus-visible` styles — don’t remove tab button focus rings |
|
|
210
|
+
| Links | `href` tabs are real anchors — ensure destination and context are clear |
|
|
211
|
+
| Notifications | Count badges should not be the only indicator of meaning; label still names the section |
|
|
212
|
+
|
|
213
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Examples
|
|
218
|
+
|
|
219
|
+
```vue
|
|
220
|
+
<script setup lang="ts">
|
|
221
|
+
import { ref } from 'vue'
|
|
222
|
+
import { VJoyTabs, VJoyTab, VJoyIcon } from '@maltjoy/core-vue'
|
|
223
|
+
|
|
224
|
+
const activeTab = ref('overview')
|
|
225
|
+
</script>
|
|
226
|
+
|
|
227
|
+
<template>
|
|
228
|
+
<!-- Controlled selection -->
|
|
229
|
+
<VJoyTabs
|
|
230
|
+
:selected-tab="activeTab"
|
|
231
|
+
@update:selected-tab="activeTab = $event"
|
|
232
|
+
>
|
|
233
|
+
<VJoyTab tab="overview" label="Overview">
|
|
234
|
+
Overview content
|
|
235
|
+
</VJoyTab>
|
|
236
|
+
<VJoyTab tab="details" label="Details">
|
|
237
|
+
Details content
|
|
238
|
+
</VJoyTab>
|
|
239
|
+
<VJoyTab tab="activity" label="Activity" :notifications="3">
|
|
240
|
+
Activity content
|
|
241
|
+
</VJoyTab>
|
|
242
|
+
</VJoyTabs>
|
|
243
|
+
|
|
244
|
+
<!-- Custom header slot -->
|
|
245
|
+
<VJoyTabs selected-tab="alerts">
|
|
246
|
+
<VJoyTab tab="alerts" label="Alerts">
|
|
247
|
+
<template #header>
|
|
248
|
+
Alerts
|
|
249
|
+
<VJoyIcon name="bell" color="info" />
|
|
250
|
+
</template>
|
|
251
|
+
Alert content
|
|
252
|
+
</VJoyTab>
|
|
253
|
+
<VJoyTab tab="archive" label="Archive">
|
|
254
|
+
Archive content
|
|
255
|
+
</VJoyTab>
|
|
256
|
+
</VJoyTabs>
|
|
257
|
+
|
|
258
|
+
<!-- Link-style tab -->
|
|
259
|
+
<VJoyTabs selected-tab="local">
|
|
260
|
+
<VJoyTab tab="external" label="Help center" href="https://help.example.com" />
|
|
261
|
+
<VJoyTab tab="local" label="On this page">
|
|
262
|
+
Local panel content
|
|
263
|
+
</VJoyTab>
|
|
264
|
+
</VJoyTabs>
|
|
265
|
+
|
|
266
|
+
<!-- Data-driven tabs -->
|
|
267
|
+
<VJoyTabs :selected-tab="activeTab" @update:selected-tab="activeTab = $event">
|
|
268
|
+
<VJoyTab
|
|
269
|
+
v-for="section in sections"
|
|
270
|
+
:key="section.id"
|
|
271
|
+
:tab="section.id"
|
|
272
|
+
:label="section.label"
|
|
273
|
+
>
|
|
274
|
+
{{ section.content }}
|
|
275
|
+
</VJoyTab>
|
|
276
|
+
</VJoyTabs>
|
|
277
|
+
</template>
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyTabs` and `VJoyTab` are already global.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Related
|
|
285
|
+
|
|
286
|
+
| Component | Use when |
|
|
287
|
+
| --- | --- |
|
|
288
|
+
| `VJoyTab` | Required child — defines each panel, label, badge, and optional `href` / `header` |
|
|
289
|
+
| `VJoyStepper` | Sequential steps with progress, not peer sections |
|
|
290
|
+
| `VJoyMenu` / `VJoyMenuItem` | Many actions or navigation targets in a dropdown |
|
|
291
|
+
| `VJoyLink` | Inline text links inside panel content |
|
|
292
|
+
| `VJoyDot` | Notification count on tab buttons (used internally) |
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## For agents
|
|
297
|
+
|
|
298
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyTabs.dsds.json`](./JoyTabs.dsds.json).
|
|
299
|
+
|
|
300
|
+
### Pick the right component
|
|
301
|
+
|
|
302
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
303
|
+
| --- | --- | --- |
|
|
304
|
+
| Peer content panels on one surface | `VJoyTabs` + `VJoyTab` children | Hand-rolled `role="tab"` markup |
|
|
305
|
+
| Multi-step wizard with progress | `VJoyStepper` | `VJoyTabs` for linear funnel steps |
|
|
306
|
+
| Form field mutually exclusive options | `VJoyRadioGroup` | `VJoyTabs` inside a single field |
|
|
307
|
+
| Navigate away from the page | `VJoyTab` with `href` or `VJoyLink` in content | `VJoyTabs` panel for external-only destinations |
|
|
308
|
+
| Primary site navigation | Shell nav / menu patterns | `VJoyTabs` in the global header |
|
|
309
|
+
|
|
310
|
+
### Hard rules
|
|
311
|
+
|
|
312
|
+
1. **MUST** call `joy_get_component_info("VJoyTabs")` and `joy_get_component_info("VJoyTab")` (or read Storybook) before generating props.
|
|
313
|
+
2. **MUST** always emit **`VJoyTab`** inside **`VJoyTabs`** default slot — one child per panel.
|
|
314
|
+
3. **MUST** set `tab` (unique id) on every `VJoyTab` and bind `selectedTab` / `@update:selected-tab` for controlled usage.
|
|
315
|
+
4. **MUST** use `notifications` (number) on `VJoyTab` for count badges — wired to `VJoyDot` via `VJoyTabs`.
|
|
316
|
+
5. **MUST NOT** invent `variant`, `size`, `disabled`, or other undocumented props.
|
|
317
|
+
6. **MUST NOT** override `.joy-tab-button`, `.joy-tabs-buttons`, or panel display rules with custom CSS.
|
|
318
|
+
7. **SHOULD** default `selectedTab` to the first / primary section’s `tab` id.
|
|
319
|
+
8. **SHOULD** use the `header` slot only when `label` alone is insufficient (icon + text).
|
|
320
|
+
|
|
321
|
+
> **API note:** Joy MCP may list `notification` (boolean) on `VJoyTab`. The implementation path used by Storybook and tests is **`notifications` (number)** on `VJoyTab`. Emit `notifications` until types and MCP align.
|
|
322
|
+
|
|
323
|
+
### Checklist
|
|
324
|
+
|
|
325
|
+
- [ ] `VJoyTabs` + one `VJoyTab` per panel (not standalone `VJoyTab`)
|
|
326
|
+
- [ ] Unique `tab` id on every `VJoyTab`
|
|
327
|
+
- [ ] `selectedTab` matches an existing `tab` id; handle `update:selected-tab`
|
|
328
|
+
- [ ] Props from MCP / Storybook only — no invented enums
|
|
329
|
+
- [ ] No custom tab chrome CSS
|
|
330
|
+
- [ ] Tab labels short, parallel, and meaningful (not `Tab 1`)
|
|
331
|
+
- [ ] `href` only when navigation is intentional
|
|
332
|
+
- [ ] `notifications` is a number when a badge is needed
|
|
333
|
+
- [ ] Custom `header` slot still provides an accessible tab name
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## DSDS mapping
|
|
338
|
+
|
|
339
|
+
This Markdown mirrors [`JoyTabs.dsds.json`](./JoyTabs.dsds.json) (DSDS **0.15.2**).
|
|
340
|
+
|
|
341
|
+
| Section here | DSDS block |
|
|
342
|
+
| --- | --- |
|
|
343
|
+
| When to use / not | `use-cases` |
|
|
344
|
+
| Anatomy | `anatomy` |
|
|
345
|
+
| Variants / sizes | `variants` |
|
|
346
|
+
| States | `states` |
|
|
347
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
348
|
+
| Accessibility | `accessibility` |
|
|
349
|
+
| Examples | `sections` |
|
|
350
|
+
| For agents | `agentDocumentBlocks` |
|
|
351
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
352
|
+
| Imports | `imports` in JSON |
|
|
353
|
+
| Scroll / layout tokens | `design-specifications` in JSON |
|