@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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -3
  3. package/dist/doc/VJoyAdminBanner/guideline.md +284 -0
  4. package/dist/doc/VJoyAvailability/guideline.md +352 -0
  5. package/dist/doc/VJoyAvatar/guideline.md +192 -0
  6. package/dist/doc/VJoyAvatarsList/guideline.md +328 -0
  7. package/dist/doc/VJoyBadge/guideline.md +169 -0
  8. package/dist/doc/VJoyBadgeLevel/guideline.md +166 -0
  9. package/dist/doc/VJoyBlockSkeleton/guideline.md +150 -0
  10. package/dist/doc/VJoyBottomSheet/guideline.md +345 -0
  11. package/dist/doc/VJoyBottomSheetTrigger/guideline.md +156 -0
  12. package/dist/doc/VJoyButton/guideline.md +371 -0
  13. package/dist/doc/VJoyButton/metadata.json +0 -16
  14. package/dist/doc/VJoyButtonCard/guideline.md +384 -0
  15. package/dist/doc/VJoyCheckbox/guideline.md +317 -0
  16. package/dist/doc/VJoyCheckboxCard/guideline.md +381 -0
  17. package/dist/doc/VJoyCheckboxGroup/guideline.md +363 -0
  18. package/dist/doc/VJoyCheckboxGroup/metadata.json +7 -7
  19. package/dist/doc/VJoyCollapse/guideline.md +362 -0
  20. package/dist/doc/VJoyCollapseItem/guideline.md +394 -0
  21. package/dist/doc/VJoyCompanyAvatar/guideline.md +162 -0
  22. package/dist/doc/VJoyDialog/guideline.md +425 -0
  23. package/dist/doc/VJoyDialogTrigger/guideline.md +186 -0
  24. package/dist/doc/VJoyDot/guideline.md +177 -0
  25. package/dist/doc/VJoyDrawer/guideline.md +392 -0
  26. package/dist/doc/VJoyDrawerTrigger/guideline.md +145 -0
  27. package/dist/doc/VJoyDropdown/guideline.md +393 -0
  28. package/dist/doc/VJoyDropdown/metadata.json +6 -6
  29. package/dist/doc/VJoyDropdownList/guideline.md +396 -0
  30. package/dist/doc/VJoyDropdownList/metadata.json +7 -7
  31. package/dist/doc/VJoyDropzone/guideline.md +390 -0
  32. package/dist/doc/VJoyDropzone/metadata.json +36 -1
  33. package/dist/doc/VJoyFilterBar/guideline.md +419 -0
  34. package/dist/doc/VJoyFilterBarButton/guideline.md +359 -0
  35. package/dist/doc/VJoyFloatingActionsBar/guideline.md +372 -0
  36. package/dist/doc/VJoyFormError/guideline.md +325 -0
  37. package/dist/doc/VJoyFormError/metadata.json +0 -14
  38. package/dist/doc/VJoyFormFieldSkeleton/guideline.md +159 -0
  39. package/dist/doc/VJoyFormGroup/guideline.md +285 -0
  40. package/dist/doc/VJoyFunnel/guideline.md +349 -0
  41. package/dist/doc/VJoyFunnelFooter/guideline.md +178 -0
  42. package/dist/doc/VJoyFunnelHeader/guideline.md +204 -0
  43. package/dist/doc/VJoyHighlight/guideline.md +169 -0
  44. package/dist/doc/VJoyIcon/JoyIcon.types.ts +1 -1
  45. package/dist/doc/VJoyIcon/guideline.md +168 -0
  46. package/dist/doc/VJoyIcon/metadata.json +0 -1
  47. package/dist/doc/VJoyIconButton/guideline.md +169 -0
  48. package/dist/doc/VJoyIconButton/metadata.json +1 -1
  49. package/dist/doc/VJoyIndicator/guideline.md +168 -0
  50. package/dist/doc/VJoyIndicators/guideline.md +183 -0
  51. package/dist/doc/VJoyInput/JoyInput.types.ts +1 -6
  52. package/dist/doc/VJoyInput/guideline.md +401 -0
  53. package/dist/doc/VJoyInput/metadata.json +5 -14
  54. package/dist/doc/VJoyInputDigit/guideline.md +381 -0
  55. package/dist/doc/VJoyInputNumber/guideline.md +414 -0
  56. package/dist/doc/VJoyInputNumber/metadata.json +22 -17
  57. package/dist/doc/VJoyInputPassword/guideline.md +379 -0
  58. package/dist/doc/VJoyInputPassword/metadata.json +0 -14
  59. package/dist/doc/VJoyLabel/guideline.md +200 -0
  60. package/dist/doc/VJoyLink/guideline.md +360 -0
  61. package/dist/doc/VJoyListItem/guideline.md +335 -0
  62. package/dist/doc/VJoyMenu/guideline.md +304 -0
  63. package/dist/doc/VJoyMenu/metadata.json +21 -21
  64. package/dist/doc/VJoyMenuItem/guideline.md +352 -0
  65. package/dist/doc/VJoyPagination/guideline.md +355 -0
  66. package/dist/doc/VJoyPanel/guideline.md +238 -0
  67. package/dist/doc/VJoyPanelSection/guideline.md +160 -0
  68. package/dist/doc/VJoyPasswordRequirement/guideline.md +336 -0
  69. package/dist/doc/VJoyPasswordRequirement/metadata.json +1 -1
  70. package/dist/doc/VJoyProductTour/JoyProductTour.types.ts +0 -1
  71. package/dist/doc/VJoyProductTour/guideline.md +455 -0
  72. package/dist/doc/VJoyProductTour/metadata.json +1 -1
  73. package/dist/doc/VJoyProductTourTrigger/guideline.md +167 -0
  74. package/dist/doc/VJoyProgressBar/guideline.md +348 -0
  75. package/dist/doc/VJoyRadio/guideline.md +346 -0
  76. package/dist/doc/VJoyRadioGroup/guideline.md +331 -0
  77. package/dist/doc/VJoyRatingStars/guideline.md +306 -0
  78. package/dist/doc/VJoyScreenLoader/guideline.md +172 -0
  79. package/dist/doc/VJoySelect/guideline.md +396 -0
  80. package/dist/doc/VJoySelectableItem/guideline.md +351 -0
  81. package/dist/doc/VJoySelectableItemGroup/guideline.md +370 -0
  82. package/dist/doc/VJoySeparator/guideline.md +161 -0
  83. package/dist/doc/VJoySnackbar/guideline.md +402 -0
  84. package/dist/doc/VJoySpinner/guideline.md +168 -0
  85. package/dist/doc/VJoyStep/guideline.md +182 -0
  86. package/dist/doc/VJoyStepper/guideline.md +390 -0
  87. package/dist/doc/VJoyTab/guideline.md +298 -0
  88. package/dist/doc/VJoyTabs/guideline.md +353 -0
  89. package/dist/doc/VJoyTag/guideline.md +226 -0
  90. package/dist/doc/VJoyTagsInput/guideline.md +387 -0
  91. package/dist/doc/VJoyTagsList/guideline.md +311 -0
  92. package/dist/doc/VJoyTagsList/metadata.json +27 -27
  93. package/dist/doc/VJoyTemplate/guideline.md +366 -0
  94. package/dist/doc/VJoyText/guideline.md +175 -0
  95. package/dist/doc/VJoyText/metadata.json +1 -1
  96. package/dist/doc/VJoyTextarea/guideline.md +378 -0
  97. package/dist/doc/VJoyTitleBrand/guideline.md +155 -0
  98. package/dist/doc/VJoyToggle/guideline.md +327 -0
  99. package/dist/doc/VJoyTooltip/guideline.md +322 -0
  100. package/dist/doc/VJoyUserCard/guideline.md +268 -0
  101. package/dist/doc/VJoyUserCard/metadata.json +3 -17
  102. package/dist/doc/VJoyWalkthrough/guideline.md +351 -0
  103. package/dist/doc/VJoyWalkthroughTrigger/guideline.md +171 -0
  104. package/dist/doc/VJoyWrapper/guideline.md +173 -0
  105. package/dist/doc/css-classes.json +1 -15
  106. package/dist/doc/design-tokens.json +1 -0
  107. package/dist/doc/index.json +20 -30
  108. package/dist/index.js +22 -2
  109. package/dist/index.js.map +1 -1
  110. package/dist/tools/get-component-guideline.d.ts +30 -0
  111. package/dist/tools/get-component-guideline.d.ts.map +1 -0
  112. package/dist/tools/get-component-guideline.js +51 -0
  113. package/dist/tools/get-component-guideline.js.map +1 -0
  114. package/dist/tools/get-documentation-page.d.ts +22 -0
  115. package/dist/tools/get-documentation-page.d.ts.map +1 -0
  116. package/dist/tools/get-documentation-page.js +21 -0
  117. package/dist/tools/get-documentation-page.js.map +1 -0
  118. package/dist/tools/list-documentation-pages.d.ts +22 -0
  119. package/dist/tools/list-documentation-pages.d.ts.map +1 -0
  120. package/dist/tools/list-documentation-pages.js +17 -0
  121. package/dist/tools/list-documentation-pages.js.map +1 -0
  122. package/dist/tools/search-documentation.d.ts +41 -0
  123. package/dist/tools/search-documentation.d.ts.map +1 -0
  124. package/dist/tools/search-documentation.js +25 -0
  125. package/dist/tools/search-documentation.js.map +1 -0
  126. package/dist/zeroheight-parser.d.ts +27 -0
  127. package/dist/zeroheight-parser.d.ts.map +1 -0
  128. package/dist/zeroheight-parser.js +77 -0
  129. package/dist/zeroheight-parser.js.map +1 -0
  130. package/package.json +10 -10
  131. 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 |