@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,226 @@
1
+ ---
2
+ component: VJoyTag
3
+ status: draft
4
+ language: en
5
+ dsds: JoyTag.dsds.json
6
+ dsdsVersion: "0.15.2"
7
+ ---
8
+
9
+ # Tag
10
+
11
+ Use **`VJoyTag`** when you need a compact label — status, category, filter chip, or removable token in a list.
12
+
13
+ It supports semantic color variants, four sizes, optional remove and drag-handle icons, and selectable or link styling on `primary` / `secondary`. Pass a `value` when tags are managed by **`VJoyTagsList`** or when remove/selection handlers need a stable key.
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | **Code** | `VJoyTag` from `@maltjoy/core-vue` |
18
+ | **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
19
+ | **Storybook** | [VJoyTag docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-view-vjoytag--docs) |
20
+ | **DSDS (machine)** | [`JoyTag.dsds.json`](./JoyTag.dsds.json) |
21
+ | **Live API** | `joy_get_component_info("VJoyTag")` |
22
+
23
+ ---
24
+
25
+ ## When to use this component
26
+
27
+ Use a tag to surface **short, scannable metadata** — skill names, filter chips, workflow status (`pending`, `inactive`), pricing hints, or AI-related labels. Keep copy to a few words; pair with an icon in the default slot when the meaning benefits from a pictogram.
28
+
29
+ Use **`removable`** when the user can dismiss a tag (for example tokens already added to a profile). Use **`selectable`** + **`selected`** on **`primary`** or **`secondary`** for toggle-style filter chips — listen to `@selected` and update selection in the parent. For several tags with shared list behavior, prefer **`VJoyTagsList`** over hand-rolling spacing and events.
30
+
31
+ ### When not to use this component
32
+
33
+ | Need | Use instead |
34
+ | --- | --- |
35
+ | Typing and committing multiple values in one field | `VJoyTagsInput` |
36
+ | Laying out many tags with list selection / removal wiring | `VJoyTagsList` wrapping `VJoyTag` |
37
+ | Numeric notification count on an icon or tab | `VJoyBadge` or `VJoyDot` |
38
+ | Achievement / level recognition (SuperMalter, verified, …) | `VJoyBadgeLevel` |
39
+ | Real navigation via URL | `@click` + your router — `link` is **style only** |
40
+ | Custom chip markup / ad-hoc CSS | Still use `VJoyTag` + Joy tokens |
41
+
42
+ > **Deprecated:** `strikethrough` — avoid in new work; the prop remains for legacy screens.
43
+
44
+ > **Not in the Vue API:** legacy Stencil `joy-tag` props `href`, `target`, `rel`, and `clickable`. **`VJoyTag` has no `href`.** Use `link` for appearance and handle navigation in `@click`.
45
+
46
+ > **`draggable`** only shows a drag-handle icon and `cursor: grab` — it does **not** implement drag-and-drop. Wire your own DnD library if reordering is required.
47
+
48
+ ---
49
+
50
+ ## How it works
51
+
52
+ ### Anatomy
53
+
54
+ | Part | Required? | What it is |
55
+ | --- | --- | --- |
56
+ | Root `<span>` | Yes | `.joy-tag` — inline-flex chip; auto-generated `id` unless `id` is set |
57
+ | Label | Yes* | `label` prop or default slot content (*slot replaces the built-in label) |
58
+ | Drag handle | No | Leading `VJoyIcon` (`drag`) when `draggable` — visual only |
59
+ | Remove control | No | Trailing `VJoyIcon` (`cross`, `role="button"`) when `removable`; emits `remove` with `{ value }` |
60
+ | Focus ring | Yes* | Token-based outline on `:focus` and when `selected` — suppressed when `disabled` |
61
+
62
+ ### Variants and sizes (no separate sections — pick by meaning)
63
+
64
+ **Variants** (`TJoyTagVariants`):
65
+
66
+ | Group | Values | Typical use |
67
+ | --- | --- | --- |
68
+ | Interactive filters | `primary`, `secondary` | Selectable chips; `selected` / hover / active styles apply |
69
+ | Status / emphasis | `important`, `pending`, `inactive`, `pricing`, `special` | Read-only semantic labels |
70
+ | AI surfaces | `ai-primary`, `ai-secondary` | AI-feature context only |
71
+ | Placeholder / add | `empty` | Dashed-border “add” style; supports selectable **styling** but `@selected` fires only for `primary` / `secondary` |
72
+
73
+ **Sizes** (`TJoyTagSizes`): `xsmall`, `small`, `medium` (default), `large`, `xxlarge`. Tokenized heights and typography are defined for `xsmall`–`large` in component styles; prefer those in product UI unless design specifies otherwise.
74
+
75
+ ### Default values
76
+
77
+ - `size` defaults to **`medium`**
78
+ - `variant` defaults to **`primary`**
79
+ - `draggable`, `link`, `removable`, `strikethrough`, `disabled` default to **`false`**
80
+ - `value` defaults to **`undefined`** — set it when handlers or `VJoyTagsList` need a stable key
81
+
82
+ Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
83
+
84
+ ### Events and interaction
85
+
86
+ | Event | When |
87
+ | --- | --- |
88
+ | `click` | Tag surface clicked and not `disabled` |
89
+ | `selected` | `selectable` is true **and** `variant` is `primary` or `secondary` — payload `{ selected, value }` toggles from current `selected` prop |
90
+ | `remove` | Remove icon clicked — payload `{ value }` |
91
+
92
+ `link` and `selectable` set `role="button"` and `tabindex="0"` (unless `disabled`). **`link` does not navigate** — add `@click` and call your router (see Storybook `Link` story).
93
+
94
+ ---
95
+
96
+ ## Best practices
97
+
98
+ Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
99
+
100
+ ### Variant and copy
101
+
102
+ - **SHOULD** pick `variant` by **semantic meaning** (status, pricing, AI), not decoration alone.
103
+ - **SHOULD** keep tag text **short** (roughly 1–3 words); move longer detail to tooltips or body copy.
104
+ - **MUST** use `ai-primary` / `ai-secondary` only on AI-related surfaces.
105
+ - **MUST NOT** use `selectable` on static variants expecting `@selected` — only `primary` and `secondary` emit it.
106
+
107
+ ### Lists and removal
108
+
109
+ - **SHOULD** pass a stable **`value`** on every tag in a managed list (alongside `label`).
110
+ - **SHOULD** use **`VJoyTagsList`** when multiple tags share selection or removal logic.
111
+ - **MUST** handle `@remove` in the parent — the tag does not remove itself from an array.
112
+ - **MUST NOT** rely on **`draggable`** for reordering without your own DnD implementation.
113
+
114
+ ### Implementation
115
+
116
+ - **MUST** use only documented props and enums from MCP or `JoyTag.types.ts`.
117
+ - **MUST** use Joy tokens — no custom `.joy-tag` background, border, or radius overrides in apps.
118
+ - **MUST NOT** invent props (`href`, `color`, `clickable`, …).
119
+ - **SHOULD** use the **default slot** when content includes icons or markup; use **`label`** for plain text only.
120
+
121
+ ### Do / don’t
122
+
123
+ | Do | Don’t |
124
+ | --- | --- |
125
+ | `variant="pending"` for in-progress status | Random variant colors for decoration |
126
+ | `removable` + `@remove` + `value` in lists | Expect the chip to update the parent array by itself |
127
+ | `link` + `@click` + `router.push` for navigation | `href` on `VJoyTag` (not in API) |
128
+ | `VJoyTagsInput` for free-form entry | One `VJoyTag` per keystroke field |
129
+ | `VJoyWrapper` / `VJoyTagsList` for spacing | Negative margins between chips |
130
+
131
+ ---
132
+
133
+ ## Accessibility
134
+
135
+ Behaviors (what must happen), not slogans.
136
+
137
+ | Behavior | Expectation |
138
+ | --- | --- |
139
+ | Role | `role="button"` when `link` or `selectable`; otherwise plain text in a `<span>` |
140
+ | Keyboard | `tabindex="0"` on interactive tags (`link`, or `selectable` when not `disabled`); **Enter** on the remove icon triggers removal |
141
+ | Focus | Visible outline via Joy form focus tokens — do not remove in product CSS |
142
+ | Name | Expose meaningful text via `label` or slot content; icon-only tags need visible or adjacent text |
143
+ | Disabled | `disabled` blocks click/selection, sets `cursor: not-allowed`, and clears focus outline |
144
+ | Remove control | Separate `role="button"` on the cross icon; use `@click.stop` so it does not toggle selection |
145
+
146
+ Target: **WCAG 2.2 AA** where the product requires it.
147
+
148
+ ---
149
+
150
+ ## Examples
151
+
152
+ ```vue
153
+ <script setup lang="ts">
154
+ import { ref } from 'vue'
155
+ import { useRouter } from 'vue-router'
156
+ import { VJoyTag, VJoyIcon, VJoyWrapper } from '@maltjoy/core-vue'
157
+
158
+ const router = useRouter()
159
+ const filters = ref<string[]>(['remote'])
160
+
161
+ function toggleFilter(value: string, selected: boolean) {
162
+ if (selected) {
163
+ filters.value = [...filters.value, value]
164
+ } else {
165
+ filters.value = filters.value.filter((v) => v !== value)
166
+ }
167
+ }
168
+
169
+ function removeSkill(value: string) {
170
+ // update parent collection
171
+ }
172
+ </script>
173
+
174
+ <template>
175
+ <!-- Status label -->
176
+ <VJoyTag variant="pending" label="Pending review" />
177
+
178
+ <!-- Icon + text via slot -->
179
+ <VJoyTag variant="special">
180
+ <VJoyIcon name="heart-fill" />
181
+ Featured
182
+ </VJoyTag>
183
+
184
+ <!-- Removable token -->
185
+ <VJoyTag
186
+ removable
187
+ label="Vue.js"
188
+ value="vue"
189
+ @remove="({ value }) => removeSkill(String(value))"
190
+ />
191
+
192
+ <!-- Selectable filter chip -->
193
+ <VJoyTag
194
+ selectable
195
+ :selected="filters.includes('remote')"
196
+ value="remote"
197
+ label="Remote"
198
+ @selected="({ selected, value }) => toggleFilter(String(value), selected)"
199
+ />
200
+
201
+ <!-- Link styling + manual navigation -->
202
+ <VJoyTag link label="View profile" @click="router.push('/profile')" />
203
+
204
+ <!-- Row of tags -->
205
+ <VJoyWrapper style="flex-wrap: wrap">
206
+ <VJoyTag variant="secondary" label="Design" />
207
+ <VJoyTag variant="secondary" label="Frontend" />
208
+ </VJoyWrapper>
209
+ </template>
210
+ ```
211
+
212
+ In Nuxt (or apps with auto-import), omit the import if `VJoyTag` is already global.
213
+
214
+ ---
215
+
216
+ ## Related
217
+
218
+ | Component | Use when |
219
+ | --- | --- |
220
+ | `VJoyTagsList` | Multiple tags with shared variant, selection, or removal |
221
+ | `VJoyTagsInput` | User types and commits many values into tag chips |
222
+ | `VJoyWrapper` | Flex layout and gap between standalone tags |
223
+ | `VJoyBadge` | Small status pill or numeric counter |
224
+ | `VJoyBadgeLevel` | User achievement / level badges |
225
+ | `VJoyRadio` | Short metadata in `radio-header-right` (display-only) |
226
+ | `VJoyIcon` | Leading icons inside the default slot |
@@ -0,0 +1,387 @@
1
+ ---
2
+ component: VJoyTagsInput
3
+ status: draft
4
+ language: en
5
+ dsds: JoyTagsInput.dsds.json
6
+ dsdsVersion: "0.15.2"
7
+ ---
8
+
9
+ # Tags input
10
+
11
+ Use **`VJoyTagsInput`** when the user must enter **multiple free-form tokens** — skills, keywords, invitee emails, or other values that appear as removable tags inside one field.
12
+
13
+ It binds to `v-model` as a `string[]`, commits tags on **Enter**, **comma**, or **blur** (and **space** when `validation="email"`), and can detect duplicates and invalid emails with built-in error messages.
14
+
15
+ | | |
16
+ | --- | --- |
17
+ | **Code** | `VJoyTagsInput` from `@maltjoy/core-vue` |
18
+ | **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
19
+ | **Storybook** | [VJoyTagsInput docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-forms-vjoytagsinput--docs) |
20
+ | **DSDS (machine)** | [`JoyTagsInput.dsds.json`](./JoyTagsInput.dsds.json) |
21
+ | **Live API** | `joy_get_component_info("VJoyTagsInput")` |
22
+
23
+ ---
24
+
25
+ ## When to use this component
26
+
27
+ Use a tags input when someone adds **several values of the same kind** in one step — for example listing skills on a profile, tagging a project, or collecting multiple email addresses for invitations.
28
+
29
+ Each committed value becomes a **`VJoyTag`** chip with a remove control. The inline text field stays available for the next entry. Wrap the field with **`VJoyLabel`** (or the deprecated default slot on legacy screens) and pair validation errors with clear copy via `duplicationError` / `invalidEmailError`.
30
+
31
+ ### When not to use this component
32
+
33
+ | Need | Use instead |
34
+ | --- | --- |
35
+ | A single line of text (one value) | `VJoyInput` |
36
+ | Multi-line paragraph | `VJoyTextarea` |
37
+ | Picking from a fixed list (single or multi) | `VJoySelect`, `VJoyCheckbox`, or `VJoySelectableItemGroup` |
38
+ | Read-only display of existing tags | `VJoyTag` / `VJoyTagsList` — not an input |
39
+ | Search with one query string | `VJoyInput` with `icon="search"` and optional `clearable` |
40
+ | Custom token field with ad-hoc chip CSS | Still use `VJoyTagsInput` + Joy tokens |
41
+
42
+ > **Deprecated:** do not use the default slot for labels on new work — prefer **`VJoyLabel`** with matching `name` / `for`. Storybook’s default-slot label example is legacy.
43
+
44
+ ---
45
+
46
+ ## How it works
47
+
48
+ ### Anatomy
49
+
50
+ | Part | Required? | What it is |
51
+ | --- | --- | --- |
52
+ | Wrapper | Yes | Root `.joy-tags-input__wrapper` |
53
+ | Label | No | `VJoyLabel` (recommended) or deprecated default slot |
54
+ | Inner container | Yes | Bordered flex region (`.joy-tags-input__inner`) holding tags + input |
55
+ | Tag chips | No (0+) | One `VJoyTag` per committed value (`removable`, `label` = tag text) |
56
+ | Native `<input>` | Yes | Free-text entry; `data-joy-form-field`; `name` / `id` from `name` prop |
57
+ | Error block | No | `VJoyFormError` with duplicate and/or email messages when validation fails |
58
+ | Focus / invalid chrome | Yes | `--focused` / `--invalid` classes on the inner container — do not override in apps |
59
+
60
+ ### Default values
61
+
62
+ - `placeholder` defaults to **`"Add your items here"`**
63
+ - `variant` defaults to **`primary`** (passed to each `VJoyTag`)
64
+ - `size` defaults to **`small`** (passed to each `VJoyTag`)
65
+ - `modelValue` defaults to **`[]`**
66
+ - `invalid` defaults to **`false`**
67
+ - `validation` defaults to **`none`**
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
+ ### Committing and removing tags
72
+
73
+ - **Add:** type text, then press **Enter** or **,** — or **blur** the field (pending text is committed on blur). With `validation="email"`, **Space** also commits the current token.
74
+ - **Remove:** click a tag’s remove control, or press **Backspace** when the text input is empty (removes the last tag).
75
+ - **Model:** each successful add/remove emits `update:modelValue` with the full `string[]`. Initialize with `v-model` bound to an array (not `undefined`).
76
+
77
+ ### Built-in validation
78
+
79
+ | Mode | Behavior |
80
+ | --- | --- |
81
+ | `validation="none"` | Duplicates blocked when `duplicationError` is set; no email format check |
82
+ | `validation="email"` | Email-like format required (see `checkEmailsValidity`); duplicates still detected |
83
+
84
+ On failure the inner border turns invalid, `validation:error` fires (`DUPLICATED_ENTRY` or `INVALID_EMAIL`), and the matching error string renders when provided. Trimming is applied when comparing duplicates.
85
+
86
+ ---
87
+
88
+ ## Variants
89
+
90
+ `variant` controls the **chip style** inside the field (same enum as `VJoyTag`). Pick by semantic meaning of the tokens, not decoration.
91
+
92
+ ### `primary` (default)
93
+
94
+ Use for standard form tags — skills, topics, generic keywords.
95
+
96
+ ### `secondary`
97
+
98
+ Use when tags should read as lower emphasis than surrounding primary chips (rare inside one input — prefer one variant per field).
99
+
100
+ ### Status / pricing variants (`important`, `inactive`, `pending`, `pricing`, `special`)
101
+
102
+ Use only when token meaning matches the tag semantics (e.g. status labels). Avoid mixing unrelated semantics in one tags field.
103
+
104
+ ### `empty`
105
+
106
+ Minimal chip chrome. Use when tags should feel lightweight inside dense UI.
107
+
108
+ ### AI variants (`ai-primary`, `ai-secondary`)
109
+
110
+ Use only when the field is part of an AI feature surface.
111
+
112
+ ### Not in the API: per-tag variants
113
+
114
+ `VJoyTagsInput` applies one `variant` to every tag. For heterogeneous tag styles, use standalone `VJoyTag` lists — not this component.
115
+
116
+ ---
117
+
118
+ ## Sizes
119
+
120
+ `size` is passed to every `VJoyTag` in the field. Heights and chip density come from Joy tag tokens — **do not hardcode chip or input heights** in apps.
121
+
122
+ | Size | Use when |
123
+ | --- | --- |
124
+ | `small` | Default — compact forms and multi-tag rows |
125
+ | `medium` | Slightly larger chips when the field is a focal form control |
126
+ | `large` / `xxlarge` | Prominent tagging UI (use sparingly) |
127
+ | `xsmall` | Very dense layouts (confirm with design — default is `small`) |
128
+
129
+ The inline `<input>` uses `min-height: var(--joy-core-spacing-6)` and grows within the wrapping flex row.
130
+
131
+ ---
132
+
133
+ ## States
134
+
135
+ | State | What the user sees / can do |
136
+ | --- | --- |
137
+ | Default | Empty or showing tags; neutral border; text input ready |
138
+ | Focused | Inner border uses `--joy-color-state-focus`; user can type or remove via keyboard |
139
+ | Hover | Border uses form hover tokens (pointer devices) |
140
+ | Valid | Tags listed; new tokens commit on Enter / comma / blur (and Space for email mode) |
141
+ | Duplicate error | Invalid border; `duplicationError` message when duplicate detected and prop is set |
142
+ | Invalid email | Invalid border; `invalidEmailError` when `validation="email"` and format fails |
143
+ | Removing tags | Remove icon on each tag; Backspace on empty input drops last tag |
144
+
145
+ There is no `disabled` or `readonly` prop on `VJoyTagsInput`. If the collection must not be edited, show `VJoyTagsList` / `VJoyTag` instead.
146
+
147
+ ---
148
+
149
+ ## Best practices
150
+
151
+ Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
152
+
153
+ ### Labels and forms
154
+
155
+ - **MUST** provide a visible label via `VJoyLabel` (with `for` matching `name`) — not placeholder-only.
156
+ - **MUST** set `name` so the label, input `id`, and form submission stay aligned.
157
+ - **MUST** bind `v-model` to a `string[]` (initialize to `[]` when empty).
158
+ - **SHOULD** wrap related fields in `VJoyWrapper` for consistent spacing.
159
+
160
+ ### Implementation
161
+
162
+ - **MUST** use only documented prop / enum values from MCP or `JoyTagsInput.types.ts` / `JoyTag.types.ts`.
163
+ - **MUST** style with Joy tokens — no custom `.joy-tags-input` border, padding, or chip overrides.
164
+ - **MUST NOT** invent props (`maxTags`, `separator`, `disabled`, `clearable`, …).
165
+ - **MUST** pass `duplicationError` when duplicate detection should surface copy to the user.
166
+ - **MUST** pass `invalidEmailError` when using `validation="email"`.
167
+ - **SHOULD** listen to `validation:error` if the parent form needs to block submit or scroll to the field.
168
+
169
+ ### Validation and errors
170
+
171
+ - **MUST** supply explicit error strings — the component does not render a generic invalid state without `duplicationError` / `invalidEmailError`.
172
+ - **MUST NOT** rely on border color alone; error text lives in `VJoyFormError`.
173
+ - **SHOULD** use `validation="email"` only for email collections (invites, notifications), not arbitrary keywords.
174
+
175
+ ### Interaction
176
+
177
+ - **SHOULD** use placeholder text that hints at the commit gesture (e.g. “Type an email and press Enter”).
178
+ - **SHOULD** keep tag values short; long tokens wrap inside the flex container (see Storybook “Multi line”).
179
+ - **MUST NOT** expect Space to commit tags unless `validation="email"`.
180
+
181
+ ### Do / don’t
182
+
183
+ | Do | Don’t |
184
+ | --- | --- |
185
+ | `VJoyLabel` + `name` / `for` | Placeholder as the only label |
186
+ | `v-model` on `string[]` | `v-model` on a single string |
187
+ | `validation="email"` + `invalidEmailError` for invite lists | Email mode without error copy |
188
+ | `duplicationError` when duplicates matter | Silent duplicate rejection |
189
+ | One `variant` per field | Mixed chip semantics without design sign-off |
190
+ | Joy form-field / tag tokens | Hardcoded chip heights or borders |
191
+
192
+ ---
193
+
194
+ ## Content
195
+
196
+ Labels, placeholders, and error messages must tell users **what** to enter and **how** tokens are added.
197
+
198
+ ### Label formula
199
+
200
+ **Noun phrase** for the collection (not the commit action):
201
+
202
+ - ✅ Skills
203
+ - ✅ Invitees
204
+ - ✅ Project tags
205
+
206
+ Use sentence case. Prefer plural when multiple items are expected.
207
+
208
+ ### Placeholder formula
209
+
210
+ **Hint at entry + commit** — not a repeat of the label:
211
+
212
+ - ✅ `Add a skill and press Enter` (under label “Skills”)
213
+ - ✅ `email@company.com` (under label “Invitees” with email validation)
214
+
215
+ - ❌ `Skills` as placeholder when the label already says “Skills”
216
+
217
+ Default placeholder is `Add your items here` — override when context is clearer.
218
+
219
+ ### Error messages
220
+
221
+ | Prop | When | Example |
222
+ | --- | --- | --- |
223
+ | `duplicationError` | Duplicate token (trimmed match) | `This skill is already listed` |
224
+ | `invalidEmailError` | `validation="email"` and format fails | `Enter a valid email address` |
225
+
226
+ State what went wrong and how to fix it — ✅ `This email is already added` · ❌ `Error`.
227
+
228
+ ### Writing rules
229
+
230
+ | Strength | Rule |
231
+ | --- | --- |
232
+ | **MUST** | Every field has a visible label |
233
+ | **MUST** | Error messages are specific and actionable |
234
+ | **MUST NOT** | Use labels that only make sense after failure |
235
+ | **SHOULD** | Keep individual tag text short (display truncates/wraps in narrow containers) |
236
+ | **SHOULD** | Use email validation copy that matches product terminology (“Invitee”, “Recipient”) |
237
+ | **SHOULD** (FR) | Follow French grammar for labels and error strings when the UI is localized |
238
+
239
+ ### Patterns
240
+
241
+ **Profile skills** — `validation="none"`, `duplicationError` set, placeholder mentions Enter or comma.
242
+
243
+ **Email invites** — `validation="email"`, `invalidEmailError` + `duplicationError`, placeholder shows example address.
244
+
245
+ **Narrow layouts** — Tags wrap to multiple lines; keep labels concise so the field doesn’t feel crowded.
246
+
247
+ ---
248
+
249
+ ## Accessibility
250
+
251
+ Behaviors (what must happen), not slogans.
252
+
253
+ | Behavior | Expectation |
254
+ | --- | --- |
255
+ | Role | Native text `<input>` for entry; tags are `VJoyTag` with removable button controls |
256
+ | Name | From associated `VJoyLabel` / `<label for="…">`; `name` sets input `id` |
257
+ | Value | Controlled list via `v-model` (`string[]`); input holds only the pending token |
258
+ | Keyboard | Enter / comma commit; Backspace on empty input removes last tag; Space commits only in email mode |
259
+ | Focus | Visible focus border on inner container; removing a tag refocuses the input |
260
+ | Invalid | Error border on inner container; messages in `VJoyFormError` below the field |
261
+ | Remove | Each tag exposes a remove control (`VJoyTag` removable) — must be keyboard-activatable via Joy tag implementation |
262
+ | Pending input | Standard text editing in the inline input; Tab moves focus out (blur may commit) |
263
+
264
+ Target: **WCAG 2.2 AA** where the product requires it.
265
+
266
+ ---
267
+
268
+ ## Examples
269
+
270
+ ```vue
271
+ <script setup lang="ts">
272
+ import { ref } from 'vue'
273
+ import { VJoyTagsInput, VJoyLabel, VJoyWrapper } from '@maltjoy/core-vue'
274
+
275
+ const skills = ref<string[]>(['Vue', 'TypeScript'])
276
+ const invitees = ref<string[]>([])
277
+ </script>
278
+
279
+ <template>
280
+ <!-- Skills with duplicate detection -->
281
+ <VJoyWrapper direction="column" row-gap="4">
282
+ <VJoyLabel for="skills">Skills</VJoyLabel>
283
+ <VJoyTagsInput
284
+ v-model="skills"
285
+ name="skills"
286
+ placeholder="Add a skill and press Enter"
287
+ duplication-error="This skill is already listed"
288
+ />
289
+ </VJoyWrapper>
290
+
291
+ <!-- Email invites -->
292
+ <VJoyLabel for="invitees">Invitees</VJoyLabel>
293
+ <VJoyTagsInput
294
+ v-model="invitees"
295
+ name="invitees"
296
+ validation="email"
297
+ invalid-email-error="Enter a valid email address"
298
+ duplication-error="This email is already added"
299
+ placeholder="name@company.com"
300
+ @validation:error="(type) => console.warn(type)"
301
+ />
302
+
303
+ <!-- Custom tag slot -->
304
+ <VJoyTagsInput v-model="skills" name="skills-custom">
305
+ <template #tag="{ name }">
306
+ <strong>{{ name }}</strong>
307
+ </template>
308
+ </VJoyTagsInput>
309
+ </template>
310
+ ```
311
+
312
+ In Nuxt (or apps with auto-import), omit the import if `VJoyTagsInput` is already global.
313
+
314
+ ---
315
+
316
+ ## Related
317
+
318
+ | Component | Use when |
319
+ | --- | --- |
320
+ | `VJoyLabel` | Accessible label (preferred over default slot) |
321
+ | `VJoyTag` | Single tag or custom tag layouts outside this input |
322
+ | `VJoyTagsList` | Read-only or pre-styled lists of tags |
323
+ | `VJoyFormError` | Used internally; use standalone errors for group-level messages if needed |
324
+ | `VJoyWrapper` | Form layout and spacing |
325
+ | `VJoyInput` | Single-value text fields |
326
+ | `VJoySelect` | Choosing from a predefined set |
327
+
328
+ ---
329
+
330
+ ## For agents
331
+
332
+ Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyTagsInput.dsds.json`](./JoyTagsInput.dsds.json).
333
+
334
+ ### Pick the right component
335
+
336
+ | If the intent is… | Emit… | Do not emit… |
337
+ | --- | --- | --- |
338
+ | Multiple free-form tokens in one field | `VJoyTagsInput` | Repeated `VJoyInput` or manual chip divs |
339
+ | Multiple email addresses | `VJoyTagsInput` + `validation="email"` | `VJoyInput` with comma-separated string |
340
+ | Single text value | `VJoyInput` | `VJoyTagsInput` with one-item array hack |
341
+ | Display-only tags | `VJoyTagsList` / `VJoyTag` | `VJoyTagsInput` with fake `modelValue` |
342
+ | Pick from fixed options | `VJoySelect` / checkboxes | Tags input with suggested datalist |
343
+ | Skills / keywords with duplicates blocked | `VJoyTagsInput` + `duplicationError` | Custom duplicate logic on raw input |
344
+
345
+ ### Hard rules
346
+
347
+ 1. **MUST** call `joy_get_component_info("VJoyTagsInput")` (or read types) before generating props.
348
+ 2. **MUST** bind `v-model` to `string[]` (default `[]`).
349
+ 3. **MUST** set `name` and associate `VJoyLabel` with `for` / `name`.
350
+ 4. **MUST** pass `invalidEmailError` when `validation="email"`.
351
+ 5. **MUST** pass `duplicationError` when duplicate feedback is required.
352
+ 6. **MUST NOT** invent props (`disabled`, `maxTags`, `separator`, `clearable`, …).
353
+ 7. **MUST NOT** override `.joy-tags-input__inner` border, padding, or tag chip styles.
354
+ 8. **MUST NOT** use the default slot for new labels — use `VJoyLabel`.
355
+ 9. **SHOULD** use `variant` / `size` from `TJoyTagVariants` / `TJoyTagSizes` only.
356
+ 10. **SHOULD** handle `validation:error` when parent submit must wait for a valid list.
357
+
358
+ ### Checklist
359
+
360
+ - [ ] Correct component (`VJoyTagsInput` or named alternative)
361
+ - [ ] Props / enums from MCP or types only
362
+ - [ ] `v-model` as `string[]` + `name` present
363
+ - [ ] Visible `VJoyLabel` (not placeholder-only)
364
+ - [ ] `duplicationError` / `invalidEmailError` set when validation applies
365
+ - [ ] `validation="email"` only for email collections
366
+ - [ ] No invented props / no custom tags-input chrome CSS
367
+ - [ ] Read-only tag display → `VJoyTagsList` / `VJoyTag`, not this input
368
+
369
+ ---
370
+
371
+ ## DSDS mapping
372
+
373
+ This Markdown mirrors [`JoyTagsInput.dsds.json`](./JoyTagsInput.dsds.json) (DSDS **0.15.2**).
374
+
375
+ | Section here | DSDS block |
376
+ | --- | --- |
377
+ | When to use / not | `use-cases` |
378
+ | Anatomy | `anatomy` |
379
+ | Variants / sizes | `variants` |
380
+ | States | `states` |
381
+ | Best practices / content rules | `guidelines` (+ `content`) |
382
+ | Accessibility | `accessibility` |
383
+ | Examples | `sections` |
384
+ | For agents | `agentDocumentBlocks` |
385
+ | Props detail | `api` in JSON + Joy MCP |
386
+ | Imports | `imports` in JSON |
387
+ | Token sizing | `design-specifications` in JSON (`--joy-core-spacing-*`, `--joy-form-border-width`, tag size tokens) |