@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,370 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoySelectableItemGroup
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoySelectableItemGroup.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Selectable item group
|
|
10
|
+
|
|
11
|
+
Use **`VJoySelectableItemGroup`** when the user must pick one or more options from a short list that should read as **selectable tiles** — each option is a bordered card with a primary label and optional supporting line.
|
|
12
|
+
|
|
13
|
+
Pass an `options` array and bind with `v-model:value`. Single selection uses hidden radios (`string` model); multiple selection embeds `VJoyCheckbox` in each tile (`array` model). Optional group label, validation, and per-option `subLabel` or custom `#item` slot content are supported.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoySelectableItemGroup` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoySelectableItemGroup docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-forms-vjoyselectableitemgroup--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoySelectableItemGroup.dsds.json`](./JoySelectableItemGroup.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoySelectableItemGroup")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a selectable item group when every option should stay visible as a compact tile — for example picking a delivery window, a billing cadence, or a small set of feature toggles where card-style affordance helps scanning.
|
|
28
|
+
|
|
29
|
+
The component works best with **2–5** options. It manages selection state for you from an `options` array, so you avoid hand-wiring multiple `VJoySelectableItem` instances.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Exactly one choice with rich card layout (outline, expandable fields) | `VJoyRadio` `theme="outline"` inside `VJoyRadioGroup` |
|
|
36
|
+
| Multiple boolean choices with card chrome (current recommendation) | `VJoyCheckboxCard` inside `VJoyCheckboxGroup` |
|
|
37
|
+
| A call to action or navigation tile | `VJoyButtonCard` |
|
|
38
|
+
| Compact inline radios without tile chrome | `VJoyRadio` inside `VJoyRadioGroup` |
|
|
39
|
+
| Standard checkbox list (no tile styling) | `VJoyCheckbox` inside `VJoyCheckboxGroup` |
|
|
40
|
+
| Long or searchable lists (roughly 8+ options) | `VJoySelect` |
|
|
41
|
+
| Custom-styled clickable card divs | Still use Joy selection components + tokens — don’t reinvent chrome |
|
|
42
|
+
|
|
43
|
+
> **Deprecated:** `VJoySelectableItemGroup` and `VJoySelectableItem` remain deprecated for new work (Joy v6+). Prefer `VJoyRadio` / `VJoyRadioGroup` for single choice, `VJoyCheckboxCard` / `VJoyCheckboxGroup` for multi-select cards, and `VJoyButtonCard` for CTA-style tiles. Keep using this component only while migrating legacy screens.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## How it works
|
|
48
|
+
|
|
49
|
+
### Anatomy
|
|
50
|
+
|
|
51
|
+
| Part | Required? | What it is |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| Fieldset wrapper | Yes | Root `<fieldset>` (`.joy-selectable-item-group__wrapper`) |
|
|
54
|
+
| Group label | No | `VJoyLabel` rendered as `<legend>` when `label` is set |
|
|
55
|
+
| Options container | Yes | Flex row/wrap (`.joy-selectable-item-group`) spacing tiles |
|
|
56
|
+
| `VJoySelectableItem` | Yes | One tile per `options` entry (radio or checkbox mode) |
|
|
57
|
+
| Option label | Yes* | From `option.label`, `labelKey`, default `#item` slot, or slot fallback |
|
|
58
|
+
| Option sublabel | No | `option.subLabel` or `selectable-item-sublabel` on the child item |
|
|
59
|
+
| Error message | No | `VJoyFormError` when both `invalid` and `errorMessage` are set |
|
|
60
|
+
| Focus ring | Yes | On the native control inside each tile — don’t remove in product CSS |
|
|
61
|
+
|
|
62
|
+
Each option object extends `VJoySelectableItem` props. Spread extra attributes with `option.attrs` (e.g. `data-testid`). Use the `#item` slot to replace the default label markup per option.
|
|
63
|
+
|
|
64
|
+
### Selection modes
|
|
65
|
+
|
|
66
|
+
| Mode | Prop | Model type | Control inside each tile |
|
|
67
|
+
| --- | --- | --- | --- |
|
|
68
|
+
| Single (default) | `multiple={false}` | `string` | Hidden `<input type="radio">` |
|
|
69
|
+
| Multiple | `multiple={true}` | `string[]` | `VJoyCheckbox` |
|
|
70
|
+
| Full width (single only) | `fullWidth={true}` | `string` | Tiles grow to fill horizontal space |
|
|
71
|
+
|
|
72
|
+
`fullWidth` applies flex growth so tiles share the row evenly. Storybook documents it for **single selection only**.
|
|
73
|
+
|
|
74
|
+
### Default values
|
|
75
|
+
|
|
76
|
+
- `value` defaults to **`null`**
|
|
77
|
+
- `multiple` defaults to **`false`**
|
|
78
|
+
- `fullWidth` defaults to **`false`**
|
|
79
|
+
- `required` defaults to **`false`**
|
|
80
|
+
- `labelSize` defaults to **`medium`**
|
|
81
|
+
- `requiredMark` defaults to **`false`**
|
|
82
|
+
- `options` is **required**
|
|
83
|
+
|
|
84
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
85
|
+
|
|
86
|
+
### Options and model
|
|
87
|
+
|
|
88
|
+
- **MUST** bind with `v-model:value` (`value` / `update:value`).
|
|
89
|
+
- **MUST** give each option a stable **`value`** (or map via `valueKey`) and a unique **`id`** for label/input association.
|
|
90
|
+
- **SHOULD** set `name` on options when using an external `VJoyLabel` with `for` (see Storybook **With label**).
|
|
91
|
+
- Use `valueKey` / `labelKey` when backend objects use different property names; type options as `TJoySelectableItemOption` (e.g. `satisfies (TJoySelectableItemOption & Record<string, unknown>)[]`).
|
|
92
|
+
- Single selection: `value` is a **string** (or empty string when cleared). Multiple: `value` is a **string array** (may be empty).
|
|
93
|
+
|
|
94
|
+
### Visual treatment
|
|
95
|
+
|
|
96
|
+
There is **no `variant` or `size` prop**. Tile width, min-height, border, and colors come from Joy tokens inside `VJoySelectableItem` (e.g. `--selectable-item-flex-rule`, `--selectable-item-min-height`, `--joy-core-spacing-5` gap) — **don’t hardcode tile dimensions** in product CSS.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## States
|
|
101
|
+
|
|
102
|
+
| State | What the user sees / can do |
|
|
103
|
+
| --- | --- |
|
|
104
|
+
| Default (unchecked) | Neutral border; option label in normal weight; tile is clickable |
|
|
105
|
+
| Checked | Secondary border and text color; label weight increases |
|
|
106
|
+
| Hover | Border shifts to secondary hover token (pointer devices, not disabled) |
|
|
107
|
+
| Focus | Visible outline on the focused radio/checkbox inside the tile |
|
|
108
|
+
| Disabled (per option) | Muted background and text; `not-allowed` cursor on that tile |
|
|
109
|
+
| Invalid | Error border on all tiles when `invalid` is true; pair with `errorMessage` |
|
|
110
|
+
| Full width | Tiles expand horizontally (`fullWidth` + single selection) |
|
|
111
|
+
|
|
112
|
+
When validation fails, set `invalid` and pass `errorMessage` so `VJoyFormError` renders below the group. `errorMessage` alone does **not** show an error — `invalid` must also be true.
|
|
113
|
+
|
|
114
|
+
In single-selection mode, clicking an already selected tile clears the selection (emits empty string).
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Best practices
|
|
119
|
+
|
|
120
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
121
|
+
|
|
122
|
+
### Selection and hierarchy
|
|
123
|
+
|
|
124
|
+
- **SHOULD** offer **2–5** visible tiles; beyond that, prefer `VJoySelect` or a different pattern.
|
|
125
|
+
- **SHOULD** keep option labels **parallel** (same grammar and similar length).
|
|
126
|
+
- **MUST NOT** use this component for new features when a non-deprecated alternative fits (see deprecation note).
|
|
127
|
+
- **MUST** use `multiple` only when the user may select more than one tile; use default single mode for exclusive choice.
|
|
128
|
+
|
|
129
|
+
### Implementation
|
|
130
|
+
|
|
131
|
+
- **MUST** use only documented props from MCP or `VJoySelectableItemGroup.vue` / `TJoySelectableItemOption`.
|
|
132
|
+
- **MUST** bind `v-model:value` with the correct type (`string` vs `string[]`).
|
|
133
|
+
- **MUST** style with Joy tokens — no custom `.joy-selectable-item` or `.joy-selectable-item-group` chrome overrides.
|
|
134
|
+
- **MUST NOT** invent props (`size`, `variant`, `direction`, …).
|
|
135
|
+
- **MUST** set `invalid` and `errorMessage` together when showing a validation error.
|
|
136
|
+
- **SHOULD** use `fullWidth` only for single-selection rows that must span the container.
|
|
137
|
+
- **SHOULD** pass `disabled: true` on individual options the user cannot pick — not only visual dimming.
|
|
138
|
+
|
|
139
|
+
### Labels and forms
|
|
140
|
+
|
|
141
|
+
- **SHOULD** provide a group label via `label` or an external `VJoyLabel` linked with matching `for` / `name` / `id`.
|
|
142
|
+
- **SHOULD** use `requiredMark` with `required` when the form uses asterisks.
|
|
143
|
+
- **SHOULD** use `optionalLabel` on non-required groups when mixed with required fields.
|
|
144
|
+
|
|
145
|
+
### Do / don’t
|
|
146
|
+
|
|
147
|
+
| Do | Don’t |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `v-model:value` with correct string vs array type | `v-model` (wrong binding name) |
|
|
150
|
+
| Unique `id` + `value` on every option | Duplicate ids across tiles |
|
|
151
|
+
| `invalid` + `errorMessage` for validation | `errorMessage` without `invalid` |
|
|
152
|
+
| `VJoyCheckboxCard` / `VJoyRadio` for new card pickers | New screens on deprecated tiles |
|
|
153
|
+
| `subLabel` for one-line detail | Long paragraphs as the primary label |
|
|
154
|
+
| `#item` slot for icons or custom markup | Custom clickable divs mimicking tiles |
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Content
|
|
159
|
+
|
|
160
|
+
Tile copy should make each option distinct at a glance and read consistently as a set.
|
|
161
|
+
|
|
162
|
+
### Label formula
|
|
163
|
+
|
|
164
|
+
**Short noun phrase** naming the option:
|
|
165
|
+
|
|
166
|
+
- ✅ Standard delivery
|
|
167
|
+
- ✅ Monthly billing
|
|
168
|
+
- ✅ Option A
|
|
169
|
+
|
|
170
|
+
Use the **same part of speech** across siblings.
|
|
171
|
+
|
|
172
|
+
### Sublabel formula
|
|
173
|
+
|
|
174
|
+
**One line of supporting detail** under the primary label:
|
|
175
|
+
|
|
176
|
+
- ✅ 3–5 business days
|
|
177
|
+
- ✅ Billed every month
|
|
178
|
+
- ✅ Includes basic support
|
|
179
|
+
|
|
180
|
+
Keep sublabels shorter than the main label; avoid duplicating the group legend.
|
|
181
|
+
|
|
182
|
+
### Writing rules
|
|
183
|
+
|
|
184
|
+
| Strength | Rule |
|
|
185
|
+
| --- | --- |
|
|
186
|
+
| **MUST** | Label the **option**, not the action — ✅ `Express delivery` · ❌ `Select express delivery` |
|
|
187
|
+
| **MUST** | Provide a group label (`label` or external `VJoyLabel`) in forms — not only tile text |
|
|
188
|
+
| **MUST** | Error messages state what to fix — ✅ `Select a delivery method` · ❌ `Invalid` |
|
|
189
|
+
| **MUST NOT** | Repeat the group legend in every tile label |
|
|
190
|
+
| **MUST NOT** | Use Yes / No as two tile labels when the question isn’t literally binary |
|
|
191
|
+
| **SHOULD** | Keep primary labels ≤ ~30 characters; move detail to `subLabel` |
|
|
192
|
+
| **SHOULD** | Use sentence case for labels and sublabels |
|
|
193
|
+
| **SHOULD** | Keep `value` as a stable programmatic key; localize display via `label` / `labelKey` |
|
|
194
|
+
|
|
195
|
+
### Patterns
|
|
196
|
+
|
|
197
|
+
**Segmented single choice** — `fullWidth` + 2–4 tiles (e.g. billing period). One sublabel per tile if timing or price differs.
|
|
198
|
+
|
|
199
|
+
**Multi-select filters** — `multiple` + short parallel labels; parent handles the selected array.
|
|
200
|
+
|
|
201
|
+
**Custom tile body** — `#item` slot for an icon + label when the default text slot is too plain; keep accessible name in the slot content.
|
|
202
|
+
|
|
203
|
+
**Validation** — Group label + `errorMessage` below: ✅ `Delivery method` + `Select one option to continue`.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Accessibility
|
|
208
|
+
|
|
209
|
+
Behaviors (what must happen), not slogans.
|
|
210
|
+
|
|
211
|
+
| Behavior | Expectation |
|
|
212
|
+
| --- | --- |
|
|
213
|
+
| Role | `<fieldset>` group; single mode uses `radiogroup` semantics via native radios; multiple mode uses checkboxes |
|
|
214
|
+
| Name | Group name from `label` / `<legend>` or external `VJoyLabel`; each option named by label text or `#item` slot |
|
|
215
|
+
| Keyboard | **Tab** to focus options; **Space** toggles checkboxes; arrow keys move between radios in single mode (browser default) |
|
|
216
|
+
| Focus | Visible focus outline on the control inside the tile — don’t remove with product CSS |
|
|
217
|
+
| Disabled | `disabled` on the underlying input per option; explain when the reason isn’t obvious |
|
|
218
|
+
| Invalid | Error styling plus `VJoyFormError` text — not color alone |
|
|
219
|
+
| Custom content | `#item` slot must still expose a readable option name (text or `aria-label` on meaningful content) |
|
|
220
|
+
|
|
221
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Examples
|
|
226
|
+
|
|
227
|
+
```vue
|
|
228
|
+
<script setup lang="ts">
|
|
229
|
+
import { ref } from 'vue'
|
|
230
|
+
import {
|
|
231
|
+
VJoySelectableItemGroup,
|
|
232
|
+
VJoyLabel,
|
|
233
|
+
type TJoySelectableItemOption,
|
|
234
|
+
} from '@maltjoy/core-vue'
|
|
235
|
+
|
|
236
|
+
const delivery = ref('standard')
|
|
237
|
+
const features = ref<string[]>(['analytics'])
|
|
238
|
+
|
|
239
|
+
const deliveryOptions: TJoySelectableItemOption[] = [
|
|
240
|
+
{ id: 'delivery-standard', value: 'standard', label: 'Standard', subLabel: '3–5 business days' },
|
|
241
|
+
{ id: 'delivery-express', value: 'express', label: 'Express', subLabel: 'Next business day' },
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
const featureOptions = [
|
|
245
|
+
{ id: 'feat-analytics', value: 'analytics', label: 'Analytics' },
|
|
246
|
+
{ id: 'feat-sso', value: 'sso', label: 'SSO', disabled: true },
|
|
247
|
+
{ id: 'feat-api', value: 'api', label: 'API access' },
|
|
248
|
+
]
|
|
249
|
+
</script>
|
|
250
|
+
|
|
251
|
+
<template>
|
|
252
|
+
<!-- Single selection (string model) -->
|
|
253
|
+
<VJoySelectableItemGroup
|
|
254
|
+
v-model:value="delivery"
|
|
255
|
+
label="Delivery method"
|
|
256
|
+
required
|
|
257
|
+
required-mark
|
|
258
|
+
:options="deliveryOptions"
|
|
259
|
+
/>
|
|
260
|
+
|
|
261
|
+
<!-- Full-width segmented row -->
|
|
262
|
+
<VJoySelectableItemGroup
|
|
263
|
+
v-model:value="delivery"
|
|
264
|
+
full-width
|
|
265
|
+
:options="deliveryOptions"
|
|
266
|
+
/>
|
|
267
|
+
|
|
268
|
+
<!-- Multiple selection (array model) -->
|
|
269
|
+
<VJoySelectableItemGroup
|
|
270
|
+
v-model:value="features"
|
|
271
|
+
multiple
|
|
272
|
+
label="Add-ons"
|
|
273
|
+
:options="featureOptions"
|
|
274
|
+
/>
|
|
275
|
+
|
|
276
|
+
<!-- Validation -->
|
|
277
|
+
<VJoySelectableItemGroup
|
|
278
|
+
v-model:value="delivery"
|
|
279
|
+
label="Delivery method"
|
|
280
|
+
:options="deliveryOptions"
|
|
281
|
+
invalid
|
|
282
|
+
error-message="Select a delivery method"
|
|
283
|
+
/>
|
|
284
|
+
|
|
285
|
+
<!-- External label -->
|
|
286
|
+
<VJoyLabel for="billing-period">Billing period</VJoyLabel>
|
|
287
|
+
<VJoySelectableItemGroup
|
|
288
|
+
v-model:value="delivery"
|
|
289
|
+
:options="deliveryOptions.map((o) => ({ ...o, name: 'billing-period' }))"
|
|
290
|
+
/>
|
|
291
|
+
</template>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoySelectableItemGroup` is already global.
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Related
|
|
299
|
+
|
|
300
|
+
| Component | Use when |
|
|
301
|
+
| --- | --- |
|
|
302
|
+
| `VJoySelectableItem` | Low-level tile (usually composed by the group — avoid standalone in new work) |
|
|
303
|
+
| `VJoyRadio` / `VJoyRadioGroup` | Single choice; preferred replacement for exclusive tile lists |
|
|
304
|
+
| `VJoyCheckboxCard` / `VJoyCheckboxGroup` | Multi-select card tiles; preferred replacement for `multiple` mode |
|
|
305
|
+
| `VJoyButtonCard` | CTA or navigation tile, not a form selection |
|
|
306
|
+
| `VJoyLabel` | External group legend with `for` / `name` binding |
|
|
307
|
+
| `VJoyFormError` | Also rendered automatically when `invalid` + `errorMessage` are set on the group |
|
|
308
|
+
| `VJoySelect` | Long dropdown lists |
|
|
309
|
+
| `VJoyCheckbox` | Plain multi-select without tile chrome |
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## For agents
|
|
314
|
+
|
|
315
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoySelectableItemGroup.dsds.json`](./JoySelectableItemGroup.dsds.json).
|
|
316
|
+
|
|
317
|
+
### Pick the right component
|
|
318
|
+
|
|
319
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
320
|
+
| --- | --- | --- |
|
|
321
|
+
| New single-choice card picker | `VJoyRadio` `theme="outline"` in `VJoyRadioGroup` | `VJoySelectableItemGroup` |
|
|
322
|
+
| New multi-choice card picker | `VJoyCheckboxCard` in `VJoyCheckboxGroup` | `VJoySelectableItemGroup` `multiple` |
|
|
323
|
+
| Legacy tile group (maintain only) | `VJoySelectableItemGroup` | Raw `VJoySelectableItem` loops without the group |
|
|
324
|
+
| Dropdown / many options | `VJoySelect` | Many selectable tiles |
|
|
325
|
+
| CTA tile | `VJoyButtonCard` | Selectable item with fake selection state |
|
|
326
|
+
| Plain checkbox list | `VJoyCheckboxGroup` | `multiple` selectable item group |
|
|
327
|
+
|
|
328
|
+
### Hard rules
|
|
329
|
+
|
|
330
|
+
1. **MUST** call `joy_get_component_info("VJoySelectableItemGroup")` (or read source types) before generating props.
|
|
331
|
+
2. **MUST** bind `v-model:value` — not `v-model` / `modelValue`.
|
|
332
|
+
3. **MUST** use `string` model for single selection and `string[]` for `multiple`.
|
|
333
|
+
4. **MUST** include `value` and unique `id` on every option object.
|
|
334
|
+
5. **MUST** set both `invalid` and `errorMessage` to show validation text.
|
|
335
|
+
6. **MUST NOT** invent props (`size`, `variant`, `direction`, …).
|
|
336
|
+
7. **MUST NOT** use `fullWidth` with `multiple` (unsupported pattern in Storybook).
|
|
337
|
+
8. **MUST NOT** override `.joy-selectable-item` / `.joy-selectable-item-group` border, size, or padding.
|
|
338
|
+
9. **SHOULD NOT** emit this component for greenfield UI — prefer `VJoyRadio`, `VJoyCheckboxCard`, or `VJoyButtonCard`.
|
|
339
|
+
10. **SHOULD** use `valueKey` / `labelKey` when option objects use non-standard keys.
|
|
340
|
+
|
|
341
|
+
### Checklist
|
|
342
|
+
|
|
343
|
+
- [ ] Correct component (or named non-deprecated alternative for new work)
|
|
344
|
+
- [ ] Props from MCP / source only — no invented API
|
|
345
|
+
- [ ] `v-model:value` with matching `string` vs `string[]` type
|
|
346
|
+
- [ ] Every option has `value` + unique `id`
|
|
347
|
+
- [ ] `multiple` aligned with selection semantics
|
|
348
|
+
- [ ] Group label present in forms (`label` or external `VJoyLabel`)
|
|
349
|
+
- [ ] `invalid` + `errorMessage` when validation fails
|
|
350
|
+
- [ ] No custom selectable-tile chrome CSS
|
|
351
|
+
- [ ] `fullWidth` only on single-selection groups
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## DSDS mapping
|
|
356
|
+
|
|
357
|
+
This Markdown mirrors [`JoySelectableItemGroup.dsds.json`](./JoySelectableItemGroup.dsds.json) (DSDS **0.15.2**).
|
|
358
|
+
|
|
359
|
+
| Section here | DSDS block |
|
|
360
|
+
| --- | --- |
|
|
361
|
+
| When to use / not | `use-cases` |
|
|
362
|
+
| Anatomy / selection modes | `anatomy` |
|
|
363
|
+
| States | `states` |
|
|
364
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
365
|
+
| Accessibility | `accessibility` |
|
|
366
|
+
| Examples | `sections` |
|
|
367
|
+
| For agents | `agentDocumentBlocks` |
|
|
368
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
369
|
+
| Imports | `imports` in JSON |
|
|
370
|
+
| Token sizing | `design-specifications` in JSON (`--selectable-item-flex-rule`, `--selectable-item-min-height`, `--joy-core-spacing-5`) |
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoySeparator
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoySeparator.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Separator
|
|
10
|
+
|
|
11
|
+
Use **`VJoySeparator`** when you need a horizontal rule with optional centered label text to break up content — for example an “or” divider between sign-in options, a date stamp between timeline blocks, or a named section boundary inside a form or list.
|
|
12
|
+
|
|
13
|
+
It renders a full-width line pair flanking the label, uses Joy secondary color and `primary-400` typography, and supports a compact **`small`** layout for narrow columns.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoySeparator` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoySeparator docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-view-vjoyseparator--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoySeparator.dsds.json`](./JoySeparator.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoySeparator")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a separator when two blocks of content should read as distinct sections but stay on the same page or panel — especially when a short label clarifies the break (`or`, `and`, a date, or a section name).
|
|
28
|
+
|
|
29
|
+
Typical jobs: alternate auth paths (“or continue with email”), grouped settings with a named divider, or visual breathing room between stacked list sections.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Structured card or panel with header, body, footer | `VJoyPanel` / `VJoyPanelSection` |
|
|
36
|
+
| Spacing only — no line | `VJoyWrapper` gap / margin tokens |
|
|
37
|
+
| Divider between menu or dropdown items | Built-in list separators in `VJoyMenu` / `VJoyDropdownList` |
|
|
38
|
+
| A semantic document break for assistive tech alone | Proper heading hierarchy (`h2`–`h6`) — this component is **not** an `<hr>` |
|
|
39
|
+
| Custom `<hr>` or border-top CSS | Still use `VJoySeparator` + Joy tokens |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
### Anatomy
|
|
46
|
+
|
|
47
|
+
| Part | Required? | What it is |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Root container | Yes | `.joy-separator` block wrapper (`display: block`, full width) |
|
|
50
|
+
| Label (`text` prop) | No | Centered copy inside `<span>`; omit or leave empty for a line-only divider |
|
|
51
|
+
| Line segments | Yes | `::before` and `::after` pseudo-elements on the label span — 1px rules using `--separator-color` |
|
|
52
|
+
| Vertical spacing | Yes | `margin: var(--joy-core-spacing-4) 0` on the root — don’t add duplicate margins in apps |
|
|
53
|
+
|
|
54
|
+
### Default values
|
|
55
|
+
|
|
56
|
+
- `size` defaults to **`default`**
|
|
57
|
+
- `text` is **optional** — when omitted, the center span is empty and only the horizontal rules show
|
|
58
|
+
|
|
59
|
+
Props: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
60
|
+
|
|
61
|
+
### Size behavior
|
|
62
|
+
|
|
63
|
+
`size` controls layout width, not typography scale:
|
|
64
|
+
|
|
65
|
+
| `size` | Behavior |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `default` | Full width; line segments grow with `flex: 1` on each side of the label |
|
|
68
|
+
| `small` | Label row capped at **320px**, centered; each line segment capped at **60px** — use in narrow columns (auth panels, sidebars) |
|
|
69
|
+
|
|
70
|
+
There is no `medium` or `large` value. Color and font come from Joy tokens (`--joy-color-secondary-50`, `primary-400`) — **do not hardcode** line color, font size, or the 320px / 60px caps in product CSS.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Best practices
|
|
75
|
+
|
|
76
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
77
|
+
|
|
78
|
+
### Layout and hierarchy
|
|
79
|
+
|
|
80
|
+
- **SHOULD** use a separator **between** two related blocks — not as the only visual structure on an empty page.
|
|
81
|
+
- **SHOULD** use `size="small"` in narrow layouts; keep `default` for full-width sections.
|
|
82
|
+
- **MUST NOT** stack multiple separators back-to-back without content between them.
|
|
83
|
+
- **MUST NOT** rely on the separator alone to convey page structure — pair with headings or landmarks when sections need names for all users.
|
|
84
|
+
|
|
85
|
+
### Implementation
|
|
86
|
+
|
|
87
|
+
- **MUST** use only documented `size` values from MCP or `JoySeparator.types.ts` (`default`, `small`).
|
|
88
|
+
- **MUST** style with Joy tokens — no custom `.joy-separator` line color, font, or margin overrides.
|
|
89
|
+
- **MUST NOT** invent props (`variant`, `color`, `orientation`, `vertical`, …).
|
|
90
|
+
- **MUST NOT** wrap the separator in a clickable element or attach click handlers for navigation — use `VJoyButton` or `VJoyLink` instead.
|
|
91
|
+
|
|
92
|
+
### Text copy
|
|
93
|
+
|
|
94
|
+
- **SHOULD** keep `text` **short** — one to three words (`or`, `and`, `Today`, `Billing`).
|
|
95
|
+
- **MUST** use sentence case for labels.
|
|
96
|
+
- **MUST NOT** put long instructions or links in `text` — use body copy or `VJoyHighlight` above/below the break.
|
|
97
|
+
- **SHOULD** (FR) follow French typography rules (non-breaking spaces before `;`, `?`, `!`, `:`) when localized.
|
|
98
|
+
|
|
99
|
+
### Do / don’t
|
|
100
|
+
|
|
101
|
+
| Do | Don’t |
|
|
102
|
+
| --- | --- |
|
|
103
|
+
| `text="or"` between two auth options | Paragraph of help text in `text` |
|
|
104
|
+
| `size="small"` in a 320px-wide panel | `default` forcing oversized lines in a sidebar |
|
|
105
|
+
| Headings + separator for named sections | Separator as the only section title |
|
|
106
|
+
| Joy spacing tokens around content blocks | Extra `margin-top` on `.joy-separator` in app CSS |
|
|
107
|
+
| `VJoyPanelSection` for major page structure | Separators instead of panel headers |
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Accessibility
|
|
112
|
+
|
|
113
|
+
Behaviors (what must happen), not slogans.
|
|
114
|
+
|
|
115
|
+
| Behavior | Expectation |
|
|
116
|
+
| --- | --- |
|
|
117
|
+
| Role | Presentational `<div>` — **not** a native `<hr>` and not a landmark |
|
|
118
|
+
| Name | No interactive name; optional `text` is visible copy, not an accessible section heading |
|
|
119
|
+
| Structure | If `text` names a section (e.g. “Billing”), also provide a real heading (`h2`–`h6`) or `aria-labelledby` on the section — don’t rely on the separator label alone |
|
|
120
|
+
| Keyboard | Not focusable; no keyboard interaction |
|
|
121
|
+
| Color | Line and label use `--joy-color-secondary-50` — decorative; section meaning must not depend on color alone |
|
|
122
|
+
| Motion | Static — no animation API |
|
|
123
|
+
|
|
124
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<script setup lang="ts">
|
|
132
|
+
import { VJoySeparator } from '@maltjoy/core-vue'
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<template>
|
|
136
|
+
<!-- Labeled break between sign-in options -->
|
|
137
|
+
<VJoyButton variant="main" label="Continue with Google" />
|
|
138
|
+
<VJoySeparator text="or" />
|
|
139
|
+
<VJoyInput v-model="email" name="email" label="Email address" />
|
|
140
|
+
|
|
141
|
+
<!-- Compact divider in a narrow column -->
|
|
142
|
+
<VJoySeparator size="small" text="Today" />
|
|
143
|
+
|
|
144
|
+
<!-- Line only (no label) -->
|
|
145
|
+
<VJoySeparator />
|
|
146
|
+
</template>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoySeparator` is already global.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Related
|
|
154
|
+
|
|
155
|
+
| Component | Use when |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `VJoyWrapper` | Flex layout and gap between blocks without a visible rule |
|
|
158
|
+
| `VJoyPanel` / `VJoyPanelSection` | Card-like sections with title, body, and actions |
|
|
159
|
+
| `VJoyText` | Body copy or section titles without a horizontal rule |
|
|
160
|
+
| `VJoyButton` / `VJoyLink` | Actions placed above or below a separator (e.g. auth flows) |
|
|
161
|
+
| `VJoyInput` | Form fields often separated by an “or” divider in combined flows |
|