@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,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyDrawerTrigger
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyDrawerTrigger.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Drawer trigger
|
|
10
|
+
|
|
11
|
+
Use **`VJoyDrawerTrigger`** when a control in the page should open a **`VJoyDrawer`** — without wiring `@click` handlers that call `drawer.show()` yourself.
|
|
12
|
+
|
|
13
|
+
Pass the drawer’s template ref via the `drawer` prop and put the clickable UI (usually a `VJoyButton`) in the default slot. On click, the trigger emits `click`, then awaits `drawer.show()`.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyDrawerTrigger` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoyDrawer docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-overlays-vjoydrawer--docs) (`VJoyDrawerTrigger` is a subcomponent) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyDrawerTrigger.dsds.json`](./JoyDrawerTrigger.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyDrawerTrigger")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a drawer trigger when opening a side panel is tied to an explicit user action on a nearby control — for example “Filters”, “Details”, or “Edit”.
|
|
28
|
+
|
|
29
|
+
It keeps drawer markup (`VJoyDrawer`) separate from the opener while avoiding boilerplate `@click` + `ref.show()` wiring. The default slot accepts any Vue nodes; multiple root nodes each receive the open handler.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Open the drawer from code (route change, API callback, timer) | Call `drawerRef.show()` on the `VJoyDrawer` ref directly |
|
|
36
|
+
| A centered modal overlay | `VJoyDialog` + `VJoyDialogTrigger` (or manual `dialog.show()`) |
|
|
37
|
+
| A mobile bottom sheet | `VJoyBottomSheet` + `VJoyBottomSheetTrigger` |
|
|
38
|
+
| Only a click handler with no drawer | Put `@click` on the control — don’t wrap with `VJoyDrawerTrigger` |
|
|
39
|
+
| Custom trigger chrome with ad-hoc CSS | Still use `VJoyDrawerTrigger` + a Joy button/link in the slot |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
### Anatomy
|
|
46
|
+
|
|
47
|
+
| Part | Required? | What it is |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `drawer` prop | Yes | Template ref to the target `VJoyDrawer` (`InstanceType<typeof VJoyDrawer>`) |
|
|
50
|
+
| Default slot | Yes | One or more trigger elements (e.g. `VJoyButton`, `<button>`, `VJoyLink`) |
|
|
51
|
+
| Click bridge | Yes (internal) | Attaches `@click` to each slotted root vnode, then calls `drawer.show()` |
|
|
52
|
+
|
|
53
|
+
### Default values
|
|
54
|
+
|
|
55
|
+
- `drawer` has **no default** — pass the ref from the parent (may be `null` until `VJoyDrawer` mounts; the component re-reads the ref via an internal computed before opening).
|
|
56
|
+
|
|
57
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
58
|
+
|
|
59
|
+
### API summary (from source)
|
|
60
|
+
|
|
61
|
+
| Kind | Name | Notes |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| Prop | `drawer` | `VJoyDrawer` instance ref; `null` / `undefined` until mounted |
|
|
64
|
+
| Event | `click` | Emitted when the user activates the trigger, **before** `drawer.show()` |
|
|
65
|
+
| Slot | `default` | Trigger UI; each root vnode gets the open click handler |
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Best practices
|
|
70
|
+
|
|
71
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
72
|
+
|
|
73
|
+
### Implementation
|
|
74
|
+
|
|
75
|
+
- **MUST** declare `const drawer = ref<InstanceType<typeof VJoyDrawer> | null>(null)` and bind it with `ref="drawer"` on `VJoyDrawer` and `:drawer="drawer"` on the trigger.
|
|
76
|
+
- **SHOULD** use `VJoyButton` (or another Joy interactive) in the default slot so variant, size, and focus styles stay consistent.
|
|
77
|
+
- **SHOULD** `await` side effects in a parent `@click` listener only when you need logic *before* open; opening is already handled by the trigger.
|
|
78
|
+
- **MUST NOT** invent props — the API is only `drawer` plus the `click` event and default slot.
|
|
79
|
+
- **MUST NOT** add custom CSS on the trigger wrapper — it renders slot content as-is with no chrome.
|
|
80
|
+
|
|
81
|
+
### Do / don’t
|
|
82
|
+
|
|
83
|
+
| Do | Don’t |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| Pair with a sibling `VJoyDrawer` that owns header, body, and footer slots | Put drawer content inside the trigger |
|
|
86
|
+
| Use an accessible button or link in the slot | Use a non-focusable `<div>` as the only trigger |
|
|
87
|
+
| Call `drawer.show()` directly when opening without a click | Wrap programmatic openers in `VJoyDrawerTrigger` |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Accessibility
|
|
92
|
+
|
|
93
|
+
Behaviors depend on the **slotted trigger**, not on extra markup from `VJoyDrawerTrigger`.
|
|
94
|
+
|
|
95
|
+
| Behavior | Expectation |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| Role | Inherited from the slot (e.g. `button` from `VJoyButton`) |
|
|
98
|
+
| Name | **MUST** expose an accessible name on the trigger (`label` on `VJoyButton`, visible text, or `aria-label`) |
|
|
99
|
+
| Keyboard | **MUST** use a native `<button>` or `VJoyButton` / focusable link so Enter and Space activate the drawer |
|
|
100
|
+
| Focus after open | Managed by `VJoyDrawer` (dialog semantics, focus trap) — don’t fight it from the trigger |
|
|
101
|
+
|
|
102
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Examples
|
|
107
|
+
|
|
108
|
+
```vue
|
|
109
|
+
<script setup lang="ts">
|
|
110
|
+
import { ref } from 'vue'
|
|
111
|
+
import { VJoyDrawer, VJoyDrawerTrigger, VJoyButton } from '@maltjoy/core-vue'
|
|
112
|
+
|
|
113
|
+
const drawer = ref<InstanceType<typeof VJoyDrawer> | null>(null)
|
|
114
|
+
</script>
|
|
115
|
+
|
|
116
|
+
<template>
|
|
117
|
+
<VJoyDrawer ref="drawer">
|
|
118
|
+
<template #drawer-header>Filters</template>
|
|
119
|
+
<template #drawer-body>Filter controls go here.</template>
|
|
120
|
+
</VJoyDrawer>
|
|
121
|
+
|
|
122
|
+
<VJoyDrawerTrigger :drawer="drawer" @click="/* optional */">
|
|
123
|
+
<VJoyButton label="Open filters" variant="secondary" />
|
|
124
|
+
</VJoyDrawerTrigger>
|
|
125
|
+
</template>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Manual open (no trigger component):
|
|
129
|
+
|
|
130
|
+
```vue
|
|
131
|
+
<VJoyButton label="Open filters" @click="drawer?.show()" />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyDrawerTrigger` is already global.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Related
|
|
139
|
+
|
|
140
|
+
| Component | Use when |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `VJoyDrawer` | The side panel overlay (header, body, footer, `show` / `hide`) |
|
|
143
|
+
| `VJoyDialogTrigger` | Same open-on-click pattern for `VJoyDialog` |
|
|
144
|
+
| `VJoyBottomSheetTrigger` | Same pattern for `VJoyBottomSheet` on mobile-first flows |
|
|
145
|
+
| `VJoyButton` | Default choice for the trigger slot |
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyDropdown
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyDropdown.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Dropdown
|
|
10
|
+
|
|
11
|
+
Use **`VJoyDropdown`** when the user needs a contextual panel of actions or choices that opens from a trigger — overflow menus, filter pickers, or compact option lists.
|
|
12
|
+
|
|
13
|
+
It composes a focusable trigger (`dropdown-button` slot) with an overlay list (`VJoyDropdownList` internally). You can drive a predefined `options` array, or inject fully custom content through slots. Positioning (`direction`, `justify`), sizing, and click-outside behavior are built in.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyDropdown` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy components](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoyDropdown docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-forms-vjoydropdown--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyDropdown.dsds.json`](./JoyDropdown.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyDropdown")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a dropdown when a compact trigger should reveal a short list of choices or contextual actions without leaving the current view — for example a row overflow menu, a filter chip that opens checkboxes, or a button that reveals cities to pick from.
|
|
28
|
+
|
|
29
|
+
It fits patterns where the trigger is custom (button, icon button, filter chip) and the panel content may be a simple option list **or** richer form controls in the header, body, or footer slots.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| A labeled form field for single selection (native select semantics) | `VJoySelect` |
|
|
36
|
+
| Persistent navigation menus (sidebar, app nav) | `VJoyMenu` + `VJoyMenuItem` |
|
|
37
|
+
| Short explanatory text on hover / focus only | `VJoyTooltip` |
|
|
38
|
+
| A standalone scrollable list without open/close trigger logic | `VJoyDropdownList` |
|
|
39
|
+
| Custom `position: absolute` panels with ad-hoc markup | Still use `VJoyDropdown` + Joy tokens — don’t reinvent overlay behavior |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
### Anatomy
|
|
46
|
+
|
|
47
|
+
| Part | Required? | What it is |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Trigger wrapper | Yes | Click target wrapping the `dropdown-button` slot |
|
|
50
|
+
| `dropdown-button` slot | Yes | The visible control that opens the panel — **must be focusable** (`VJoyButton`, `VJoyIconButton`, etc.) |
|
|
51
|
+
| Dropdown list | Yes (when open) | `VJoyDropdownList` panel with options or custom slots |
|
|
52
|
+
| `dropdown-header` slot | No | Content above the list (search field, title, filters) |
|
|
53
|
+
| `dropdown-items` slot | No | Fully custom body — e.g. `VJoyCheckboxGroup` |
|
|
54
|
+
| `dropdown-item` slot | No | Scoped slot to customize each option row when using `options` |
|
|
55
|
+
| `dropdown-footer` slot | No | Actions below the list (Apply, Submit) |
|
|
56
|
+
| Click-outside layer | Yes | Closes the panel when clicking outside trigger + list |
|
|
57
|
+
|
|
58
|
+
### Default values
|
|
59
|
+
|
|
60
|
+
- `direction` defaults to **`down`**
|
|
61
|
+
- `justify` defaults to **`left`**
|
|
62
|
+
- `size` defaults to **`medium`** (list item density)
|
|
63
|
+
- `closeOnSelect` defaults to **`true`**
|
|
64
|
+
- `width` defaults to **`300`** (px max-width of the list)
|
|
65
|
+
- `height` defaults to **`300`** (px max-height of the scrollable items area)
|
|
66
|
+
- `dropdownGap` defaults to **`12`** (px between trigger and list)
|
|
67
|
+
- `modelValue` defaults to **`""`**
|
|
68
|
+
- `options` defaults to **`[]`**
|
|
69
|
+
- `reset` defaults to **`false`**
|
|
70
|
+
- `appendTo` defaults to **`"body"`**
|
|
71
|
+
- `disableTeleport` defaults to **`false`**
|
|
72
|
+
|
|
73
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Variants
|
|
78
|
+
|
|
79
|
+
`VJoyDropdown` has no visual emphasis variants. Choose **positioning** based on available space and reading direction.
|
|
80
|
+
|
|
81
|
+
### Direction: `down` (default)
|
|
82
|
+
|
|
83
|
+
Use when there is room below the trigger — the common case for toolbars, table rows, and form fields.
|
|
84
|
+
|
|
85
|
+
Avoid when the trigger sits near the bottom of the viewport; prefer `up` so the list stays visible.
|
|
86
|
+
|
|
87
|
+
### Direction: `up`
|
|
88
|
+
|
|
89
|
+
Use when the trigger is low on the page or inside a bottom-aligned container (drawer footers, bottom toolbars).
|
|
90
|
+
|
|
91
|
+
Pair with a chevron-up icon on the trigger when the open direction helps users predict behavior.
|
|
92
|
+
|
|
93
|
+
### Justify: `left` (default)
|
|
94
|
+
|
|
95
|
+
Align the list’s leading edge with the trigger’s leading edge. Default for LTR layouts and left-aligned controls.
|
|
96
|
+
|
|
97
|
+
### Justify: `right`
|
|
98
|
+
|
|
99
|
+
Align the list’s trailing edge with the trigger’s trailing edge. Use for right-aligned icon buttons (overflow / kebab menus).
|
|
100
|
+
|
|
101
|
+
### Not in the API: center alignment
|
|
102
|
+
|
|
103
|
+
Some design explorations show centered panels. **`justify` only supports `left` and `right`** (`TJoyDropdownJustify`). Do not invent `center` — adjust trigger placement or confirm with design before extending the API.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Sizes
|
|
108
|
+
|
|
109
|
+
`size` controls **list item density** inside `VJoyDropdownList`, not the trigger button. Size the trigger separately on `VJoyButton` / `VJoyIconButton`.
|
|
110
|
+
|
|
111
|
+
| Size | Use when |
|
|
112
|
+
| --- | --- |
|
|
113
|
+
| `medium` | Default product menus and option lists |
|
|
114
|
+
| `small` | Dense toolbars, filter bars, tables |
|
|
115
|
+
| `xsmall` | Very compact UI (match trigger `xsmall` buttons) |
|
|
116
|
+
|
|
117
|
+
Heights and padding come from Joy list tokens inside the component — **don’t hardcode item heights** in apps.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## States
|
|
122
|
+
|
|
123
|
+
| State | What the user sees / can do |
|
|
124
|
+
| --- | --- |
|
|
125
|
+
| Closed | Only the trigger is visible; list is not in the DOM |
|
|
126
|
+
| Open | List animates in (`direction` controls animation); `dropdown:open` fires |
|
|
127
|
+
| Hiding | Close animation plays (~300 ms) before the list unmounts; `dropdown:hide` fires after |
|
|
128
|
+
| Option selected | Matching `modelValue` row shows selected styling (`aria-selected="true"`) |
|
|
129
|
+
| Option disabled | Row is non-interactive (`disabled: true` on the option) |
|
|
130
|
+
| Multi-step panel | With `close-on-select="false"`, list stays open while user edits checkboxes; footer Submit closes manually |
|
|
131
|
+
| Reset available | With `reset`, a reset CTA appears in the list header; fires `dropdown:reset` (does not clear models automatically) |
|
|
132
|
+
|
|
133
|
+
Clicking the trigger toggles open/closed. Clicking outside, pressing **Escape** or **Tab** in the list (options mode), or resizing the window closes the panel.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Best practices
|
|
138
|
+
|
|
139
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
140
|
+
|
|
141
|
+
### Trigger and hierarchy
|
|
142
|
+
|
|
143
|
+
- **MUST** provide a focusable element in `dropdown-button` (button or icon button — not a bare `<div>`).
|
|
144
|
+
- **MUST** set an accessible name on icon-only triggers (`label` / `aria-label` on `VJoyIconButton`).
|
|
145
|
+
- **SHOULD** use `VJoyIconButton` with a dots / overflow icon for row-level action menus.
|
|
146
|
+
- **SHOULD** use `VJoyButton` with a chevron when the trigger also communicates the open direction.
|
|
147
|
+
|
|
148
|
+
### Options and selection
|
|
149
|
+
|
|
150
|
+
- **MUST** give every option a unique `id` (and optional `value` when `modelValue` should differ from `id`).
|
|
151
|
+
- **MUST** bind `v-model` when selection should persist and reflect the chosen option.
|
|
152
|
+
- **SHOULD** set `close-on-select="false"` when the panel contains multi-select controls (`VJoyCheckboxGroup`) and needs an explicit Apply / Submit in `dropdown-footer`.
|
|
153
|
+
- **SHOULD** handle `dropdown:reset` when `reset` is true — the component does not clear parent state by itself.
|
|
154
|
+
|
|
155
|
+
### Layout and positioning
|
|
156
|
+
|
|
157
|
+
- **SHOULD** set `width` to fit the longest expected label (or search field) without truncation.
|
|
158
|
+
- **SHOULD** set `height` when the option count can overflow the viewport — the items area scrolls internally.
|
|
159
|
+
- **SHOULD** pick `direction="up"` when placing dropdowns near the bottom edge of drawers, modals, or the viewport.
|
|
160
|
+
- **MUST NOT** hardcode `z-index` on the list — Joy uses `--joy-core-z-index-dropdown`.
|
|
161
|
+
|
|
162
|
+
### Implementation
|
|
163
|
+
|
|
164
|
+
- **MUST** use only documented prop / enum values (MCP / `JoyDropdown.types.ts`).
|
|
165
|
+
- **MUST** use Joy tokens and slots — no custom overlay chrome CSS.
|
|
166
|
+
- **MUST NOT** invent props (`align`, `open`, `variant`, …).
|
|
167
|
+
- **MUST NOT** mount `VJoyDropdownList` directly when you need open/close, click-outside, and trigger focus management — use `VJoyDropdown`.
|
|
168
|
+
|
|
169
|
+
### Do / don’t
|
|
170
|
+
|
|
171
|
+
| Do | Don’t |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `VJoyDropdown` + slots for custom filter panels | Raw `position: absolute` div menus |
|
|
174
|
+
| `reset` + `@dropdown:reset` handler | Expect `reset` to clear `v-model` automatically |
|
|
175
|
+
| `close-on-select="false"` + footer Submit for multi-select | Let each checkbox click close the panel |
|
|
176
|
+
| `aria-label` on icon-only triggers | Icon button with no accessible name |
|
|
177
|
+
| Match list `size` to trigger density | Mismatched xsmall trigger + medium list items |
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Content
|
|
182
|
+
|
|
183
|
+
Dropdowns surface user-facing copy on the trigger, in each option, and sometimes in header/footer actions. Keep labels scannable — users open the panel to decide quickly.
|
|
184
|
+
|
|
185
|
+
### Label formula
|
|
186
|
+
|
|
187
|
+
**Trigger (button with text):** **noun or noun phrase** describing what opens — e.g. `Cities`, `Filters`, `More actions`.
|
|
188
|
+
|
|
189
|
+
**Trigger (icon-only):** short **verb + object** in the accessible name — e.g. `Open menu`, `Show cities`, `Filter results`.
|
|
190
|
+
|
|
191
|
+
**Options:** **plain noun or short phrase** — e.g. `Paris`, `Delete project`, `Export as PDF`. Use parallel grammar across siblings.
|
|
192
|
+
|
|
193
|
+
**Reset CTA:** **verb + scope** — e.g. `Reset filter`, `Clear selection`. Default fallback in code is `Reset` if `reset-label` is omitted.
|
|
194
|
+
|
|
195
|
+
**Footer actions:** same rules as `VJoyButton` — infinitive verb + noun (`Apply filters`, `Save changes`).
|
|
196
|
+
|
|
197
|
+
### Writing rules
|
|
198
|
+
|
|
199
|
+
| Strength | Rule |
|
|
200
|
+
| --- | --- |
|
|
201
|
+
| **MUST** | Icon-only triggers have an accessible name (`label` / `aria-label`) |
|
|
202
|
+
| **MUST** | Option labels stay concise; long text truncates with `title` tooltip on the row |
|
|
203
|
+
| **MUST** | Disabled options include context elsewhere if the reason isn’t obvious |
|
|
204
|
+
| **MUST NOT** | Vague triggers — ❌ `Click here`, ❌ `Options` alone when a specific noun fits |
|
|
205
|
+
| **MUST NOT** | Duplicate the trigger label on every option when the list context is clear |
|
|
206
|
+
| **SHOULD** | ≤ 3 words per option where possible |
|
|
207
|
+
| **SHOULD** | Use sentence case for multi-word labels |
|
|
208
|
+
| **SHOULD** | Name destructive actions explicitly — ✅ `Delete project` · ❌ `Delete` alone in a mixed menu |
|
|
209
|
+
| **SHOULD** | Localize `reset-label` — do not rely on the English default `Reset` in product UI |
|
|
210
|
+
|
|
211
|
+
### Patterns
|
|
212
|
+
|
|
213
|
+
**Overflow / kebab menu** — Icon trigger: `Open menu` or `More actions for {item name}`. Options are imperative verbs: `Edit`, `Duplicate`, `Archive`. Destructive action last with explicit wording.
|
|
214
|
+
|
|
215
|
+
**Filter chip dropdown** — Trigger shows current filter state: `Continent: Europe` or `Status (2)`. Header may hold search. Footer holds `Apply filters` when `close-on-select` is false.
|
|
216
|
+
|
|
217
|
+
**Searchable list** — Put `VJoyInput` in `dropdown-header`. Filter `options` in the parent; keep the placeholder specific (`Search cities`).
|
|
218
|
+
|
|
219
|
+
**Multi-select with checkboxes** — Checkbox group label describes the set (`Choose options…`). Footer `Submit` or `Apply` confirms; don’t close on each toggle.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Accessibility
|
|
224
|
+
|
|
225
|
+
Behaviors (what must happen), not slogans.
|
|
226
|
+
|
|
227
|
+
| Behavior | Expectation |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| Trigger name | Visible text or `aria-label` / `label` on icon buttons — required when there is no visible text |
|
|
230
|
+
| List labeling | On mount, the first child of the trigger receives `generatedId`; the listbox uses `aria-labelledby` with that id |
|
|
231
|
+
| Role (options mode) | `role="listbox"` on the `<ul>`; each row `role="option"` with `aria-selected` / `aria-disabled` |
|
|
232
|
+
| Focus on open | First focusable field inside the list receives focus when the panel opens |
|
|
233
|
+
| Focus on close | Focus returns to the element that opened the panel |
|
|
234
|
+
| Click outside | Clicking outside trigger + list closes the panel |
|
|
235
|
+
| Keyboard (options list) | **ArrowUp** / **ArrowDown** move focus; **Home** / **End** jump; **Enter** selects; **Escape** / **Tab** close |
|
|
236
|
+
| Keyboard (custom slot body) | When using `dropdown-items` without `options`, list keyboard patterns are not enforced — manage focus inside custom content |
|
|
237
|
+
| Window resize | Panel closes to avoid stranded overlays |
|
|
238
|
+
| Disabled options | `aria-disabled="true"`; not selectable |
|
|
239
|
+
|
|
240
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Examples
|
|
245
|
+
|
|
246
|
+
```vue
|
|
247
|
+
<script setup lang="ts">
|
|
248
|
+
import { ref } from 'vue'
|
|
249
|
+
import { VJoyDropdown, VJoyButton, VJoyIconButton } from '@maltjoy/core-vue'
|
|
250
|
+
import type { TJoyDropdownOption } from '@maltjoy/core-vue'
|
|
251
|
+
|
|
252
|
+
const city = ref('')
|
|
253
|
+
const cities: TJoyDropdownOption[] = [
|
|
254
|
+
{ id: 'paris', label: 'Paris' },
|
|
255
|
+
{ id: 'toulouse', label: 'Toulouse' },
|
|
256
|
+
{ id: 'bordeaux', label: 'Bordeaux' },
|
|
257
|
+
]
|
|
258
|
+
</script>
|
|
259
|
+
|
|
260
|
+
<template>
|
|
261
|
+
<!-- Predefined options + icon trigger -->
|
|
262
|
+
<VJoyDropdown v-model="city" :options="cities" width="200" justify="left">
|
|
263
|
+
<template #dropdown-button>
|
|
264
|
+
<VJoyIconButton variant="secondary" icon="pen" size="xsmall" label="Show cities" />
|
|
265
|
+
</template>
|
|
266
|
+
</VJoyDropdown>
|
|
267
|
+
|
|
268
|
+
<!-- Text trigger, opens upward near page bottom -->
|
|
269
|
+
<VJoyDropdown v-model="city" :options="cities" direction="up" width="200">
|
|
270
|
+
<template #dropdown-button>
|
|
271
|
+
<VJoyButton variant="secondary" size="xsmall" icon="chevron-up" icon-position="right">
|
|
272
|
+
Direction up
|
|
273
|
+
</VJoyButton>
|
|
274
|
+
</template>
|
|
275
|
+
</VJoyDropdown>
|
|
276
|
+
|
|
277
|
+
<!-- Custom body: checkboxes + footer submit -->
|
|
278
|
+
<VJoyDropdown :close-on-select="false" size="small" justify="right" width="200" height="200">
|
|
279
|
+
<template #dropdown-button>
|
|
280
|
+
<VJoyButton variant="secondary" size="xsmall" icon="chevron-down" icon-position="right">
|
|
281
|
+
Filters
|
|
282
|
+
</VJoyButton>
|
|
283
|
+
</template>
|
|
284
|
+
<template #dropdown-items>
|
|
285
|
+
<!-- VJoyCheckboxGroup or other controls -->
|
|
286
|
+
</template>
|
|
287
|
+
<template #dropdown-footer>
|
|
288
|
+
<VJoyButton size="xxsmall" variant="main">Apply filters</VJoyButton>
|
|
289
|
+
</template>
|
|
290
|
+
</VJoyDropdown>
|
|
291
|
+
|
|
292
|
+
<!-- Filter bar reset pattern -->
|
|
293
|
+
<VJoyDropdown
|
|
294
|
+
reset
|
|
295
|
+
reset-label="Reset filter"
|
|
296
|
+
:close-on-select="false"
|
|
297
|
+
@dropdown:reset="() => { /* clear parent filter state */ }"
|
|
298
|
+
>
|
|
299
|
+
<template #dropdown-button>
|
|
300
|
+
<VJoyButton variant="secondary" size="xsmall">Continent</VJoyButton>
|
|
301
|
+
</template>
|
|
302
|
+
<template #dropdown-items>
|
|
303
|
+
<!-- filter controls -->
|
|
304
|
+
</template>
|
|
305
|
+
</VJoyDropdown>
|
|
306
|
+
</template>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyDropdown` is already global.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Related
|
|
314
|
+
|
|
315
|
+
| Component | Use when |
|
|
316
|
+
| --- | --- |
|
|
317
|
+
| `VJoyDropdownList` | You need only the list UI, without trigger / overlay lifecycle |
|
|
318
|
+
| `VJoySelect` | Labeled single-select form field with integrated styling |
|
|
319
|
+
| `VJoyMenu` | Persistent navigation menus with `VJoyMenuItem` |
|
|
320
|
+
| `VJoyButton` / `VJoyIconButton` | Triggers inside `dropdown-button` |
|
|
321
|
+
| `VJoyCheckboxGroup` | Multi-select content inside `dropdown-items` |
|
|
322
|
+
| `VJoyFilterBar` | Composed filter UI that embeds `VJoyDropdown` patterns |
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## For agents
|
|
327
|
+
|
|
328
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyDropdown.dsds.json`](./JoyDropdown.dsds.json).
|
|
329
|
+
|
|
330
|
+
### Pick the right component
|
|
331
|
+
|
|
332
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
333
|
+
| --- | --- | --- |
|
|
334
|
+
| Overflow / contextual menu from a trigger | `VJoyDropdown` | Custom absolutely positioned `<div>` menus |
|
|
335
|
+
| Labeled form single-select | `VJoySelect` | `VJoyDropdown` mimicking a native select |
|
|
336
|
+
| App / sidebar navigation | `VJoyMenu` | `VJoyDropdown` for persistent nav |
|
|
337
|
+
| List panel only (no trigger) | `VJoyDropdownList` | `VJoyDropdown` with empty `dropdown-button` |
|
|
338
|
+
| Multi-select filters with Apply | `VJoyDropdown` + `close-on-select="false"` + footer button | `close-on-select` default `true` on checkbox panels |
|
|
339
|
+
| Icon-only trigger | `VJoyIconButton` in `dropdown-button` | Bare icon without `label` / `aria-label` |
|
|
340
|
+
|
|
341
|
+
### Hard rules
|
|
342
|
+
|
|
343
|
+
1. **MUST** call `joy_get_component_info("VJoyDropdown")` and/or `joy_get_component_types("VJoyDropdown")` before generating props.
|
|
344
|
+
2. **MUST** always provide the `dropdown-button` slot with a focusable Joy trigger component.
|
|
345
|
+
3. **MUST** set an accessible name when the trigger has no visible text.
|
|
346
|
+
4. **MUST** use `TJoyDropdownOption` shape for `options` (`id`, `label`, optional `value`, `href`, `disabled`, `checked`).
|
|
347
|
+
5. **MUST** wire `@dropdown:reset` when `reset` is true — reset does not mutate parent state.
|
|
348
|
+
6. **MUST NOT** invent props (`variant`, `open`, `align`, `center`, …) or enum values outside types.
|
|
349
|
+
7. **MUST NOT** override `.joy-dropdown` / `.joy-dropdown__list` positioning, `z-index`, or animation in product CSS.
|
|
350
|
+
8. **SHOULD** set `direction="up"` when the story/layout places the trigger near the bottom of the viewport.
|
|
351
|
+
9. **SHOULD** use `justify="right"` for right-aligned overflow icon buttons.
|
|
352
|
+
10. **SHOULD** pass `width` / `height` as strings (pixel values without units in the prop value, e.g. `width="200"`).
|
|
353
|
+
|
|
354
|
+
### Slot map
|
|
355
|
+
|
|
356
|
+
| Slot | Maps to | Typical content |
|
|
357
|
+
| --- | --- | --- |
|
|
358
|
+
| `dropdown-button` | Trigger | `VJoyButton`, `VJoyIconButton` |
|
|
359
|
+
| `dropdown-header` | List header | `VJoyInput` search, title text |
|
|
360
|
+
| `dropdown-items` | Custom list body | `VJoyCheckboxGroup`, freeform markup |
|
|
361
|
+
| `dropdown-item` | Option row template | Custom label / link per `option` |
|
|
362
|
+
| `dropdown-footer` | List footer | `VJoyButton` Apply / Submit |
|
|
363
|
+
|
|
364
|
+
### Checklist
|
|
365
|
+
|
|
366
|
+
- [ ] Correct component (`VJoyDropdown` or named alternative)
|
|
367
|
+
- [ ] Props / enums from MCP or `JoyDropdown.types.ts` only
|
|
368
|
+
- [ ] `dropdown-button` slot present with focusable trigger
|
|
369
|
+
- [ ] Icon-only trigger has accessible name
|
|
370
|
+
- [ ] `v-model` bound when single selection from `options` should persist
|
|
371
|
+
- [ ] `close-on-select` set intentionally for multi-step / multi-select panels
|
|
372
|
+
- [ ] `dropdown:reset` handler when `reset` is true
|
|
373
|
+
- [ ] No invented props / no custom dropdown chrome CSS
|
|
374
|
+
- [ ] `direction` / `justify` match viewport space and trigger alignment
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## DSDS mapping
|
|
379
|
+
|
|
380
|
+
This Markdown mirrors [`JoyDropdown.dsds.json`](./JoyDropdown.dsds.json) (DSDS **0.15.2**).
|
|
381
|
+
|
|
382
|
+
| Section here | DSDS block |
|
|
383
|
+
| --- | --- |
|
|
384
|
+
| When to use / not | `use-cases` |
|
|
385
|
+
| Anatomy | `anatomy` |
|
|
386
|
+
| Direction / justify / sizes | `variants` |
|
|
387
|
+
| States | `states` |
|
|
388
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
389
|
+
| Accessibility | `accessibility` |
|
|
390
|
+
| Examples | `sections` |
|
|
391
|
+
| For agents | `agentDocumentBlocks` |
|
|
392
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
393
|
+
| Imports | `imports` in JSON |
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
74
74
|
"range": [
|
|
75
75
|
183,
|
|
76
76
|
194
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
92
92
|
"range": [
|
|
93
93
|
199,
|
|
94
94
|
214
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
117
117
|
"range": [
|
|
118
118
|
219,
|
|
119
119
|
233
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
135
135
|
"range": [
|
|
136
136
|
238,
|
|
137
137
|
252
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
158
158
|
"range": [
|
|
159
159
|
295,
|
|
160
160
|
314
|
|
@@ -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/JoyDropdown/JoyDropdown.types.ts",
|
|
189
189
|
"range": [
|
|
190
190
|
357,
|
|
191
191
|
375
|