@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,396 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyDropdownList
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyDropdownList.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Dropdown list
|
|
10
|
+
|
|
11
|
+
Use **`VJoyDropdownList`** when you need the scrollable option panel of a dropdown — selectable rows, optional header/footer, and listbox keyboard support — without the trigger, overlay positioning, or open/close lifecycle.
|
|
12
|
+
|
|
13
|
+
It powers `VJoyDropdown` internally and can be mounted standalone when the parent already controls visibility and placement. Bind selection with `v-model`, pass an `options` array, or inject fully custom body content through slots.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyDropdownList` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy components](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoyDropdownList docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-forms-vjoydropdownlist--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyDropdownList.dsds.json`](./JoyDropdownList.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyDropdownList")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a dropdown list when a panel must show one or more selectable rows (or custom filter controls) inside an already-open overlay — for example the list body of `VJoyDropdown`, a filter bar panel you position yourself, or a popover whose open state lives in the parent.
|
|
28
|
+
|
|
29
|
+
It fits both **predefined option lists** (`options` + `v-model`) and **custom bodies** (`dropdown-list-items` slot with `VJoyCheckboxGroup` or other form controls).
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Trigger + open/close + click-outside + focus return | `VJoyDropdown` |
|
|
36
|
+
| Labeled single-select form field (native select semantics) | `VJoySelect` |
|
|
37
|
+
| Persistent navigation menus | `VJoyMenu` + `VJoyMenuItem` |
|
|
38
|
+
| Every option visible at once (2–7 parallel choices) | `VJoyRadio` inside `VJoyRadioGroup` |
|
|
39
|
+
| Card-style selectable tiles | `VJoySelectableItemGroup` |
|
|
40
|
+
| Custom `position: absolute` panels with ad-hoc list markup | Still use `VJoyDropdownList` + Joy tokens — don’t reinvent list chrome |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
### Anatomy
|
|
47
|
+
|
|
48
|
+
| Part | Required? | What it is |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| Root container | Yes | `.joy-dropdown-list` — bordered panel with elevation and fixed `width` |
|
|
51
|
+
| Header | No | `.joy-dropdown-list__header` — optional reset CTA and/or `dropdown-list-header` slot |
|
|
52
|
+
| Reset control | No | Text button when `reset` is true; emits `reset` (does not clear parent state) |
|
|
53
|
+
| Items scroller | Yes | `.joy-dropdown-list__items` — scrollable area capped by `height` |
|
|
54
|
+
| Options list | No* | `<ul role="listbox">` with `<li role="option">` rows when `options` is non-empty |
|
|
55
|
+
| Custom items body | No* | `dropdown-list-items` slot — replaces the built-in list when `options` is empty |
|
|
56
|
+
| Option label | No | Default `<span>{{ option.label }}</span>` or scoped `label` slot per row |
|
|
57
|
+
| Footer | No | `.joy-dropdown-list__footer` via `dropdown-list-footer` slot |
|
|
58
|
+
|
|
59
|
+
\* Use either the built-in `options` list **or** `dropdown-list-items`, not both for the same purpose.
|
|
60
|
+
|
|
61
|
+
### Default values
|
|
62
|
+
|
|
63
|
+
- `size` defaults to **`medium`**
|
|
64
|
+
- `width` defaults to **`300`** (px, applied as inline `width` / `max-width`)
|
|
65
|
+
- `height` defaults to **`300`** (px max-height of the scrollable items area)
|
|
66
|
+
- `options` defaults to **`[]`**
|
|
67
|
+
- `reset` defaults to **`false`**
|
|
68
|
+
- `modelValue` has no default — bind when selection should persist
|
|
69
|
+
|
|
70
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
71
|
+
|
|
72
|
+
### Option shape
|
|
73
|
+
|
|
74
|
+
Each entry in `options` should provide:
|
|
75
|
+
|
|
76
|
+
- **`id`** (required, unique) — row identity and default `v-model` value
|
|
77
|
+
- **`label`** (required) — visible text; also used for `title` tooltip and `aria-label` on the row
|
|
78
|
+
- **`value`** (optional) — alternate `v-model` value when it must differ from `id`
|
|
79
|
+
- **`disabled`** (optional, default `false`) — non-interactive row
|
|
80
|
+
- **`href`** (optional) — when the `label` slot renders a link, **Enter** delegates to the anchor click
|
|
81
|
+
|
|
82
|
+
### Events the parent should handle
|
|
83
|
+
|
|
84
|
+
| Event | When it fires | Typical parent action |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `update:modelValue` | User selects an enabled option | Persist selection; optionally close overlay (`VJoyDropdown` does this when `closeOnSelect` is true) |
|
|
87
|
+
| `close:dropdownList` | **Escape** or **Tab** while focus is inside the built-in listbox | Close the surrounding overlay and return focus to the trigger |
|
|
88
|
+
| `reset` | User clicks the reset CTA (`reset` prop) | Clear filter / checkbox state in the parent — the list does not mutate models |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Variants
|
|
93
|
+
|
|
94
|
+
`VJoyDropdownList` has **no visual emphasis variants** (no primary/secondary). Appearance is controlled by **size**, **selection state**, and **disabled** rows only.
|
|
95
|
+
|
|
96
|
+
Choose a different component if you need card layouts, native form-field chrome, or trigger-driven overlay behavior.
|
|
97
|
+
|
|
98
|
+
### Not in the API: `dropdown-list-item` slot
|
|
99
|
+
|
|
100
|
+
`defineSlots` documents a `dropdown-list-item` scoped slot, but the template wires **`label`** for per-row customization. Use `#label="{ …option }"` (or let `VJoyDropdown` forward `dropdown-item`) — do not rely on `dropdown-list-item` until it is implemented.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Sizes
|
|
105
|
+
|
|
106
|
+
`size` controls **row density** (height and font size via form-size tokens). It does not size header/footer padding beyond shared spacing tokens.
|
|
107
|
+
|
|
108
|
+
| Size | Use when |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `medium` | Default product menus and option lists |
|
|
111
|
+
| `small` | Dense toolbars, filter bars, tables |
|
|
112
|
+
| `xsmall` | Very compact UI (match `xsmall` triggers on `VJoyButton` / `VJoyIconButton`) |
|
|
113
|
+
|
|
114
|
+
Heights come from `--dropdown-item-height` / `--dropdown-item-font-size` inside the component — **don’t hardcode item heights** in apps.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## States
|
|
119
|
+
|
|
120
|
+
| State | What the user sees / can do |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| Default row | Neutral text; pointer cursor; hover highlights row |
|
|
123
|
+
| Selected row | `joy-dropdown-list__item--selected` — secondary background and text color; `aria-selected="true"` |
|
|
124
|
+
| Focused row | Inset focus ring on the `<li>` (`box-shadow` focus token) |
|
|
125
|
+
| Disabled row | Muted text; `cursor: not-allowed`; not clickable; `aria-disabled="true"` |
|
|
126
|
+
| Scroll overflow | Items area scrolls vertically when content exceeds `height` |
|
|
127
|
+
| Custom slot body | `dropdown-list-items` adds padding class; built-in listbox keyboard patterns are **not** applied |
|
|
128
|
+
| Reset visible | Header shows reset CTA when `reset` is true |
|
|
129
|
+
|
|
130
|
+
On mount, if `modelValue` matches an option, that row receives focus and the scroller scrolls it into view.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Best practices
|
|
135
|
+
|
|
136
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
137
|
+
|
|
138
|
+
### Selection and data
|
|
139
|
+
|
|
140
|
+
- **MUST** give every option a unique `id`.
|
|
141
|
+
- **MUST** bind `v-model` when a single selected value should persist across opens.
|
|
142
|
+
- **SHOULD** set `value` on an option only when the stored model must differ from `id`.
|
|
143
|
+
- **SHOULD** handle `@reset` when `reset` is true — the component does not clear parent state.
|
|
144
|
+
- **SHOULD** listen for `@close:dropdown-list` when the list sits inside a custom overlay (mirror `VJoyDropdown` behavior).
|
|
145
|
+
|
|
146
|
+
### Layout
|
|
147
|
+
|
|
148
|
+
- **SHOULD** set `width` to fit the longest expected label (rows truncate with ellipsis; full text is in `title`).
|
|
149
|
+
- **SHOULD** set `height` when option count can exceed the viewport — scrolling is internal to the items area.
|
|
150
|
+
- **MUST NOT** hardcode panel border, radius, shadow, or `z-index` — Joy tokens apply via `.joy-dropdown-list`.
|
|
151
|
+
|
|
152
|
+
### Composition
|
|
153
|
+
|
|
154
|
+
- **SHOULD** prefer `VJoyDropdown` when you need a trigger, click-outside, open/close animation, and focus return.
|
|
155
|
+
- **SHOULD** use `dropdown-list-items` + `VJoyCheckboxGroup` for multi-select filter panels; keep the list open until the parent closes it.
|
|
156
|
+
- **MUST** set `aria-label` or `aria-labelledby` when the list is not labelled by a visible trigger element (standalone panels).
|
|
157
|
+
|
|
158
|
+
### Implementation
|
|
159
|
+
|
|
160
|
+
- **MUST** use only documented prop / enum values (MCP / `JoyDropdownList.types.ts`).
|
|
161
|
+
- **MUST** use Joy tokens and slots — no custom list chrome CSS.
|
|
162
|
+
- **MUST NOT** invent props (`variant`, `open`, `direction`, …).
|
|
163
|
+
- **MUST NOT** mount `VJoyDropdownList` without a parent plan for open/close and focus when users expect dropdown behavior — use `VJoyDropdown`.
|
|
164
|
+
|
|
165
|
+
### Do / don’t
|
|
166
|
+
|
|
167
|
+
| Do | Don’t |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `VJoyDropdown` for full dropdown UX | `VJoyDropdownList` alone with no overlay logic |
|
|
170
|
+
| `label` slot for custom row content | Undocumented `dropdown-list-item` slot |
|
|
171
|
+
| `@reset` handler when `reset` is true | Expect reset to clear `v-model` automatically |
|
|
172
|
+
| `aria-labelledby` from trigger id (`VJoyDropdown` pattern) | Orphan listbox with no accessible name |
|
|
173
|
+
| Match list `size` to trigger density | `xsmall` trigger + `medium` rows |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Content
|
|
178
|
+
|
|
179
|
+
Dropdown lists surface copy on options, in the optional header/footer, and on the reset control. Keep labels scannable — users scan the panel to decide quickly.
|
|
180
|
+
|
|
181
|
+
### Label formula
|
|
182
|
+
|
|
183
|
+
**Options:** **plain noun or short phrase** — e.g. `Paris`, `Export as PDF`, `Last 30 days`. Use parallel grammar across siblings.
|
|
184
|
+
|
|
185
|
+
**Header slot:** **instruction or context** — e.g. `Select a station`, `Filter by status`.
|
|
186
|
+
|
|
187
|
+
**Reset CTA:** **verb + scope** — e.g. `Reset filter`, `Clear selection`. Code falls back to `Reset` when `reset-label` is omitted.
|
|
188
|
+
|
|
189
|
+
**Footer actions:** same rules as `VJoyButton` — infinitive verb + noun (`Apply filters`, `Save changes`).
|
|
190
|
+
|
|
191
|
+
### Writing rules
|
|
192
|
+
|
|
193
|
+
| Strength | Rule |
|
|
194
|
+
| --- | --- |
|
|
195
|
+
| **MUST** | Option labels stay concise; long text truncates with `title` tooltip on the row |
|
|
196
|
+
| **MUST** | Disabled options include context elsewhere if the reason isn’t obvious |
|
|
197
|
+
| **MUST NOT** | Vague option labels — ❌ `Item 1`, ❌ `Click here` |
|
|
198
|
+
| **MUST NOT** | Duplicate header context on every option when the list topic is clear |
|
|
199
|
+
| **SHOULD** | ≤ 3 words per option where possible |
|
|
200
|
+
| **SHOULD** | Use sentence case for multi-word labels |
|
|
201
|
+
| **SHOULD** | Name destructive actions explicitly — ✅ `Delete project` · ❌ `Delete` alone in a mixed list |
|
|
202
|
+
| **SHOULD** | Localize `reset-label` — do not rely on the English default `Reset` in product UI |
|
|
203
|
+
| **SHOULD** (FR) | Follow French grammar for labels and reset copy when the UI is localized |
|
|
204
|
+
|
|
205
|
+
### Patterns
|
|
206
|
+
|
|
207
|
+
**Simple pick list** — Options are place names or enum values. No header unless the trigger label is ambiguous.
|
|
208
|
+
|
|
209
|
+
**Filter panel** — Header holds search or title; body uses `dropdown-list-items` with checkboxes; footer holds `Apply filters`; `reset` clears parent filter state.
|
|
210
|
+
|
|
211
|
+
**Link rows** — Use the `label` slot to render `<a href="…">`; **Enter** on a focused row activates the link instead of emitting `update:modelValue`.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Accessibility
|
|
216
|
+
|
|
217
|
+
Behaviors (what must happen), not slogans.
|
|
218
|
+
|
|
219
|
+
| Behavior | Expectation |
|
|
220
|
+
| --- | --- |
|
|
221
|
+
| List naming | Set `aria-label` or `aria-labelledby` on the listbox — `VJoyDropdown` passes the trigger’s generated id via `aria-labelledby` |
|
|
222
|
+
| Role (options mode) | `role="listbox"` on `<ul>`; each row `role="option"` with `aria-selected` / `aria-disabled` |
|
|
223
|
+
| Row name | `aria-label` and `title` use `option.label` |
|
|
224
|
+
| Focus on mount | Selected option receives focus; scroller scrolls it into view |
|
|
225
|
+
| Keyboard (built-in list) | **ArrowUp** / **ArrowDown** move focus; **Home** / **End** jump; **Enter** selects or activates link; **Space** prevented from scrolling page while row focused |
|
|
226
|
+
| Keyboard (close) | **Escape** or **Tab** while focus is inside the listbox emits `close:dropdownList` |
|
|
227
|
+
| Keyboard (custom slot) | When using `dropdown-list-items` without `options`, listbox patterns are not enforced — manage focus inside custom content |
|
|
228
|
+
| Disabled options | `aria-disabled="true"`; not selectable on click |
|
|
229
|
+
|
|
230
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Examples
|
|
235
|
+
|
|
236
|
+
```vue
|
|
237
|
+
<script setup lang="ts">
|
|
238
|
+
import { ref } from 'vue'
|
|
239
|
+
import { VJoyDropdownList, VJoyButton } from '@maltjoy/core-vue'
|
|
240
|
+
|
|
241
|
+
interface StationOption {
|
|
242
|
+
id: string
|
|
243
|
+
label: string
|
|
244
|
+
value?: string
|
|
245
|
+
disabled?: boolean
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const station = ref('toulouse')
|
|
249
|
+
const open = ref(false)
|
|
250
|
+
|
|
251
|
+
const stations: StationOption[] = [
|
|
252
|
+
{ id: 'paris', label: 'Paris Montparnasse' },
|
|
253
|
+
{ id: 'toulouse', label: 'Toulouse Matabiau' },
|
|
254
|
+
{ id: 'bordeaux', label: 'Bordeaux Saint-Jean' },
|
|
255
|
+
{ id: 'marseille', label: 'Marseille Saint-Charles', disabled: true },
|
|
256
|
+
]
|
|
257
|
+
|
|
258
|
+
function clearFilter() {
|
|
259
|
+
station.value = ''
|
|
260
|
+
}
|
|
261
|
+
</script>
|
|
262
|
+
|
|
263
|
+
<template>
|
|
264
|
+
<!-- Built-in options + v-model -->
|
|
265
|
+
<VJoyDropdownList
|
|
266
|
+
v-model="station"
|
|
267
|
+
:options="stations"
|
|
268
|
+
aria-label="French stations"
|
|
269
|
+
width="300"
|
|
270
|
+
height="200"
|
|
271
|
+
size="medium"
|
|
272
|
+
/>
|
|
273
|
+
|
|
274
|
+
<!-- Header, reset, and footer -->
|
|
275
|
+
<VJoyDropdownList
|
|
276
|
+
v-model="station"
|
|
277
|
+
:options="stations"
|
|
278
|
+
reset
|
|
279
|
+
reset-label="Clear selection"
|
|
280
|
+
width="280"
|
|
281
|
+
height="240"
|
|
282
|
+
aria-labelledby="station-picker-label"
|
|
283
|
+
@reset="clearFilter"
|
|
284
|
+
>
|
|
285
|
+
<template #dropdown-list-header>
|
|
286
|
+
<span id="station-picker-label">Choose a station</span>
|
|
287
|
+
</template>
|
|
288
|
+
<template #dropdown-list-footer>
|
|
289
|
+
<VJoyButton size="xxsmall" variant="main">Done</VJoyButton>
|
|
290
|
+
</template>
|
|
291
|
+
</VJoyDropdownList>
|
|
292
|
+
|
|
293
|
+
<!-- Custom body (no built-in keyboard listbox) -->
|
|
294
|
+
<VJoyDropdownList width="240" height="200" aria-label="Filter options">
|
|
295
|
+
<template #dropdown-list-items>
|
|
296
|
+
<!-- VJoyCheckboxGroup or other controls -->
|
|
297
|
+
</template>
|
|
298
|
+
</VJoyDropdownList>
|
|
299
|
+
|
|
300
|
+
<!-- Custom row label -->
|
|
301
|
+
<VJoyDropdownList v-model="station" :options="stations" aria-label="Stations">
|
|
302
|
+
<template #label="{ label, href }">
|
|
303
|
+
<a v-if="href" :href="href">{{ label }}</a>
|
|
304
|
+
<span v-else>{{ label }}</span>
|
|
305
|
+
</template>
|
|
306
|
+
</VJoyDropdownList>
|
|
307
|
+
</template>
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyDropdownList` is already global.
|
|
311
|
+
|
|
312
|
+
For trigger, positioning, and click-outside, wrap usage in `VJoyDropdown` instead of reimplementing those behaviors around the list alone.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Related
|
|
317
|
+
|
|
318
|
+
| Component | Use when |
|
|
319
|
+
| --- | --- |
|
|
320
|
+
| `VJoyDropdown` | Full dropdown with trigger, overlay lifecycle, and focus management |
|
|
321
|
+
| `VJoySelect` | Labeled single-select form field with native `<select>` semantics |
|
|
322
|
+
| `VJoyCheckboxGroup` | Multi-select content inside `dropdown-list-items` |
|
|
323
|
+
| `VJoyFilterBar` | Composed filter UI that embeds dropdown patterns |
|
|
324
|
+
| `VJoyButton` | Footer actions and standalone triggers when not using `VJoyDropdown` |
|
|
325
|
+
| `VJoyMenu` | Persistent navigation menus with `VJoyMenuItem` |
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## For agents
|
|
330
|
+
|
|
331
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyDropdownList.dsds.json`](./JoyDropdownList.dsds.json).
|
|
332
|
+
|
|
333
|
+
### Pick the right component
|
|
334
|
+
|
|
335
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
336
|
+
| --- | --- | --- |
|
|
337
|
+
| Overflow / contextual menu from a trigger | `VJoyDropdown` | `VJoyDropdownList` without overlay parent |
|
|
338
|
+
| List panel only (parent owns open/close) | `VJoyDropdownList` | `VJoyDropdown` with empty `dropdown-button` |
|
|
339
|
+
| Labeled form single-select | `VJoySelect` | `VJoyDropdownList` mimicking a native select |
|
|
340
|
+
| Multi-select filters with checkboxes | `VJoyDropdown` or `VJoyDropdownList` in custom overlay + `dropdown-list-items` | `options` array with fake checkbox markup |
|
|
341
|
+
| Custom row content per option | `#label` scoped slot | `dropdown-list-item` slot (not wired) |
|
|
342
|
+
|
|
343
|
+
### Hard rules
|
|
344
|
+
|
|
345
|
+
1. **MUST** call `joy_get_component_info("VJoyDropdownList")` and/or read `JoyDropdownList.types.ts` before generating props.
|
|
346
|
+
2. **MUST** give every `options` entry a unique `id` and a `label`.
|
|
347
|
+
3. **MUST** bind `v-model` when single selection from `options` should persist.
|
|
348
|
+
4. **MUST** set `aria-label` or `aria-labelledby` when no visible trigger labels the list.
|
|
349
|
+
5. **MUST** handle `@reset` when `reset` is true — reset does not mutate parent state.
|
|
350
|
+
6. **MUST** handle `@close:dropdown-list` when embedding in a custom overlay (close on Escape / Tab from list focus).
|
|
351
|
+
7. **MUST NOT** invent props (`variant`, `direction`, `justify`, `open`, …) or size values outside `xsmall` | `small` | `medium`.
|
|
352
|
+
8. **MUST NOT** override `.joy-dropdown-list` border, shadow, radius, or item heights in product CSS.
|
|
353
|
+
9. **SHOULD** use `VJoyDropdown` instead of standalone list when trigger + click-outside are needed.
|
|
354
|
+
10. **SHOULD** pass `width` / `height` as strings (pixel values without units in the prop value, e.g. `width="300"`).
|
|
355
|
+
|
|
356
|
+
### Slot map
|
|
357
|
+
|
|
358
|
+
| Slot | Typical content |
|
|
359
|
+
| --- | --- |
|
|
360
|
+
| `dropdown-list-header` | Title, search field, instructions |
|
|
361
|
+
| `dropdown-list-items` | `VJoyCheckboxGroup`, custom markup (replaces built-in `options` list) |
|
|
362
|
+
| `label` | Custom per-row content (text, links) |
|
|
363
|
+
| `dropdown-list-footer` | `VJoyButton` Apply / Done |
|
|
364
|
+
|
|
365
|
+
### Checklist
|
|
366
|
+
|
|
367
|
+
- [ ] Correct component (`VJoyDropdownList` or named alternative)
|
|
368
|
+
- [ ] Props / enums from MCP or types only
|
|
369
|
+
- [ ] `options` rows have unique `id` + `label`
|
|
370
|
+
- [ ] `v-model` bound when selection should persist
|
|
371
|
+
- [ ] `aria-label` or `aria-labelledby` set for standalone list
|
|
372
|
+
- [ ] `@reset` handler when `reset` is true
|
|
373
|
+
- [ ] `@close:dropdown-list` wired when inside custom overlay
|
|
374
|
+
- [ ] `#label` slot for row customization (not `dropdown-list-item`)
|
|
375
|
+
- [ ] No invented props / no custom list chrome CSS
|
|
376
|
+
- [ ] `size` matches surrounding trigger density
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## DSDS mapping
|
|
381
|
+
|
|
382
|
+
This Markdown mirrors [`JoyDropdownList.dsds.json`](./JoyDropdownList.dsds.json) (DSDS **0.15.2**).
|
|
383
|
+
|
|
384
|
+
| Section here | DSDS block |
|
|
385
|
+
| --- | --- |
|
|
386
|
+
| When to use / not | `use-cases` |
|
|
387
|
+
| Anatomy | `anatomy` |
|
|
388
|
+
| Sizes | `variants` |
|
|
389
|
+
| States | `states` |
|
|
390
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
391
|
+
| Accessibility | `accessibility` |
|
|
392
|
+
| Examples | `sections` |
|
|
393
|
+
| For agents | `agentDocumentBlocks` |
|
|
394
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
395
|
+
| Imports | `imports` in JSON |
|
|
396
|
+
| Token sizing | `design-specifications` in JSON (`--dropdown-item-height`, form-size tokens) |
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"type": "string",
|
|
71
71
|
"declarations": [
|
|
72
72
|
{
|
|
73
|
-
"file": "/
|
|
73
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
74
74
|
"range": [
|
|
75
75
|
2376,
|
|
76
76
|
2387
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
"type": "string | undefined",
|
|
89
89
|
"declarations": [
|
|
90
90
|
{
|
|
91
|
-
"file": "/
|
|
91
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
92
92
|
"range": [
|
|
93
93
|
2396,
|
|
94
94
|
2411
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
"type": "string",
|
|
114
114
|
"declarations": [
|
|
115
115
|
{
|
|
116
|
-
"file": "/
|
|
116
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
117
117
|
"range": [
|
|
118
118
|
2420,
|
|
119
119
|
2434
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"type": "string | undefined",
|
|
132
132
|
"declarations": [
|
|
133
133
|
{
|
|
134
|
-
"file": "/
|
|
134
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
135
135
|
"range": [
|
|
136
136
|
2443,
|
|
137
137
|
2457
|
|
@@ -154,7 +154,7 @@
|
|
|
154
154
|
"type": "boolean | undefined",
|
|
155
155
|
"declarations": [
|
|
156
156
|
{
|
|
157
|
-
"file": "/
|
|
157
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
158
158
|
"range": [
|
|
159
159
|
2516,
|
|
160
160
|
2535
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"type": "boolean | undefined",
|
|
186
186
|
"declarations": [
|
|
187
187
|
{
|
|
188
|
-
"file": "/
|
|
188
|
+
"file": "/Users/pierrereynaud/Documents/Work/design-system/packages/core-vue/src/components/JoyDropdownList/VJoyDropdownList.vue",
|
|
189
189
|
"range": [
|
|
190
190
|
2594,
|
|
191
191
|
2612
|
|
@@ -265,7 +265,7 @@
|
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"name": "dropdown-list-items",
|
|
268
|
-
"description": "Free slot in order to render any kind of component. Can be handy to use it with
|
|
268
|
+
"description": "Free slot in order to render any kind of component. Can be handy to use it with VJoyCheckboxGroup for instance."
|
|
269
269
|
},
|
|
270
270
|
{
|
|
271
271
|
"name": "dropdown-list-item",
|