@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,355 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyPagination
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyPagination.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Pagination
|
|
10
|
+
|
|
11
|
+
Use **`VJoyPagination`** when someone needs to move through **pages of results** — for example a searchable list, a data table, or any collection split into fixed-size chunks.
|
|
12
|
+
|
|
13
|
+
The component renders previous/next controls, numbered page buttons, and automatic ellipsis gaps for large page counts. Bind `v-model:current-page` in the parent and load the matching slice of data when the page changes. Use slots when prev/next or page cells must be real links (`RouterLink`, `NuxtLink`, `<a>`).
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyPagination` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [ZeroHeight — Pagination](https://zeroheight.com/6bf479565/p/952103) |
|
|
19
|
+
| **Storybook** | [VJoyPagination docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-navigation-vjoypagination--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyPagination.dsds.json`](./JoyPagination.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyPagination")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use pagination when content is **split into discrete pages** and users should jump to a specific page or step forward/back one page at a time.
|
|
28
|
+
|
|
29
|
+
It fits server-driven lists (API returns `totalPages`), client-paginated tables, and any surface where the full dataset is too large to show at once. The component handles which page numbers to display and when to show ellipsis separators — you supply `currentPage`, `totalPages`, and react to page changes.
|
|
30
|
+
|
|
31
|
+
### When not to use this component
|
|
32
|
+
|
|
33
|
+
| Need | Use instead |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| Carousel or slideshow position dots | `VJoyIndicators` |
|
|
36
|
+
| Linear multi-step workflow with progress | `VJoyStepper` |
|
|
37
|
+
| “Load more” / endless feed | Load-more button or infinite scroll |
|
|
38
|
+
| Primary app navigation between sections | `VJoyTabs`, `VJoyMenu`, or route-level nav |
|
|
39
|
+
| Changing how many items appear per page | Page-size control alongside pagination (not built into this component) |
|
|
40
|
+
| Custom pagination markup / ad-hoc CSS | Still use `VJoyPagination` + Joy tokens |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## How it works
|
|
45
|
+
|
|
46
|
+
`VJoyPagination` is a controlled component: the parent owns `currentPage` and updates it when `update:current-page` fires (or via `v-model:current-page`). Page buttons are `<button type="button">` by default; prev/next use `VJoyIconButton` chevrons (`PaginationIcon`).
|
|
47
|
+
|
|
48
|
+
### Anatomy
|
|
49
|
+
|
|
50
|
+
| Part | Required? | What it is |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Navigation root | Yes | `<nav role="navigation">` with optional `aria-label` from `labelPagination` |
|
|
53
|
+
| Previous control | Yes | Chevron left (`pagination-prev` slot or default `PaginationIcon`) |
|
|
54
|
+
| Page list | Yes | Numbered page controls; always includes page **1** and the **last** page when `totalPages > 1` |
|
|
55
|
+
| Ellipsis separators | No | CSS `…` when the visible window skips pages (`.joy-pagination__separator`) |
|
|
56
|
+
| Next control | Yes | Chevron right (`pagination-next` slot or default `PaginationIcon`) |
|
|
57
|
+
| Current page marker | Yes | `.current` class + `aria-current="true"` on the active page button |
|
|
58
|
+
|
|
59
|
+
### Page window algorithm
|
|
60
|
+
|
|
61
|
+
When `totalPages` is **6 or more**, the component shows at most **six** page entries (first page, last page, and up to four in between). The window recenters around `currentPage` and adds ellipsis when page **1** or the last page is not adjacent to the next visible number.
|
|
62
|
+
|
|
63
|
+
When `totalPages` is **2–5**, all intermediate pages render with no ellipsis. When `totalPages` is **1**, only page `1` appears (prev/next stay visible but prev/next clicks do not emit at the boundaries).
|
|
64
|
+
|
|
65
|
+
### Default values
|
|
66
|
+
|
|
67
|
+
- `currentPage` defaults to **`1`**
|
|
68
|
+
- `totalPages` defaults to **`1`**
|
|
69
|
+
- `labelPagination`, `labelPrev`, and `labelNext` are **optional** — set them for accessibility and i18n
|
|
70
|
+
|
|
71
|
+
Props, events, and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
72
|
+
|
|
73
|
+
### Controlled usage
|
|
74
|
+
|
|
75
|
+
```vue
|
|
76
|
+
<VJoyPagination
|
|
77
|
+
v-model:current-page="page"
|
|
78
|
+
:total-pages="totalPages"
|
|
79
|
+
label-pagination="Results pages"
|
|
80
|
+
label-prev="Previous page"
|
|
81
|
+
label-next="Next page"
|
|
82
|
+
/>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Fetch or slice data in the parent when `page` changes. The component does **not** fetch data or compute `totalPages` for you.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Variants
|
|
90
|
+
|
|
91
|
+
`VJoyPagination` has **no variant or emphasis prop**. Visual treatment is fixed: neutral page numbers, teal hover, filled circle on the current page, ghost chevron icon buttons for prev/next.
|
|
92
|
+
|
|
93
|
+
Do not simulate “primary / compact pagination” with custom CSS — use the component as shipped.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Sizes
|
|
98
|
+
|
|
99
|
+
`VJoyPagination` has **no size prop**. Page cells and icon buttons use a fixed **36px** circular hit target and `--joy-font-size-primary-650` typography inside the component stylesheet.
|
|
100
|
+
|
|
101
|
+
Control density with layout around the nav — **do not hardcode `36px` widths or circle radii** in product CSS. Prev/next icons use `VJoyIconButton` at `size="xsmall"` internally.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## States
|
|
106
|
+
|
|
107
|
+
| State | What the user sees / can do |
|
|
108
|
+
| --- | --- |
|
|
109
|
+
| Default page | Neutral number; clickable |
|
|
110
|
+
| Current page | Filled secondary background, white label; `aria-current="true"` |
|
|
111
|
+
| Hover / focus | Teal hover background on page buttons and chevrons; focus ring via `--joy-form-field-focus-shadow` |
|
|
112
|
+
| Previous at page 1 | Prev icon button `disabled`; clicks on the prev region do not emit |
|
|
113
|
+
| Next at last page | Next icon button `disabled`; clicks on the next region do not emit |
|
|
114
|
+
| Ellipsis gap | `…` pseudo-element after page 1 and/or before the last page when numbers are skipped |
|
|
115
|
+
| Single page (`totalPages === 1`) | Only page `1` in the list; boundary guards still apply |
|
|
116
|
+
| Slotted prev/next | Custom link content; component still blocks emit when already at first/last page |
|
|
117
|
+
|
|
118
|
+
There is **no loading or disabled API** on the whole control. Disable interaction at the page level (e.g. while fetching) in the parent if needed.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Best practices
|
|
123
|
+
|
|
124
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
125
|
+
|
|
126
|
+
### Data / selection
|
|
127
|
+
|
|
128
|
+
- **MUST** bind `currentPage` with `v-model:current-page` (or `:current-page` + `@update:current-page`).
|
|
129
|
+
- **MUST** pass `totalPages` from real data — never invent a page count.
|
|
130
|
+
- **MUST** keep `currentPage` within `1 … totalPages` when `totalPages` shrinks (reset in the parent).
|
|
131
|
+
- **SHOULD** place pagination **below** the list or table it controls, centered or aligned per layout.
|
|
132
|
+
- **SHOULD NOT** show pagination when `totalPages <= 1` unless the layout requires a consistent chrome slot.
|
|
133
|
+
|
|
134
|
+
### Implementation
|
|
135
|
+
|
|
136
|
+
- **MUST** use only documented props: `currentPage`, `totalPages`, `labelPagination`, `labelPrev`, `labelNext`.
|
|
137
|
+
- **MUST** style with Joy tokens — no custom `.joy-pagination` chrome overrides.
|
|
138
|
+
- **MUST NOT** invent props. The Stencil `joy-pagination` web component exposes `linkPrev`, `linkNext`, `heapPrev`, and `heapNext` — **these are not on `VJoyPagination`**. Use slots for links in Vue.
|
|
139
|
+
- **SHOULD** use default buttons for SPA page changes; use `pagination-prev`, `pagination-next`, and `page` slots only when links must carry `href`, tracking attributes, or router components.
|
|
140
|
+
- **SHOULD** keep page numbers **1-based**, matching the component API.
|
|
141
|
+
|
|
142
|
+
### Interaction
|
|
143
|
+
|
|
144
|
+
- **MUST** reload or slice content when `update:current-page` fires.
|
|
145
|
+
- **MUST** provide `labelPagination`, `labelPrev`, and `labelNext` in production UIs (localized).
|
|
146
|
+
- **SHOULD** scroll the list into view or move focus to the list heading after a page change when it helps orientation.
|
|
147
|
+
|
|
148
|
+
### Do / don’t
|
|
149
|
+
|
|
150
|
+
| Do | Don’t |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `v-model:current-page` + computed `totalPages` | Uncontrolled page state scattered in child components |
|
|
153
|
+
| Localized `labelPrev` / `labelNext` | Icon-only chevrons with no accessible name |
|
|
154
|
+
| Slots for `RouterLink` / `NuxtLink` when URLs matter | `@click` router pushes on default buttons when a link is required |
|
|
155
|
+
| Hide pagination when there is only one page | Pagination with `totalPages={0}` or negative values |
|
|
156
|
+
| Let the component compute the page window | Hand-rolling ellipsis logic beside Joy pagination |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Content
|
|
161
|
+
|
|
162
|
+
Pagination copy is minimal but **required for accessibility**: names for the nav landmark and the prev/next controls. Page cells show **numbers only** — do not embed words inside page buttons.
|
|
163
|
+
|
|
164
|
+
### Label guidance
|
|
165
|
+
|
|
166
|
+
| Prop | Purpose | Example (EN) |
|
|
167
|
+
| --- | --- | --- |
|
|
168
|
+
| `labelPagination` | `aria-label` on the `<nav>` | `Results pages`, `Search results pagination` |
|
|
169
|
+
| `labelPrev` | `title` + `aria-label` on previous control | `Previous page` |
|
|
170
|
+
| `labelNext` | `title` + `aria-label` on next control | `Next page` |
|
|
171
|
+
|
|
172
|
+
### Writing rules
|
|
173
|
+
|
|
174
|
+
| Strength | Rule |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| **MUST** | Localize all three labels for non-English locales |
|
|
177
|
+
| **MUST** | Describe the control action — ✅ `Previous page` · ❌ `Back` (ambiguous) |
|
|
178
|
+
| **MUST NOT** | Put “Page” text inside every number button — the number alone is the label |
|
|
179
|
+
| **SHOULD** | Keep `labelPagination` short and specific to the list (`Invoice pagination`) |
|
|
180
|
+
| **SHOULD** | Use sentence case in English |
|
|
181
|
+
| **SHOULD** (FR) | Use natural phrasing — _Page précédente_, _Page suivante_, _Pagination des résultats_ |
|
|
182
|
+
|
|
183
|
+
### Patterns
|
|
184
|
+
|
|
185
|
+
**Tables** — Pair with a results summary above (`Showing 1–20 of 142`). Pagination labels name the control, not the row range.
|
|
186
|
+
|
|
187
|
+
**Search** — Reset `currentPage` to `1` when filters or query change.
|
|
188
|
+
|
|
189
|
+
**Router-driven pages** — Use the `page` slot so each number is a real link; keep `currentPage` in sync with the route in the parent.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Accessibility
|
|
194
|
+
|
|
195
|
+
Behaviors (what must happen), not slogans.
|
|
196
|
+
|
|
197
|
+
| Behavior | Expectation |
|
|
198
|
+
| --- | --- |
|
|
199
|
+
| Landmark | Root is `<nav role="navigation">`; set `labelPagination` so the region has an accessible name |
|
|
200
|
+
| Page buttons | Native `<button type="button">` with visible page number; current page has `aria-current="true"` |
|
|
201
|
+
| Prev / next | Default path uses `VJoyIconButton` with `label` / `title` from `labelPrev` / `labelNext` |
|
|
202
|
+
| Disabled boundaries | Prev disabled on page 1; next disabled on last page; emit blocked even when slots replace icons |
|
|
203
|
+
| Keyboard | Page buttons activate with **Enter** / **Space**; icon buttons follow `VJoyIconButton` keyboard behavior |
|
|
204
|
+
| Focus | Preserve visible focus rings — component uses `--joy-form-field-focus-shadow` on `:focus` |
|
|
205
|
+
| Slotted links | When using `RouterLink` or `<a>`, ensure each link has a clear name (page number or chevron + `aria-label` on the control) |
|
|
206
|
+
|
|
207
|
+
Target: **WCAG 2.2 AA** where the product requires it.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Examples
|
|
212
|
+
|
|
213
|
+
```vue
|
|
214
|
+
<script setup lang="ts">
|
|
215
|
+
import { ref, watch } from 'vue'
|
|
216
|
+
import { VJoyPagination } from '@maltjoy/core-vue'
|
|
217
|
+
|
|
218
|
+
const currentPage = ref(1)
|
|
219
|
+
const totalPages = ref(12)
|
|
220
|
+
|
|
221
|
+
watch(currentPage, (page) => {
|
|
222
|
+
// fetch or slice data for `page`
|
|
223
|
+
})
|
|
224
|
+
</script>
|
|
225
|
+
|
|
226
|
+
<template>
|
|
227
|
+
<!-- Controlled SPA pagination -->
|
|
228
|
+
<VJoyPagination
|
|
229
|
+
v-model:current-page="currentPage"
|
|
230
|
+
:total-pages="totalPages"
|
|
231
|
+
label-pagination="Results pages"
|
|
232
|
+
label-prev="Previous page"
|
|
233
|
+
label-next="Next page"
|
|
234
|
+
/>
|
|
235
|
+
|
|
236
|
+
<!-- Router links via slots (Nuxt: use NuxtLink) -->
|
|
237
|
+
<VJoyPagination
|
|
238
|
+
v-model:current-page="currentPage"
|
|
239
|
+
:total-pages="totalPages"
|
|
240
|
+
label-prev="Previous page"
|
|
241
|
+
label-next="Next page"
|
|
242
|
+
>
|
|
243
|
+
<template #pagination-prev>
|
|
244
|
+
<RouterLink :to="{ query: { page: currentPage - 1 } }">
|
|
245
|
+
<VJoyIcon size="xxsmall" name="chevron-left" />
|
|
246
|
+
</RouterLink>
|
|
247
|
+
</template>
|
|
248
|
+
|
|
249
|
+
<template #page="{ page }">
|
|
250
|
+
<RouterLink :to="{ query: { page: page.page } }">
|
|
251
|
+
{{ page.page }}
|
|
252
|
+
</RouterLink>
|
|
253
|
+
</template>
|
|
254
|
+
|
|
255
|
+
<template #pagination-next>
|
|
256
|
+
<RouterLink :to="{ query: { page: currentPage + 1 } }">
|
|
257
|
+
<VJoyIcon size="xxsmall" name="chevron-right" />
|
|
258
|
+
</RouterLink>
|
|
259
|
+
</template>
|
|
260
|
+
</VJoyPagination>
|
|
261
|
+
</template>
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyPagination` is already global.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Related
|
|
269
|
+
|
|
270
|
+
| Component | Use when |
|
|
271
|
+
| --- | --- |
|
|
272
|
+
| `VJoyIconButton` | Prev/next chevrons (used internally via `PaginationIcon`) |
|
|
273
|
+
| `VJoyIcon` | Chevron icons inside custom `pagination-prev` / `pagination-next` slots |
|
|
274
|
+
| `VJoyIndicators` | Dot indicators for carousels or step position — not paged lists |
|
|
275
|
+
| `VJoyTabs` | Switching peer sections on one surface — not sequential pages of one list |
|
|
276
|
+
| `VJoyButton` | “Load more” or actions beside pagination |
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## For agents
|
|
281
|
+
|
|
282
|
+
Optional for readers; **required** for vibe-coding quality. Maps to `agentDocumentBlocks` in [`JoyPagination.dsds.json`](./JoyPagination.dsds.json).
|
|
283
|
+
|
|
284
|
+
### Pick the right component
|
|
285
|
+
|
|
286
|
+
| If the intent is… | Emit… | Do not emit… |
|
|
287
|
+
| --- | --- | --- |
|
|
288
|
+
| Paginated list / table pages | `VJoyPagination` | Hand-built page number buttons |
|
|
289
|
+
| Carousel slide index | `VJoyIndicators` | `VJoyPagination` |
|
|
290
|
+
| Multi-step form wizard | `VJoyStepper` | `VJoyPagination` |
|
|
291
|
+
| URL-based page links in Vue | `VJoyPagination` + `page` / prev / next slots | Stencil `link-prev` / `link-next` props (not in Vue API) |
|
|
292
|
+
| Infinite scroll feed | Load-more pattern | `VJoyPagination` with `totalPages={1}` hack |
|
|
293
|
+
|
|
294
|
+
### Hard rules
|
|
295
|
+
|
|
296
|
+
1. **MUST** read `VJoyPagination.vue`, Storybook, or Joy MCP list entry before generating props — `joy_get_component_info("VJoyPagination")` may be unavailable in some MCP builds; fall back to source.
|
|
297
|
+
2. **MUST** bind `v-model:current-page` (or equivalent controlled pair) and pass `:total-pages`.
|
|
298
|
+
3. **MUST** use only these props: `currentPage`, `totalPages`, `labelPagination`, `labelPrev`, `labelNext`.
|
|
299
|
+
4. **MUST** listen for `@update:current-page` and update list data in the parent.
|
|
300
|
+
5. **MUST NOT** emit Stencil-only props (`linkPrev`, `linkNext`, `heapPrev`, `heapNext`) on `VJoyPagination`.
|
|
301
|
+
6. **MUST NOT** override `.joy-pagination` sizes, colors, or ellipsis spacing with product CSS.
|
|
302
|
+
7. **MUST** set `labelPagination`, `labelPrev`, and `labelNext` for accessible names.
|
|
303
|
+
8. **SHOULD** omit the component when `totalPages <= 1`.
|
|
304
|
+
9. **SHOULD** use slots only when links or extra attributes are required.
|
|
305
|
+
|
|
306
|
+
### Valid API (from `VJoyPagination.vue`)
|
|
307
|
+
|
|
308
|
+
| Prop | Type | Default |
|
|
309
|
+
| --- | --- | --- |
|
|
310
|
+
| `currentPage` | `number` | `1` |
|
|
311
|
+
| `totalPages` | `number` | `1` |
|
|
312
|
+
| `labelPagination` | `string` | — |
|
|
313
|
+
| `labelPrev` | `string` | — |
|
|
314
|
+
| `labelNext` | `string` | — |
|
|
315
|
+
|
|
316
|
+
| Event | Payload |
|
|
317
|
+
| --- | --- |
|
|
318
|
+
| `update:current-page` | `number` (1-based page index) |
|
|
319
|
+
|
|
320
|
+
| Slot | Scope |
|
|
321
|
+
| --- | --- |
|
|
322
|
+
| `pagination-prev` | — |
|
|
323
|
+
| `pagination-next` | — |
|
|
324
|
+
| `page` | `{ page: number }` |
|
|
325
|
+
|
|
326
|
+
### Checklist
|
|
327
|
+
|
|
328
|
+
- [ ] `VJoyPagination` (not `joy-pagination` web component) for Vue apps
|
|
329
|
+
- [ ] `v-model:current-page` + `:total-pages` from real data
|
|
330
|
+
- [ ] `labelPagination`, `labelPrev`, `labelNext` set and localized
|
|
331
|
+
- [ ] Parent handles `update:current-page` / data fetch
|
|
332
|
+
- [ ] No invented props; no Stencil `link-*` / `heap-*` props
|
|
333
|
+
- [ ] No custom `.joy-pagination` chrome CSS
|
|
334
|
+
- [ ] Slots used only when router links or extra attrs are needed
|
|
335
|
+
- [ ] Hidden or omitted when `totalPages <= 1` (unless layout requires it)
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## DSDS mapping
|
|
340
|
+
|
|
341
|
+
This Markdown mirrors [`JoyPagination.dsds.json`](./JoyPagination.dsds.json) (DSDS **0.15.2**).
|
|
342
|
+
|
|
343
|
+
| Section here | DSDS block |
|
|
344
|
+
| --- | --- |
|
|
345
|
+
| When to use / not | `use-cases` |
|
|
346
|
+
| Anatomy / page window | `anatomy` |
|
|
347
|
+
| Variants / sizes | `variants` |
|
|
348
|
+
| States | `states` |
|
|
349
|
+
| Best practices / content rules | `guidelines` (+ `content`) |
|
|
350
|
+
| Accessibility | `accessibility` |
|
|
351
|
+
| Examples | `sections` |
|
|
352
|
+
| For agents | `agentDocumentBlocks` |
|
|
353
|
+
| Props detail | `api` in JSON + Joy MCP |
|
|
354
|
+
| Imports | `imports` in JSON |
|
|
355
|
+
| Fixed cell sizing | `design-specifications` in JSON |
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
component: VJoyPanel
|
|
3
|
+
status: draft
|
|
4
|
+
language: en
|
|
5
|
+
dsds: JoyPanel.dsds.json
|
|
6
|
+
dsdsVersion: "0.15.2"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Panel
|
|
10
|
+
|
|
11
|
+
Use **`VJoyPanel`** when content should sit inside a bordered card-like container with an optional header (title, subtitle, actions), a main body, and optional footer CTAs.
|
|
12
|
+
|
|
13
|
+
It is a layout primitive: slot-driven structure, Joy spacing tokens, responsive padding, a loading overlay, and an `ai` border variant for AI-related surfaces. Nest **`VJoyPanelSection`** inside `panel-body` when the body needs titled sub-blocks.
|
|
14
|
+
|
|
15
|
+
| | |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| **Code** | `VJoyPanel` from `@maltjoy/core-vue` |
|
|
18
|
+
| **Design** | [ZeroHeight — Panel](https://zeroheight.com/6bf479565/p/3095627-panel) |
|
|
19
|
+
| **Storybook** | [VJoyPanel docs](https://doc.malt.tech/joy-storybook/?path=/docs/components-layout-vjoypanel--docs) |
|
|
20
|
+
| **DSDS (machine)** | [`JoyPanel.dsds.json`](./JoyPanel.dsds.json) |
|
|
21
|
+
| **Live API** | `joy_get_component_info("VJoyPanel")` |
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## When to use this component
|
|
26
|
+
|
|
27
|
+
Use a panel to group related content on a page or inside a wider layout — settings blocks, summary cards, form sections with a title and confirm/cancel row, or dashboard tiles that need a consistent border and padding.
|
|
28
|
+
|
|
29
|
+
Put the primary copy and controls in `panel-body`. Use `panel-title` / `panel-subtitle` for the section heading, `panel-title-action` for header utilities (edit, overflow menu), and `panel-action` for bottom-aligned CTAs (often a `VJoyWrapper` of buttons).
|
|
30
|
+
|
|
31
|
+
Set `loading` when the panel’s content is fetching or saving; optional `panel-loader-content` customizes the overlay message.
|
|
32
|
+
|
|
33
|
+
### When not to use this component
|
|
34
|
+
|
|
35
|
+
| Need | Use instead |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| Full-page chrome (header, sidebar, back link, page loader) | `VJoyTemplate` |
|
|
38
|
+
| Multi-step funnel layout | `VJoyFunnel` |
|
|
39
|
+
| Modal or drawer with open/close lifecycle | `VJoyDialog` / `VJoyDrawer` |
|
|
40
|
+
| Generic flex row/column without card chrome | `VJoyWrapper` |
|
|
41
|
+
| Inline highlight / alert banner (no card frame) | `VJoyHighlight` |
|
|
42
|
+
| Custom `<div>` + border-radius CSS for the same job | Still use `VJoyPanel` + Joy tokens |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## How it works
|
|
47
|
+
|
|
48
|
+
### Anatomy
|
|
49
|
+
|
|
50
|
+
| Part | Required? | What it is |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
| Root `<section>` | Yes | `joy-panel`; receives `flex` inline style when set |
|
|
53
|
+
| Header | No* | Rendered when any header slot is present (`panel-title`, `panel-subtitle`, `panel-title-action`, `panel-title-icon`) |
|
|
54
|
+
| Title row | No | `panel-title` (+ optional `panel-title-icon` before it); typography from `titleSize` |
|
|
55
|
+
| Title action | No | `panel-title-action` — top-right header utilities |
|
|
56
|
+
| Subtitle | No | `panel-subtitle` — full-width under the title row |
|
|
57
|
+
| Body | Yes** | `panel-body` — main content area |
|
|
58
|
+
| Footer | No | `panel-action` — typically CTAs with top spacing |
|
|
59
|
+
| Loading overlay | No | `VJoyScreenLoader` when `loading` is true; blurs header/body/footer |
|
|
60
|
+
| Loader message | No | `panel-loader-content` slot inside the overlay |
|
|
61
|
+
|
|
62
|
+
\*Header omitted when no header slots are provided.
|
|
63
|
+
\*\*The slot is always available; omit content only when the panel is intentionally empty (e.g. skeleton parent).
|
|
64
|
+
|
|
65
|
+
### Default values
|
|
66
|
+
|
|
67
|
+
- `padding` defaults to **`medium`** (`small` | `medium` | `large`)
|
|
68
|
+
- `variant` defaults to **`default`** (`default` | `ai`)
|
|
69
|
+
- `titleSize` defaults to **`large`** (`small` | `large`)
|
|
70
|
+
- `loading` defaults to **`false`**
|
|
71
|
+
- `loadingColor` defaults to **`light`** (`light` | `dark`)
|
|
72
|
+
- `noMargin` defaults to **`false`**
|
|
73
|
+
- Bottom margin is also suppressed automatically when the panel is a direct child of **`VJoyWrapper`** (injected context)
|
|
74
|
+
|
|
75
|
+
Props and slots: use Joy MCP or Storybook — this page focuses on **when** and **how**, not a full API dump.
|
|
76
|
+
|
|
77
|
+
### Composition notes
|
|
78
|
+
|
|
79
|
+
- Use semantic heading levels inside `panel-title` (e.g. `<h2>`) — the panel does not render a heading element for you.
|
|
80
|
+
- Stack **`VJoyPanelSection`** components in `panel-body` for repeated titled blocks; spacing between sections is handled by panel CSS.
|
|
81
|
+
- For footer actions, align buttons with **`VJoyWrapper`** (`justify="flex-end"` is common).
|
|
82
|
+
- Set `variant="ai"` only for AI-feature surfaces; it changes the border token to `--joy-color-ai-primary-30`.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Best practices
|
|
87
|
+
|
|
88
|
+
Rules use RFC 2119 strength (**MUST** / **SHOULD** / **MUST NOT** / **SHOULD NOT**).
|
|
89
|
+
|
|
90
|
+
### Structure
|
|
91
|
+
|
|
92
|
+
- **SHOULD** provide `panel-title` when the panel needs a visible section name; pair with `panel-subtitle` for supporting context.
|
|
93
|
+
- **SHOULD** reserve `panel-action` for primary/secondary actions tied to the panel content — not global page actions.
|
|
94
|
+
- **SHOULD** use `VJoyPanelSection` inside `panel-body` when the body has multiple titled subsections.
|
|
95
|
+
- **SHOULD** set `loading` during async refresh of the panel’s own data, not for whole-page navigation.
|
|
96
|
+
|
|
97
|
+
### Implementation
|
|
98
|
+
|
|
99
|
+
- **MUST** use only documented props and enums from MCP / component types (`padding`, `variant`, `titleSize`, `loading`, `loadingColor`, `noMargin`, `flex`).
|
|
100
|
+
- **MUST** use Joy tokens — no custom panel border, radius, or background in product CSS.
|
|
101
|
+
- **MUST NOT** invent props (`size`, `theme`, `elevation`, …).
|
|
102
|
+
- **MUST NOT** override `.joy-panel` padding, border, or `--panel-bottom-margin` with hardcoded pixel values.
|
|
103
|
+
- **SHOULD** set `noMargin` (or place the panel in `VJoyWrapper`) when stacking panels in a flex column — avoid double vertical spacing.
|
|
104
|
+
- **SHOULD** use `titleSize="small"` for nested or secondary panels; keep `large` for primary section titles.
|
|
105
|
+
|
|
106
|
+
### Do / don’t
|
|
107
|
+
|
|
108
|
+
| Do | Don’t |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| `panel-title` + `panel-body` + optional `panel-action` | Empty bordered box with no title or body purpose |
|
|
111
|
+
| `VJoyPanelSection` for multi-block bodies | Manual `<hr>` + ad-hoc margins between blocks |
|
|
112
|
+
| `variant="ai"` on AI surfaces only | `ai` variant for generic marketing cards |
|
|
113
|
+
| `loading` + `panel-loader-content` for panel-scoped fetch | Full-page loader for a small inline refresh |
|
|
114
|
+
| `VJoyWrapper` in `panel-action` for button alignment | Absolutely positioned footer buttons |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Accessibility
|
|
119
|
+
|
|
120
|
+
`VJoyPanel` renders a **`<section>`** landmark. It does not supply headings or labels — those come from slot content.
|
|
121
|
+
|
|
122
|
+
| Behavior | Expectation |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| Landmark | Root is `<section>` — use when the panel is a distinct document region |
|
|
125
|
+
| Naming | Put a real heading in `panel-title` (`<h2>`–`<h4>` matching page outline) so the section has an accessible name |
|
|
126
|
+
| Subtitle | `panel-subtitle` is plain content — associate visually with the title; don’t rely on it alone for critical instructions |
|
|
127
|
+
| Header actions | Controls in `panel-title-action` need their own accessible names (`label` on `VJoyIconButton`, visible text on links) |
|
|
128
|
+
| Footer actions | Use `VJoyButton` / `VJoyLink` with clear labels; order primary action consistently (often last in LTR layouts) |
|
|
129
|
+
| Loading | `loading` blurs content and shows an overlay — ensure `panel-loader-content` (or loader default) is readable; avoid trapping keyboard focus inside a non-modal panel overlay |
|
|
130
|
+
| Focus | The panel wrapper is not focusable; focus moves to interactive children in slots |
|
|
131
|
+
|
|
132
|
+
Target: **WCAG 2.2 AA** where the product requires it — meet requirements through proper headings, button labels, and loading feedback in slot content.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Examples
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<script setup lang="ts">
|
|
140
|
+
import { ref } from 'vue'
|
|
141
|
+
import {
|
|
142
|
+
VJoyPanel,
|
|
143
|
+
VJoyPanelSection,
|
|
144
|
+
VJoyButton,
|
|
145
|
+
VJoyWrapper,
|
|
146
|
+
VJoyIcon,
|
|
147
|
+
} from '@maltjoy/core-vue'
|
|
148
|
+
|
|
149
|
+
const saving = ref(false)
|
|
150
|
+
|
|
151
|
+
async function save() {
|
|
152
|
+
saving.value = true
|
|
153
|
+
// await api.save()
|
|
154
|
+
saving.value = false
|
|
155
|
+
}
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<template>
|
|
159
|
+
<!-- Standard panel with header, body, footer -->
|
|
160
|
+
<VJoyPanel>
|
|
161
|
+
<template #panel-title>
|
|
162
|
+
<h2>Notification preferences</h2>
|
|
163
|
+
</template>
|
|
164
|
+
<template #panel-subtitle>
|
|
165
|
+
Choose how we contact you about project updates.
|
|
166
|
+
</template>
|
|
167
|
+
<template #panel-body>
|
|
168
|
+
<!-- form fields, lists, etc. -->
|
|
169
|
+
</template>
|
|
170
|
+
<template #panel-action>
|
|
171
|
+
<VJoyWrapper justify="flex-end">
|
|
172
|
+
<VJoyButton variant="ghost" size="xxsmall" label="Cancel" />
|
|
173
|
+
<VJoyButton variant="main" size="xxsmall" label="Save" @click="save" />
|
|
174
|
+
</VJoyWrapper>
|
|
175
|
+
</template>
|
|
176
|
+
</VJoyPanel>
|
|
177
|
+
|
|
178
|
+
<!-- Title icon + header action -->
|
|
179
|
+
<VJoyPanel title-size="small">
|
|
180
|
+
<template #panel-title-icon>
|
|
181
|
+
<VJoyIcon name="star" size="medium" />
|
|
182
|
+
</template>
|
|
183
|
+
<template #panel-title>
|
|
184
|
+
<h3>Featured</h3>
|
|
185
|
+
</template>
|
|
186
|
+
<template #panel-title-action>
|
|
187
|
+
<VJoyButton variant="ghost" size="xxsmall" label="Edit" />
|
|
188
|
+
</template>
|
|
189
|
+
<template #panel-body>
|
|
190
|
+
<p>Summary content…</p>
|
|
191
|
+
</template>
|
|
192
|
+
</VJoyPanel>
|
|
193
|
+
|
|
194
|
+
<!-- Nested sections + loading -->
|
|
195
|
+
<VJoyPanel :loading="saving" padding="large">
|
|
196
|
+
<template #panel-title>
|
|
197
|
+
<h2>Billing details</h2>
|
|
198
|
+
</template>
|
|
199
|
+
<template #panel-body>
|
|
200
|
+
<VJoyPanelSection>
|
|
201
|
+
<template #panel-section-title>Payment method</template>
|
|
202
|
+
<template #panel-section-content>…</template>
|
|
203
|
+
</VJoyPanelSection>
|
|
204
|
+
<VJoyPanelSection>
|
|
205
|
+
<template #panel-section-title>Invoice address</template>
|
|
206
|
+
<template #panel-section-content>…</template>
|
|
207
|
+
</VJoyPanelSection>
|
|
208
|
+
</template>
|
|
209
|
+
<template #panel-loader-content>Updating billing…</template>
|
|
210
|
+
</VJoyPanel>
|
|
211
|
+
|
|
212
|
+
<!-- AI variant -->
|
|
213
|
+
<VJoyPanel variant="ai">
|
|
214
|
+
<template #panel-title>
|
|
215
|
+
<h2>AI suggestions</h2>
|
|
216
|
+
</template>
|
|
217
|
+
<template #panel-body>
|
|
218
|
+
<p>Generated recommendations…</p>
|
|
219
|
+
</template>
|
|
220
|
+
</VJoyPanel>
|
|
221
|
+
</template>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
In Nuxt (or apps with auto-import), omit the import if `VJoyPanel` is already global.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Related
|
|
229
|
+
|
|
230
|
+
| Component | Use when |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `VJoyPanelSection` | Titled sub-blocks inside `panel-body` |
|
|
233
|
+
| `VJoyWrapper` | Flex layout for footer buttons or stacked panels (auto `no-margin`) |
|
|
234
|
+
| `VJoyTemplate` | Full page layout with header, content, and sidebar slots |
|
|
235
|
+
| `VJoyDialog` / `VJoyDrawer` | Focus-trapped overlay with its own header/footer patterns |
|
|
236
|
+
| `VJoyHighlight` | Inline status or info banner without card chrome |
|
|
237
|
+
| `VJoyScreenLoader` | Standalone overlay (panel embeds it when `loading` is true) |
|
|
238
|
+
| `VJoyButton` | Primary/secondary actions in `panel-action` or `panel-title-action` |
|