@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,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyBlockSkeleton
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyBlockSkeleton.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Block skeleton
|
|
10
|
+
|
|
11
|
+
Use **`VJoyBlockSkeleton`** when you need a **generic rectangular or circular placeholder** while content is loading — for example image areas, text blocks, cards, or avatar circles.
|
|
12
|
+
|
|
13
|
+
Set `width` and `height` to approximate the final layout, use `circle` for round placeholders, and compose multiple instances inside `VJoyWrapper` for responsive skeleton rows and columns.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyBlockSkeleton` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy components](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/) |
|
|
19
|
+
| **Storybook** | [VJoyBlockSkeleton docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-skeletons-vjoyblockskeleton--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyBlockSkeleton.dsds.json`](./JoyBlockSkeleton.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyBlockSkeleton")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a block skeleton to show **layout-preserving placeholders** while async data or media loads. Typical jobs: card thumbnails, profile header blocks, list row shapes, or any free-form area where the final content is not a standard form field.
|
|
28
|
+
|
|
29
|
+
The shimmer animation signals activity without blocking interaction elsewhere on the page.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Form label + input-shaped placeholder | `VJoyFormFieldSkeleton` |
|
|
36
|
+
| Button or inline processing indicator | `VJoySpinner` |
|
|
37
|
+
| Full-page or section-blocking load | `VJoyScreenLoader` |
|
|
38
|
+
| Custom `<div>` with ad-hoc gray boxes and CSS animation | Still use `VJoyBlockSkeleton` + Joy tokens — don't reinvent skeleton chrome |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## How it works
|
|
43
|
+
|
|
44
|
+
### Anatomy
|
|
45
|
+
|
|
46
|
+
| Part | Required? | What it is |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| Block container | Yes | Single `<div>` with class `joy-block-skeleton` |
|
|
49
|
+
| Shimmer surface | Yes | Neutral gradient background with `shine` animation (1.5s loop) |
|
|
50
|
+
| Circle modifier | No | Class `joy-block-skeleton--circle` when `circle` is true (`border-radius: 50%`) |
|
|
51
|
+
|
|
52
|
+
There are **no slots, events, or exposed methods** — sizing and shape are controlled only through props.
|
|
53
|
+
|
|
54
|
+
### Default values
|
|
55
|
+
|
|
56
|
+
- `circle` defaults to **`false`** (rounded rectangle via `--joy-core-radius-1`)
|
|
57
|
+
- `width` defaults to **unset** — block stretches to **`width: 100%`** of its container
|
|
58
|
+
- `height` defaults to **`30px`** via internal `--block-skeleton-height` when not passed as a prop
|
|
59
|
+
|
|
60
|
+
Props: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
61
|
+
|
|
62
|
+
### Sizing and layout
|
|
63
|
+
|
|
64
|
+
- **`width`** — applied as inline `max-width` (any CSS unit: `px`, `%`, `rem`, …).
|
|
65
|
+
- **`height`** — applied as inline `height` (overrides the 30px default).
|
|
66
|
+
- **`circle`** — renders a circle; **set both `width` and `height` to the same value** (e.g. `60px`) so the shape is round.
|
|
67
|
+
- **`VJoyWrapper`** — when used inside a wrapper, the component auto-applies `flex: 1 0` (or `flex: 1 0 {width}` when `width` is set) so skeletons share horizontal space evenly.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Best practices
|
|
72
|
+
|
|
73
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
74
|
+
|
|
75
|
+
### Layout and shape
|
|
76
|
+
|
|
77
|
+
- **SHOULD** match skeleton dimensions to the expected loaded content to reduce layout shift.
|
|
78
|
+
- **MUST** set explicit `width` and `height` when using `circle` — equal values for a true circle.
|
|
79
|
+
- **SHOULD** compose multiple block skeletons inside `VJoyWrapper` for rows, columns, and mixed-width layouts.
|
|
80
|
+
- **SHOULD** prefer `VJoyFormFieldSkeleton` when the placeholder should look like a form field (label + input proportions).
|
|
81
|
+
|
|
82
|
+
### Implementation
|
|
83
|
+
|
|
84
|
+
- **MUST** use only documented props from MCP (`circle`, `width`, `height`).
|
|
85
|
+
- **MUST** use Joy tokens — no custom `.joy-block-skeleton` chrome overrides in product CSS.
|
|
86
|
+
- **MUST NOT** invent props (`size`, `variant`, `color`, …).
|
|
87
|
+
- **MUST NOT** use block skeletons as interactive elements or click targets.
|
|
88
|
+
|
|
89
|
+
### Do / don't
|
|
90
|
+
|
|
91
|
+
| Do | Don't |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| Size blocks to mirror final content | One generic skeleton for every layout |
|
|
94
|
+
| `circle` + equal `width` / `height` for avatars | `circle` without fixed dimensions |
|
|
95
|
+
| `VJoyFormFieldSkeleton` for form-shaped placeholders | Block skeletons mimicking every input size |
|
|
96
|
+
| `VJoyWrapper` for multi-block layouts | Manual flex CSS duplicating wrapper behavior |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Accessibility
|
|
101
|
+
|
|
102
|
+
`VJoyBlockSkeleton` is **decorative loading chrome** — it is not interactive and does not convey status to assistive technology on its own.
|
|
103
|
+
|
|
104
|
+
| Behavior | Expectation |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| `aria-hidden` | Always **`true`** — skeleton is hidden from the accessibility tree |
|
|
107
|
+
| Role | No semantic role; purely visual placeholder |
|
|
108
|
+
| Keyboard / focus | Not focusable — no tab stop |
|
|
109
|
+
| Loading announcement | **MUST** be handled by the parent region (e.g. `aria-busy="true"` on the loading container, or a visible loading label) — the skeleton alone does not announce "loading" |
|
|
110
|
+
| Motion | Continuous shimmer animation — respect `prefers-reduced-motion` at the app level if product policy requires it |
|
|
111
|
+
|
|
112
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
```vue
|
|
119
|
+
<script setup lang="ts">
|
|
120
|
+
import { VJoyBlockSkeleton, VJoyWrapper } from '@maltjoy/core-vue'
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<!-- Single content block -->
|
|
125
|
+
<VJoyBlockSkeleton width="100%" height="100px" />
|
|
126
|
+
|
|
127
|
+
<!-- Avatar placeholder -->
|
|
128
|
+
<VJoyBlockSkeleton circle width="60px" height="60px" />
|
|
129
|
+
|
|
130
|
+
<!-- Row of mixed-width blocks -->
|
|
131
|
+
<VJoyWrapper>
|
|
132
|
+
<VJoyBlockSkeleton height="48px" />
|
|
133
|
+
<VJoyBlockSkeleton width="120px" height="48px" />
|
|
134
|
+
</VJoyWrapper>
|
|
135
|
+
</template>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyBlockSkeleton` is already global.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Related
|
|
143
|
+
|
|
144
|
+
| Component | Use when |
|
|
145
|
+
| --- | --- |
|
|
146
|
+
| `VJoyFormFieldSkeleton` | Placeholder shaped like a form field (with `size` presets) |
|
|
147
|
+
| `VJoyWrapper` | Flex layout for groups of skeletons or mixed content |
|
|
148
|
+
| `VJoySpinner` | Inline or button loading indicator |
|
|
149
|
+
| `VJoyScreenLoader` | Full-screen or overlay loading state |
|
|
150
|
+
| `VJoyAvatar` | Loaded avatar to replace a circular block skeleton |
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyBottomSheet
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyBottomSheet.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Bottom Sheet
|
|
10
|
+
|
|
11
|
+
Use **`VJoyBottomSheet`** when you need a mobile-first modal surface that slides up from the bottom — action menus, contextual choices, or focused content that must be dismissed before returning to the page behind it.
|
|
12
|
+
|
|
13
|
+
It teleports to `body` by default, supports drag-to-dismiss on the header handle, click-outside dismiss, customizable content and footer slots, and imperative `show()` / `hide()` via a template ref. Use **`VJoyBottomSheetTrigger`** when a declarative open trigger is simpler than wiring refs yourself.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyBottomSheet` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [ZeroHeight — Bottom Sheet](https://zeroheight.com/6bf479565/) |
|
|
19
|
+
| **Storybook** | [VJoyBottomSheet docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-overlays-vjoybottomsheet--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyBottomSheet.dsds.json`](./JoyBottomSheet.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyBottomSheet")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a bottom sheet when the user must focus on a short set of options or actions on **mobile or narrow viewports** — for example choosing a filter, confirming a lightweight decision, or presenting supplementary content without leaving the current screen.
|
|
28
|
+
|
|
29
|
+
Bottom sheets block interaction with the underlying page (overlay + scroll lock) and should be dismissed explicitly, via the default close CTA, drag-down, or click outside.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Centered modal on desktop (confirmations, forms, announcements) | `VJoyDialog` |
|
|
36
|
+
| Edge panel with long or exploratory content | `VJoyDrawer` |
|
|
37
|
+
| Dropdown / action list anchored to a control | `VJoyDropdown` or `VJoyMenu` |
|
|
38
|
+
| Brief non-blocking feedback after an action | `pushVJoySnackbar` |
|
|
39
|
+
| Inline expandable section on the same page | Page layout or `VJoyAccordion` — not a bottom sheet |
|
|
40
|
+
| Custom fixed panel with ad-hoc CSS | Still use `VJoyBottomSheet` + Joy tokens — don't reinvent overlay chrome |
|
|
41
|
+
|
|
42
|
+
> **Docs only:** `frozen` keeps the sheet open and skips animation timing (Storybook). Do not use in production.
|
|
43
|
+
|
|
44
|
+
> **Custom footer:** when you replace the default `bottom-sheet-cta` slot, you **must** call `hide()` yourself on dismiss actions — the sheet will not close automatically and `bottom-sheet:close` will not fire until `hide()` runs.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
### Anatomy
|
|
51
|
+
|
|
52
|
+
| Part | Required? | What it is |
|
|
53
|
+
| --- | --- | --- |
|
|
54
|
+
| Overlay | Yes | Semi-transparent backdrop (`joy-bottom-sheet-overlay`); click outside container dismisses |
|
|
55
|
+
| Container (`joy-bottom-sheet-container`) | Yes | White card sliding from bottom; top radius 20px; elevation shadow |
|
|
56
|
+
| Header (`joy-bottom-sheet_header`) | Yes | Drag handle bar; touch/mouse drag-to-dismiss target |
|
|
57
|
+
| Content (`bottom-sheet-content` slot) | Yes (content) | Main body; scrolls when taller than ~60vh |
|
|
58
|
+
| Footer (`bottom-sheet-cta` slot) | No* | Default full-width `VJoyButton` close CTA (*provided by default) |
|
|
59
|
+
|
|
60
|
+
### Default values
|
|
61
|
+
|
|
62
|
+
- `open` defaults to **`false`**
|
|
63
|
+
- `closeLabel` defaults to **`"Close"`**
|
|
64
|
+
- `teleport` defaults to **`"body"`**
|
|
65
|
+
- `preventBodyScroll` defaults to **`true`** (locks page scroll while open)
|
|
66
|
+
- `frozen` defaults to **`false`**
|
|
67
|
+
|
|
68
|
+
Props, events, slots, and exposed methods: use Joy MCP (`joy_get_component_info`) or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
69
|
+
|
|
70
|
+
### Open / close lifecycle
|
|
71
|
+
|
|
72
|
+
1. **Open** — set `open` to `true`, or call `show()` on the exposed ref. Body scroll is prevented when `preventBodyScroll` is true.
|
|
73
|
+
2. **Dismiss** — default close CTA, drag header down past **75px**, or click outside the container. All paths call `hide()` unless `frozen` is true.
|
|
74
|
+
3. **Close event** — `bottom-sheet:close` fires after the **300ms** leave animation completes and the sheet is removed from the DOM.
|
|
75
|
+
4. **Controlled mode** — `open` prop is watched; setting it to `false` triggers the same hide animation as imperative `hide()`.
|
|
76
|
+
|
|
77
|
+
`show()` and `hide()` are synchronous. There is no `bottom-sheet:open` event.
|
|
78
|
+
|
|
79
|
+
### Drag-to-dismiss
|
|
80
|
+
|
|
81
|
+
The header handle listens for mouse and touch events. While dragging, the container follows the pointer (`translateY`). Releasing after more than **75px** downward travel dismisses the sheet; smaller movements snap back.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## States
|
|
86
|
+
|
|
87
|
+
| State | What the user sees / can do |
|
|
88
|
+
| --- | --- |
|
|
89
|
+
| Closed | Not rendered (`v-if="isOpen"`); no overlay |
|
|
90
|
+
| Opening | Slide-up animation (300ms) |
|
|
91
|
+
| Open | Overlay visible; body scroll locked (default); interact with content and footer |
|
|
92
|
+
| Dragging | Container follows finger/cursor; transition disabled on container |
|
|
93
|
+
| Closing | Slide-down animation (300ms); `bottom-sheet:close` fires after timer |
|
|
94
|
+
| Frozen (`frozen`) | Open state locked — `hide()` is a no-op (docs / Storybook) |
|
|
95
|
+
| Scrollable content | Body scrolls inside content area when taller than `max-height: 60vh` |
|
|
96
|
+
|
|
97
|
+
Prefer wiring async work on custom footer buttons with `loading` on `VJoyButton` instead of leaving the sheet in an ambiguous half-open state.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Best practices
|
|
102
|
+
|
|
103
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
104
|
+
|
|
105
|
+
### Hierarchy and placement
|
|
106
|
+
|
|
107
|
+
- **SHOULD** reserve bottom sheets for **mobile-first** or narrow layouts; prefer `VJoyDialog` for desktop modal decisions.
|
|
108
|
+
- **SHOULD** use one bottom sheet per focused task — don't stack unrelated sheets.
|
|
109
|
+
- **MUST NOT** use a bottom sheet for content that fits inline on the page.
|
|
110
|
+
|
|
111
|
+
### Implementation
|
|
112
|
+
|
|
113
|
+
- **MUST** open and close via `show()` / `hide()`, the `open` prop, or `VJoyBottomSheetTrigger` — there is no automatic hide CSS in product code.
|
|
114
|
+
- **MUST** use only documented props (MCP / component source).
|
|
115
|
+
- **MUST** teleport to `body` in production (`teleport` default).
|
|
116
|
+
- **MUST** wire `hide()` on every dismiss path when using a custom `bottom-sheet-cta` slot.
|
|
117
|
+
- **MUST NOT** invent props (`size`, `variant`, `title`, …).
|
|
118
|
+
- **MUST NOT** override `.joy-bottom-sheet` chrome (overlay, radius, shadow, z-index) with app CSS.
|
|
119
|
+
- **MUST NOT** use `frozen` outside Storybook or docs.
|
|
120
|
+
|
|
121
|
+
### Interaction
|
|
122
|
+
|
|
123
|
+
- **SHOULD** keep content concise — long forms belong in `VJoyDrawer` or a full page.
|
|
124
|
+
- **SHOULD** listen to `bottom-sheet:close` to reset local state or sync parent `open` when using imperative open.
|
|
125
|
+
- **SHOULD** add `data-click-outside-ignore` on inner controls when click-outside dismiss would break a nested popover (honours `OnClickOutside` ignore list).
|
|
126
|
+
- **SHOULD NOT** rely on drag-to-dismiss as the only dismiss path — always provide a visible close or cancel action in the footer.
|
|
127
|
+
|
|
128
|
+
### Do / don't
|
|
129
|
+
|
|
130
|
+
| Do | Don't |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `VJoyBottomSheetTrigger` for simple open buttons | Reimplement overlay + slide animation |
|
|
133
|
+
| Default close CTA for single-action dismiss | Custom footer with no `hide()` wiring |
|
|
134
|
+
| `ref` + `show()` for programmatic open | Expect the sheet to auto-close without calling `hide()` |
|
|
135
|
+
| Scrollable content inside the slot | Unbounded content that exceeds viewport without internal scroll |
|
|
136
|
+
| `preventBodyScroll` in production (default) | Disable scroll lock without a deliberate reason |
|
|
137
|
+
| Localized `closeLabel` | Leave default `"Close"` in non-English UI |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Content
|
|
142
|
+
|
|
143
|
+
Bottom sheets present focused choices or short copy. Keep header area minimal (drag handle only) — put titles and descriptions in `bottom-sheet-content`.
|
|
144
|
+
|
|
145
|
+
### Footer CTA rules
|
|
146
|
+
|
|
147
|
+
Follow `VJoyButton` content rules. Bottom-sheet-specific expectations:
|
|
148
|
+
|
|
149
|
+
| Strength | Rule |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| **MUST** | Primary action names the outcome — ✅ `Apply filters`, `Save changes` |
|
|
152
|
+
| **MUST** | Dismiss CTA is neutral — ✅ `Close`, `Cancel` |
|
|
153
|
+
| **MUST** | Custom slot buttons call `hide()` when they dismiss |
|
|
154
|
+
| **MUST NOT** | Vague confirm — ❌ `OK`, `Yes` without clear context in the body |
|
|
155
|
+
| **SHOULD** | ≤ 25 characters per button label; wrap, don't truncate |
|
|
156
|
+
| **SHOULD** | Pair `main` / `primary` confirm with `secondary` or `ghost` cancel when both appear |
|
|
157
|
+
| **SHOULD** | Translate `closeLabel` when UI language ≠ English |
|
|
158
|
+
|
|
159
|
+
### Body copy
|
|
160
|
+
|
|
161
|
+
- **SHOULD** lead with a short title or question in the content slot.
|
|
162
|
+
- **SHOULD** keep lists and action grids scannable — one primary path per sheet.
|
|
163
|
+
- **MUST NOT** bury the only dismiss action below long scroll-only content without a sticky footer alternative.
|
|
164
|
+
|
|
165
|
+
### Patterns
|
|
166
|
+
|
|
167
|
+
**Action menu** — Short list of choices in content; default `Close` in footer or a single confirm CTA that calls `hide()` after the action.
|
|
168
|
+
|
|
169
|
+
**Light confirm** — One or two sentences in content; footer with `Cancel` + outcome-named confirm (`Delete item`). Wire confirm to persist, then `hide()`.
|
|
170
|
+
|
|
171
|
+
**Custom dual CTA** — Use `bottom-sheet-cta` with `VJoyWrapper` (see Storybook `CustomizeCTA`); **must** call `hide()` on cancel and after successful confirm.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Accessibility
|
|
176
|
+
|
|
177
|
+
Behaviors (what must happen), not slogans.
|
|
178
|
+
|
|
179
|
+
| Behavior | Expectation |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| Role | No `role="dialog"` or `aria-modal` in the component today — **apps SHOULD** add dialog semantics and focus management when the sheet is business-critical |
|
|
182
|
+
| Name | Provide a visible heading in `bottom-sheet-content`; associate with `aria-labelledby` if you add dialog role |
|
|
183
|
+
| Keyboard | No built-in **Escape** dismiss — provide footer actions; consider app-level Escape handler if required |
|
|
184
|
+
| Focus | Move focus into the sheet on open and restore on close when implementing custom flows |
|
|
185
|
+
| Scroll | Page scroll locked while open (`preventBodyScroll` default) |
|
|
186
|
+
| Drag handle | Visual affordance only — **must not** be the only dismiss path |
|
|
187
|
+
| Close control | Default footer `VJoyButton` has an accessible name from `closeLabel` |
|
|
188
|
+
| Motion | 300ms slide animations; respect `prefers-reduced-motion` at app level if required |
|
|
189
|
+
|
|
190
|
+
Target: **WCAG 2.2 AA** where the product requires it. Treat focus trap and dialog role as **product responsibilities** until the component adds them natively.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Examples
|
|
195
|
+
|
|
196
|
+
```vue
|
|
197
|
+
<script setup lang="ts">
|
|
198
|
+
import { ref } from 'vue'
|
|
199
|
+
import {
|
|
200
|
+
VJoyBottomSheet,
|
|
201
|
+
VJoyBottomSheetTrigger,
|
|
202
|
+
VJoyButton,
|
|
203
|
+
VJoyWrapper,
|
|
204
|
+
} from '@maltjoy/core-vue'
|
|
205
|
+
|
|
206
|
+
const bottomSheet = ref<InstanceType<typeof VJoyBottomSheet>>()
|
|
207
|
+
const triggeredSheet = ref<InstanceType<typeof VJoyBottomSheet>>()
|
|
208
|
+
const customSheet = ref<InstanceType<typeof VJoyBottomSheet>>()
|
|
209
|
+
const isOpen = ref(false)
|
|
210
|
+
|
|
211
|
+
function onClosed() {
|
|
212
|
+
// sync parent state, analytics, etc.
|
|
213
|
+
}
|
|
214
|
+
</script>
|
|
215
|
+
|
|
216
|
+
<template>
|
|
217
|
+
<!-- Imperative open via ref -->
|
|
218
|
+
<VJoyBottomSheet
|
|
219
|
+
ref="bottomSheet"
|
|
220
|
+
close-label="Close"
|
|
221
|
+
@bottom-sheet:close="onClosed"
|
|
222
|
+
>
|
|
223
|
+
<template #bottom-sheet-content>
|
|
224
|
+
<h2>Sort results</h2>
|
|
225
|
+
<p>Choose how listings are ordered.</p>
|
|
226
|
+
<!-- options -->
|
|
227
|
+
</template>
|
|
228
|
+
</VJoyBottomSheet>
|
|
229
|
+
|
|
230
|
+
<VJoyButton variant="primary" label="Open sort" @click="bottomSheet?.show()" />
|
|
231
|
+
|
|
232
|
+
<!-- Declarative trigger -->
|
|
233
|
+
<VJoyBottomSheet ref="triggeredSheet">
|
|
234
|
+
<template #bottom-sheet-content>
|
|
235
|
+
<p>Opened via VJoyBottomSheetTrigger.</p>
|
|
236
|
+
</template>
|
|
237
|
+
</VJoyBottomSheet>
|
|
238
|
+
|
|
239
|
+
<VJoyBottomSheetTrigger :bottom-sheet="triggeredSheet">
|
|
240
|
+
<VJoyButton variant="secondary" label="Show options" />
|
|
241
|
+
</VJoyBottomSheetTrigger>
|
|
242
|
+
|
|
243
|
+
<!-- Controlled open prop -->
|
|
244
|
+
<VJoyBottomSheet :open="isOpen" @bottom-sheet:close="isOpen = false">
|
|
245
|
+
<template #bottom-sheet-content>
|
|
246
|
+
<p>Controlled by parent state.</p>
|
|
247
|
+
</template>
|
|
248
|
+
</VJoyBottomSheet>
|
|
249
|
+
|
|
250
|
+
<!-- Custom footer — you must call hide() -->
|
|
251
|
+
<VJoyBottomSheet ref="customSheet">
|
|
252
|
+
<template #bottom-sheet-content>
|
|
253
|
+
<p>Discard unsaved changes?</p>
|
|
254
|
+
</template>
|
|
255
|
+
<template #bottom-sheet-cta>
|
|
256
|
+
<VJoyWrapper justify="flex-end">
|
|
257
|
+
<VJoyButton variant="ghost" size="small" label="Cancel" @click="customSheet?.hide()" />
|
|
258
|
+
<VJoyButton variant="main" size="small" label="Discard" @click="customSheet?.hide()" />
|
|
259
|
+
</VJoyWrapper>
|
|
260
|
+
</template>
|
|
261
|
+
</VJoyBottomSheet>
|
|
262
|
+
</template>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
In Nuxt (or apps with auto-import), omit imports if `VJoyBottomSheet`, `VJoyBottomSheetTrigger`, and `VJoyButton` are already global.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Related
|
|
270
|
+
|
|
271
|
+
| Component | Use when |
|
|
272
|
+
| --- | --- |
|
|
273
|
+
| `VJoyBottomSheetTrigger` | Declarative open trigger wrapping any activator |
|
|
274
|
+
| `VJoyDialog` | Centered modal on desktop; richer header/footer patterns |
|
|
275
|
+
| `VJoyDrawer` | Edge panel; long or exploratory content |
|
|
276
|
+
| `VJoyButton` | Default close CTA and custom footer actions |
|
|
277
|
+
| `VJoyWrapper` | Layout for multiple footer CTAs |
|
|
278
|
+
| `VJoyDropdown` / `VJoyMenu` | Anchored menus without full-screen overlay |
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## For agents
|
|
283
|
+
|
|
284
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyBottomSheet.dsds.json`](./JoyBottomSheet.dsds.json).
|
|
285
|
+
|
|
286
|
+
### Pick the right component
|
|
287
|
+
|
|
288
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
289
|
+
| --- | --- | --- |
|
|
290
|
+
| Mobile modal choices / short focused content | `VJoyBottomSheet` | Custom `position: fixed` slide panel |
|
|
291
|
+
| Desktop modal confirm / form | `VJoyDialog` | `VJoyBottomSheet` on wide desktop-only flows |
|
|
292
|
+
| Simple open from a button | `VJoyBottomSheet` + `VJoyBottomSheetTrigger` | Inline overlay markup |
|
|
293
|
+
| Long filters or details panel | `VJoyDrawer` | Bottom sheet with unbounded scroll |
|
|
294
|
+
| Custom footer actions | `bottom-sheet-cta` slot + `hide()` | Default slot expecting auto-close |
|
|
295
|
+
| Keep sheet visible in Storybook | `frozen` | `frozen` in product code |
|
|
296
|
+
|
|
297
|
+
### Hard rules
|
|
298
|
+
|
|
299
|
+
1. **MUST** call MCP (`joy_get_component_info`) before generating props.
|
|
300
|
+
2. **MUST** populate `bottom-sheet-content` for every sheet.
|
|
301
|
+
3. **MUST** call `hide()` on every custom footer dismiss / confirm path.
|
|
302
|
+
4. **MUST** use `show()` / `hide()` or `open` — the sheet does not self-close from CSS alone.
|
|
303
|
+
5. **MUST NOT** invent props (`size`, `variant`, `title`, …).
|
|
304
|
+
6. **MUST NOT** use `frozen` in product code.
|
|
305
|
+
7. **MUST NOT** override `.joy-bottom-sheet` overlay, shadow, radius, or z-index in app CSS.
|
|
306
|
+
8. **SHOULD** prefer `VJoyDialog` when the surface is desktop-primary.
|
|
307
|
+
9. **SHOULD** set localized `closeLabel` when UI language ≠ English.
|
|
308
|
+
10. **SHOULD** listen to `bottom-sheet:close` to sync parent `open` state.
|
|
309
|
+
|
|
310
|
+
### Slot checklist
|
|
311
|
+
|
|
312
|
+
- [ ] `bottom-sheet-content` — main body present
|
|
313
|
+
- [ ] `bottom-sheet-cta` — only when customizing footer (+ manual `hide()` on dismiss)
|
|
314
|
+
- [ ] Default close CTA — omit custom slot when a single dismiss button is enough
|
|
315
|
+
|
|
316
|
+
### Generation checklist
|
|
317
|
+
|
|
318
|
+
- [ ] Correct component (`VJoyBottomSheet` or named alternative)
|
|
319
|
+
- [ ] Props from MCP only (`closeLabel`, `open`, `preventBodyScroll`, `teleport`, `frozen`)
|
|
320
|
+
- [ ] No invented props / no custom bottom-sheet chrome CSS
|
|
321
|
+
- [ ] Open path defined (`show()`, `open`, or `VJoyBottomSheetTrigger`)
|
|
322
|
+
- [ ] Close path defined (default CTA, drag, click-outside, or custom `hide()`)
|
|
323
|
+
- [ ] Custom footer wires `hide()` on every dismiss action
|
|
324
|
+
- [ ] `closeLabel` localized when needed
|
|
325
|
+
- [ ] `frozen` not set in product code
|
|
326
|
+
- [ ] Content fits mobile; long forms → `VJoyDrawer` / `VJoyDialog`
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## DSDS mapping
|
|
331
|
+
|
|
332
|
+
This Markdown mirrors [`JoyBottomSheet.dsds.json`](./JoyBottomSheet.dsds.json) (DSDS **0.15.2**).
|
|
333
|
+
|
|
334
|
+
| Section here | DSDS block |
|
|
335
|
+
| --- | --- |
|
|
336
|
+
| When to use / not | `use-cases` |
|
|
337
|
+
| Anatomy | `anatomy` |
|
|
338
|
+
| States | `states` |
|
|
339
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
340
|
+
| Accessibility | `accessibility` |
|
|
341
|
+
| Examples | `sections` |
|
|
342
|
+
| For agents | `agentDocumentBlocks` |
|
|
343
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
344
|
+
| Imports | `imports` in JSON |
|
|
345
|
+
| Token sizing | `design-specifications` in JSON (`--joy-core-z-index-dialog`, spacing tokens) |
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyBottomSheetTrigger
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyBottomSheetTrigger.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Bottom sheet trigger
|
|
10
|
+
|
|
11
|
+
Use **`VJoyBottomSheetTrigger`** when you need a declarative click target that opens a linked **`VJoyBottomSheet`** — for example a button or link in a mobile funnel that reveals sheet content.
|
|
12
|
+
|
|
13
|
+
The component has no visual chrome: it renders the default slot as-is, emits `click`, then calls `show()` on the `bottomSheet` ref you pass in.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyBottomSheetTrigger` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [Figma — Joy Core Design System](https://www.figma.com/design/jfndp2QBJGS9yeuWbeGuvD/%F0%9F%9F%A0--Joy---Core-Design-System--official-) |
|
|
19
|
+
| **Storybook** | [VJoyBottomSheet docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-overlays-vjoybottomsheet--docs) (subcomponent) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyBottomSheetTrigger.dsds.json`](./JoyBottomSheetTrigger.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyBottomSheetTrigger")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a bottom sheet trigger when a user action in the page should open a specific `VJoyBottomSheet` instance and you prefer a declarative wrapper over calling `bottomSheetRef.value?.show()` in a manual `@click` handler.
|
|
28
|
+
|
|
29
|
+
Typical pattern: declare a template ref on `VJoyBottomSheet`, pass that ref to `bottomSheet`, and put the activator (`VJoyButton`, native `<button>`, etc.) in the default slot. The trigger handles ref timing so `show()` runs once the sheet instance is mounted.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Open the sheet from code (on mount, after an API call, from a route guard) | `bottomSheetRef.value?.show()` on the `VJoyBottomSheet` ref |
|
|
36
|
+
| A centered modal that blocks the full viewport | `VJoyDialog` + `VJoyDialogTrigger` |
|
|
37
|
+
| A side panel sliding from the edge | `VJoyDrawer` + `VJoyDrawerTrigger` |
|
|
38
|
+
| Tooltip-style contextual help | `VJoyTooltip` |
|
|
39
|
+
| Custom overlay markup / ad-hoc open logic | Still use `VJoyBottomSheetTrigger` or `show()` — don’t reinvent overlay wiring |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## How it works
|
|
44
|
+
|
|
45
|
+
`VJoyBottomSheetTrigger` is a **behavioral wrapper** only. On click it emits `click`, then calls `show()` on the linked bottom sheet. A computed `renderedBottomSheet` tracks the prop so the ref is used even when parent and sheet mount in the same tick.
|
|
46
|
+
|
|
47
|
+
### Anatomy
|
|
48
|
+
|
|
49
|
+
| Part | Required? | What it is |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| Trigger wrapper | Yes | Root logic — no styled DOM of its own |
|
|
52
|
+
| Default slot (trigger) | Yes | One or more activator nodes (e.g. `VJoyButton`, `<button>`) |
|
|
53
|
+
| `bottomSheet` binding | Yes | Template ref to the target `VJoyBottomSheet` instance |
|
|
54
|
+
|
|
55
|
+
### Default values
|
|
56
|
+
|
|
57
|
+
- `bottomSheet` is **required** — bind the `VJoyBottomSheet` template ref (may be `null` until mounted; the component retries via `renderedBottomSheet`)
|
|
58
|
+
|
|
59
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Best practices
|
|
64
|
+
|
|
65
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
66
|
+
|
|
67
|
+
### Composition
|
|
68
|
+
|
|
69
|
+
- **MUST** place `VJoyBottomSheet` in the same parent (or an ancestor that shares the ref) as `VJoyBottomSheetTrigger`.
|
|
70
|
+
- **MUST** pass the sheet’s template ref to `bottomSheet` (`:bottom-sheet="bottomSheetRef"` in templates).
|
|
71
|
+
- **SHOULD** put an accessible control in the default slot — `VJoyButton` with a `label`, or a native `<button type="button">` with visible text.
|
|
72
|
+
- **SHOULD** listen to `@click` on the trigger when the parent needs side effects before or alongside opening the sheet.
|
|
73
|
+
- **SHOULD** call `bottomSheetRef.value?.show()` directly when there is no user-facing activator.
|
|
74
|
+
|
|
75
|
+
### Implementation
|
|
76
|
+
|
|
77
|
+
- **MUST** use only documented props (`bottomSheet`) from MCP / types.
|
|
78
|
+
- **MUST NOT** invent props (`open`, `variant`, `disabled`, …).
|
|
79
|
+
- **MUST NOT** add custom CSS on the trigger wrapper — style the slot content instead.
|
|
80
|
+
- **MUST NOT** use this component without a paired `VJoyBottomSheet` instance.
|
|
81
|
+
|
|
82
|
+
### Do / don’t
|
|
83
|
+
|
|
84
|
+
| Do | Don’t |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| `VJoyBottomSheet` ref + trigger in the same view | Orphan trigger with no sheet ref |
|
|
87
|
+
| `VJoyButton` or `<button type="button">` in the default slot | `<div @click>` as the only activator |
|
|
88
|
+
| `@click` for parent-side logic | Rely on the trigger to close the sheet |
|
|
89
|
+
| Imperative `show()` when there is no click target | Force a trigger wrapper for programmatic-only open |
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Accessibility
|
|
94
|
+
|
|
95
|
+
Behaviors (what must happen), not slogans. The trigger adds no semantics of its own — accessibility comes from the default slot.
|
|
96
|
+
|
|
97
|
+
| Behavior | Expectation |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| Role | No dedicated role on the wrapper; the slot content supplies it (`button`, `a`, etc.) |
|
|
100
|
+
| Name | Activator must have an accessible name (`label` on `VJoyButton`, visible text, or `aria-label`) |
|
|
101
|
+
| Keyboard | Activator must be keyboard-operable (native button/link or Joy button components) |
|
|
102
|
+
| Focus | Focus stays on the trigger until the sheet opens; ensure sheet content is reachable after open (handled by `VJoyBottomSheet`) |
|
|
103
|
+
| Touch | Use on mobile-first surfaces where bottom sheets are the primary overlay pattern |
|
|
104
|
+
|
|
105
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Examples
|
|
110
|
+
|
|
111
|
+
```vue
|
|
112
|
+
<script setup lang="ts">
|
|
113
|
+
import { ref } from 'vue'
|
|
114
|
+
import {
|
|
115
|
+
VJoyBottomSheet,
|
|
116
|
+
VJoyBottomSheetTrigger,
|
|
117
|
+
VJoyButton,
|
|
118
|
+
} from '@maltjoy/core-vue'
|
|
119
|
+
|
|
120
|
+
const bottomSheetRef = ref<InstanceType<typeof VJoyBottomSheet> | null>(null)
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<VJoyBottomSheetTrigger :bottom-sheet="bottomSheetRef" @click="/* optional */">
|
|
125
|
+
<VJoyButton label="Show options" variant="main" />
|
|
126
|
+
</VJoyBottomSheetTrigger>
|
|
127
|
+
|
|
128
|
+
<VJoyBottomSheet ref="bottomSheetRef">
|
|
129
|
+
<template #bottom-sheet-content>
|
|
130
|
+
Sheet content here
|
|
131
|
+
</template>
|
|
132
|
+
</VJoyBottomSheet>
|
|
133
|
+
</template>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Native button activator (matches test and funnel demos):
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<VJoyBottomSheetTrigger :bottom-sheet="bottomSheetRef">
|
|
140
|
+
<button type="button">Open bottom sheet</button>
|
|
141
|
+
</VJoyBottomSheetTrigger>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyBottomSheetTrigger` is already global.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Related
|
|
149
|
+
|
|
150
|
+
| Component | Use when |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `VJoyBottomSheet` | The mobile sheet overlay opened by this trigger |
|
|
153
|
+
| `VJoyDialogTrigger` | Same declarative pattern for `VJoyDialog` |
|
|
154
|
+
| `VJoyDrawerTrigger` | Same declarative pattern for `VJoyDrawer` |
|
|
155
|
+
| `VJoyButton` | Typical labeled trigger in the default slot |
|
|
156
|
+
| `VJoyDialog` | Desktop or centered modal instead of a bottom sheet |
|