prime-ui-kit 0.13.0 → 0.14.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/README.md +7 -6
- package/dist/chunks/{chunk-ZSJMNA46.js → chunk-P6ZO37JM.js} +1 -1
- package/dist/chunks/{chunk-Z3WKKAS6.js → chunk-SYEX7OVT.js} +3572 -2865
- package/dist/chunks/{chunk-E6SQBSDF.js → chunk-WXKRUVSE.js} +199 -162
- package/dist/color-picker.js +2 -2
- package/dist/components/banner/Banner.d.ts +5 -2
- package/dist/components/chart-frame/ChartFrame.d.ts +19 -6
- package/dist/components/chat/Chat.d.ts +2 -2
- package/dist/components/chat/ChatPinned.d.ts +2 -2
- package/dist/components/chat/MessageList.d.ts +1 -1
- package/dist/components/chat/chatContext.d.ts +1 -1
- package/dist/components/field-list/FieldList.d.ts +90 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/input/Input.d.ts +2 -2
- package/dist/components/kanban/Kanban.d.ts +6 -3
- package/dist/components/page-content/PageContent.d.ts +55 -4
- package/dist/components/sparkline/Sparkline.d.ts +64 -15
- package/dist/components/sparkline/geometry.d.ts +26 -0
- package/dist/components/tooltip/Tooltip.d.ts +5 -2
- package/dist/hooks/usePosition.d.ts +15 -4
- package/dist/index.css +1 -1
- package/dist/index.js +4 -2
- package/dist/internal/ChartTooltip.d.ts +26 -0
- package/dist/internal/FieldAction.d.ts +6 -7
- package/dist/internal/fieldCell.d.ts +21 -0
- package/dist/internal/messageList.d.ts +2 -2
- package/dist/internal/palette.d.ts +7 -0
- package/dist/skill/SKILL.md +24 -14
- package/dist/skill/anti-slop.md +77 -4
- package/dist/skill/cheatsheet.md +24 -5
- package/dist/skill/checklist.md +19 -1
- package/dist/skill/choosing.md +10 -5
- package/dist/skill/components.md +4 -3
- package/dist/skill/composition.md +28 -15
- package/dist/skill/foundations.md +8 -6
- package/dist/skill/layouts.md +6 -6
- package/dist/skill/patterns/app-shell.module.css +1 -8
- package/dist/skill/patterns/app-shell.tsx +11 -9
- package/dist/skill/patterns/chat.tsx +23 -5
- package/dist/skill/patterns/dashboard.module.css +1 -31
- package/dist/skill/patterns/dashboard.tsx +150 -136
- package/dist/skill/patterns/detail-page.module.css +0 -3
- package/dist/skill/patterns/detail-page.tsx +4 -7
- package/dist/skill/patterns/form-drawer.tsx +2 -2
- package/dist/skill/patterns/kanban-board.tsx +4 -2
- package/dist/skill/patterns/list-page.tsx +2 -2
- package/dist/skill/patterns/media-library.module.css +0 -22
- package/dist/skill/patterns/media-library.tsx +17 -18
- package/dist/skill/patterns/record-editor.module.css +18 -0
- package/dist/skill/patterns/record-editor.tsx +356 -0
- package/dist/skill/patterns/screen-states.module.css +3 -17
- package/dist/skill/patterns/screen-states.tsx +70 -69
- package/dist/skill/patterns/settings-page.module.css +1 -21
- package/dist/skill/patterns/settings-page.tsx +58 -79
- package/dist/skill/reference/components/avatar/COMPONENT.md +4 -4
- package/dist/skill/reference/components/avatar/examples/presence.tsx +1 -1
- package/dist/skill/reference/components/avatar/examples/states.tsx +1 -1
- package/dist/skill/reference/components/banner/COMPONENT.md +6 -6
- package/dist/skill/reference/components/banner/examples/dismissible.tsx +22 -11
- package/dist/skill/reference/components/banner/examples/examples.module.css +3 -3
- package/dist/skill/reference/components/banner/examples/narrow.tsx +1 -1
- package/dist/skill/reference/components/breadcrumb/COMPONENT.md +1 -1
- package/dist/skill/reference/components/breadcrumb/examples/overflow.tsx +1 -1
- package/dist/skill/reference/components/card/COMPONENT.md +5 -4
- package/dist/skill/reference/components/card/examples/examples.module.css +0 -28
- package/dist/skill/reference/components/card/examples/kpi-media.tsx +13 -11
- package/dist/skill/reference/components/card/examples/narrow.tsx +1 -1
- package/dist/skill/reference/components/card/examples/panel-chart.tsx +45 -22
- package/dist/skill/reference/components/chart-frame/COMPONENT.md +10 -7
- package/dist/skill/reference/components/chart-frame/examples/tooltip.tsx +20 -24
- package/dist/skill/reference/components/chat/COMPONENT.md +3 -3
- package/dist/skill/reference/components/chat/examples/search.tsx +1 -1
- package/dist/skill/reference/components/chat-list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/checkbox/examples/indicator.tsx +24 -2
- package/dist/skill/reference/components/color-picker/COMPONENT.md +2 -2
- package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
- package/dist/skill/reference/components/datepicker/COMPONENT.md +1 -0
- package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
- package/dist/skill/reference/components/field-list/COMPONENT.md +164 -0
- package/dist/skill/reference/components/field-list/examples/base-row.tsx +76 -0
- package/dist/skill/reference/components/field-list/examples/controlled.tsx +72 -0
- package/dist/skill/reference/components/field-list/examples/errors.tsx +85 -0
- package/dist/skill/reference/components/field-list/examples/examples.module.css +34 -0
- package/dist/skill/reference/components/field-list/examples/limits.tsx +55 -0
- package/dist/skill/reference/components/field-list/examples/locked.tsx +139 -0
- package/dist/skill/reference/components/field-list/examples/narrow.tsx +68 -0
- package/dist/skill/reference/components/field-list/examples/overview.tsx +66 -0
- package/dist/skill/reference/components/field-list/examples/reorder.tsx +42 -0
- package/dist/skill/reference/components/field-list/examples/sizes.tsx +42 -0
- package/dist/skill/reference/components/field-list/examples/states.tsx +51 -0
- package/dist/skill/reference/components/input/examples/examples.module.css +2 -7
- package/dist/skill/reference/components/kanban/COMPONENT.md +13 -9
- package/dist/skill/reference/components/kanban/examples/limits.tsx +2 -1
- package/dist/skill/reference/components/kanban/examples/variants.tsx +16 -8
- package/dist/skill/reference/components/list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/media-viewer/COMPONENT.md +2 -2
- package/dist/skill/reference/components/message/COMPONENT.md +2 -2
- package/dist/skill/reference/components/notification/COMPONENT.md +1 -0
- package/dist/skill/reference/components/page-content/COMPONENT.md +70 -14
- package/dist/skill/reference/components/page-content/examples/grid.tsx +107 -0
- package/dist/skill/reference/components/page-content/examples/overview.tsx +2 -2
- package/dist/skill/reference/components/page-content/examples/sections.tsx +81 -0
- package/dist/skill/reference/components/page-toolbar/COMPONENT.md +1 -1
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
- package/dist/skill/reference/components/select/COMPONENT.md +4 -2
- package/dist/skill/reference/components/select/examples/icon-only.tsx +38 -0
- package/dist/skill/reference/components/sparkline/COMPONENT.md +83 -30
- package/dist/skill/reference/components/sparkline/examples/compact.tsx +1 -1
- package/dist/skill/reference/components/sparkline/examples/curve.tsx +28 -0
- package/dist/skill/reference/components/sparkline/examples/gaps.tsx +33 -0
- package/dist/skill/reference/components/sparkline/examples/overview.tsx +11 -2
- package/dist/skill/reference/components/sparkline/examples/period.tsx +7 -2
- package/dist/skill/reference/components/sparkline/examples/reference.tsx +37 -0
- package/dist/skill/reference/components/sparkline/examples/variants.tsx +2 -2
- package/dist/skill/reference/components/tabs/examples/examples.module.css +0 -6
- package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +11 -13
- package/dist/skill/reference/components/timeline/examples/examples.module.css +0 -8
- package/dist/skill/reference/components/timeline/examples/narrow.tsx +2 -2
- package/dist/skill/reference/components/timeline/examples/sizes.tsx +3 -3
- package/dist/skill/reference/components/timeline/examples/variants.tsx +4 -4
- package/dist/skill/reference/components/tooltip/COMPONENT.md +6 -4
- package/dist/skill/reference/components/tooltip/examples/placement.tsx +2 -1
- package/dist/skill/reference/components/video-note/COMPONENT.md +1 -1
- package/dist/skill/reference/components/voice-player/COMPONENT.md +1 -1
- package/dist/skill/reference/layout/app-header/examples/in-app-shell.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
- package/dist/skill/reference/layout/app-shell/examples/footer.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/examples/overview.tsx +2 -2
- package/dist/skill/reference/layout/app-shell/examples/template.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/floating-action.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/floating.tsx +2 -2
- package/dist/skill/reference/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
- package/dist/skill/responsive.md +5 -3
- package/dist/skill/ux.md +145 -0
- package/dist/tokens/semantic.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/avatar/COMPONENT.md +4 -4
- package/src/components/avatar/examples/presence.tsx +1 -1
- package/src/components/avatar/examples/states.tsx +1 -1
- package/src/components/banner/COMPONENT.md +6 -6
- package/src/components/banner/examples/dismissible.tsx +22 -11
- package/src/components/banner/examples/examples.module.css +3 -3
- package/src/components/banner/examples/narrow.tsx +1 -1
- package/src/components/breadcrumb/COMPONENT.md +1 -1
- package/src/components/breadcrumb/examples/overflow.tsx +1 -1
- package/src/components/card/COMPONENT.md +5 -4
- package/src/components/card/examples/examples.module.css +0 -28
- package/src/components/card/examples/kpi-media.tsx +13 -11
- package/src/components/card/examples/narrow.tsx +1 -1
- package/src/components/card/examples/panel-chart.tsx +45 -22
- package/src/components/chart-frame/COMPONENT.md +10 -7
- package/src/components/chart-frame/examples/tooltip.tsx +20 -24
- package/src/components/chat/COMPONENT.md +3 -3
- package/src/components/chat/examples/search.tsx +1 -1
- package/src/components/chat-list/COMPONENT.md +1 -1
- package/src/components/checkbox/examples/indicator.tsx +24 -2
- package/src/components/color-picker/COMPONENT.md +2 -2
- package/src/components/color-swatches/COMPONENT.md +1 -0
- package/src/components/datepicker/COMPONENT.md +1 -0
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/field-list/COMPONENT.md +164 -0
- package/src/components/field-list/examples/base-row.tsx +76 -0
- package/src/components/field-list/examples/controlled.tsx +72 -0
- package/src/components/field-list/examples/errors.tsx +85 -0
- package/src/components/field-list/examples/examples.module.css +34 -0
- package/src/components/field-list/examples/limits.tsx +55 -0
- package/src/components/field-list/examples/locked.tsx +139 -0
- package/src/components/field-list/examples/narrow.tsx +68 -0
- package/src/components/field-list/examples/overview.tsx +66 -0
- package/src/components/field-list/examples/reorder.tsx +42 -0
- package/src/components/field-list/examples/sizes.tsx +42 -0
- package/src/components/field-list/examples/states.tsx +51 -0
- package/src/components/input/examples/examples.module.css +2 -7
- package/src/components/kanban/COMPONENT.md +13 -9
- package/src/components/kanban/examples/limits.tsx +2 -1
- package/src/components/kanban/examples/variants.tsx +16 -8
- package/src/components/list/COMPONENT.md +1 -1
- package/src/components/media-viewer/COMPONENT.md +2 -2
- package/src/components/message/COMPONENT.md +2 -2
- package/src/components/notification/COMPONENT.md +1 -0
- package/src/components/page-content/COMPONENT.md +70 -14
- package/src/components/page-content/examples/grid.tsx +107 -0
- package/src/components/page-content/examples/overview.tsx +2 -2
- package/src/components/page-content/examples/sections.tsx +81 -0
- package/src/components/page-toolbar/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +1 -0
- package/src/components/select/COMPONENT.md +4 -2
- package/src/components/select/examples/icon-only.tsx +38 -0
- package/src/components/sparkline/COMPONENT.md +83 -30
- package/src/components/sparkline/examples/compact.tsx +1 -1
- package/src/components/sparkline/examples/curve.tsx +28 -0
- package/src/components/sparkline/examples/gaps.tsx +33 -0
- package/src/components/sparkline/examples/overview.tsx +11 -2
- package/src/components/sparkline/examples/period.tsx +7 -2
- package/src/components/sparkline/examples/reference.tsx +37 -0
- package/src/components/sparkline/examples/variants.tsx +2 -2
- package/src/components/tabs/examples/examples.module.css +0 -6
- package/src/components/tabs/examples/in-drawer.tsx +11 -13
- package/src/components/timeline/examples/examples.module.css +0 -8
- package/src/components/timeline/examples/narrow.tsx +2 -2
- package/src/components/timeline/examples/sizes.tsx +3 -3
- package/src/components/timeline/examples/variants.tsx +4 -4
- package/src/components/tooltip/COMPONENT.md +6 -4
- package/src/components/tooltip/examples/placement.tsx +2 -1
- package/src/components/video-note/COMPONENT.md +1 -1
- package/src/components/voice-player/COMPONENT.md +1 -1
- package/src/layout/app-header/examples/in-app-shell.tsx +2 -2
- package/src/layout/app-shell/COMPONENT.md +1 -1
- package/src/layout/app-shell/examples/footer.tsx +2 -2
- package/src/layout/app-shell/examples/overview.tsx +2 -2
- package/src/layout/app-shell/examples/template.tsx +2 -2
- package/src/layout/bottom-nav/examples/floating-action.tsx +2 -2
- package/src/layout/bottom-nav/examples/floating.tsx +2 -2
- package/src/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
- package/src/styles/theme-light.css +1 -0
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
```
|
|
33
33
|
Kanban.Root board: horizontal strip of columns, its own Dnd.Root if none above
|
|
34
34
|
└─ <ul> (Dnd.Sortable, axis x) the columns in `columnOrder`
|
|
35
|
-
└─ <li> column (from `columns`) `
|
|
35
|
+
└─ <li> column (from `columns`) no well with a header plate (`plain`) or a header line (`line`), or a `fill-muted` well (`well`)
|
|
36
36
|
├─ header title (h3) · count Badge (the stage color) · renderColumnActions(column) · [grip Dnd.Handle, a lock when `locked`]
|
|
37
37
|
│ ├─ description muted line under the title (`column.description`)
|
|
38
38
|
│ └─ meta the stage's figures (`renderColumnMeta(column)`)
|
|
@@ -73,7 +73,7 @@ Generic `Kanban.Root<T>`; `ref` → `HTMLDivElement`. The board: a strip of colu
|
|
|
73
73
|
| `renderColumnActions` | `(column: KanbanColumn) => ReactNode` | — | Trailing actions of a column header (add a card, a menu); kit controls inside take the `s` tier. |
|
|
74
74
|
| `renderColumnMeta` | `(column: KanbanColumn) => ReactNode` | — | The meta line under a column's title and description: the stage's figures («12 сделок · 4,8 млн ₽»). |
|
|
75
75
|
| `canDrop` | `(id: string, columnId: string) => boolean \| string` | — | A workflow rule: refuses card `id` in column `columnId` — `false`, or a string that says why (spoken and shown under the carried card). |
|
|
76
|
-
| `variant` | `"
|
|
76
|
+
| `variant` | `"plain" \| "line" \| "well"` | `"plain"` | No wells: `plain` — a filled header plate over the cards, `line` — a bare header over a faint line; cards stand on the host. `well`: every column is a filled well holding its cards (bounded queues). |
|
|
77
77
|
| `loading` | `boolean` | `false` | Columns show skeleton cards in the card geometry; the cards cross-fade in when it turns off. |
|
|
78
78
|
| `disabled` | `boolean` | `false` | Read-only board: no drag, no keyboard moves, no column grips; `onItemClick` and `getItemHref` still open cards. |
|
|
79
79
|
| `labels` | `Partial<KanbanLabels>` | — | Built-in strings, see Labels. |
|
|
@@ -126,8 +126,11 @@ Generic `Kanban.Root<T>`; `ref` → `HTMLDivElement`. The board: a strip of colu
|
|
|
126
126
|
|
|
127
127
|
| Value | Looks like | Use when | Default |
|
|
128
128
|
|---|---|---|---|
|
|
129
|
-
| `
|
|
130
|
-
| `
|
|
129
|
+
| `plain` | no wells: the column is its header — a filled plate as wide as the cards (title, count) — and its cards, which stand on the board's host, each a surface one ladder step off it (white on the light page), as a Card on the page | the board is the page's main region (a pipeline, a task board, a queue screen) or sits in a Card or Drawer — one level of nesting, cards are the only boxes | yes |
|
|
130
|
+
| `line` | no wells, like `plain`, but the header is bare: title and count over a faint inset hairline as wide as the cards | the same places as `plain` when the headers should stay quiet — many columns, a calm board, the column headers are not the focus | |
|
|
131
|
+
| `well` | every column is a filled well (`fill-muted`, radius `l`) holding its cards (the selected step of the well) | columns must read as bounded queues: WIP limits and capacity, many sparse or empty columns whose drop area must show, a dense board of many narrow columns | |
|
|
132
|
+
|
|
133
|
+
Do not wrap a `plain` board's columns in your own boxes, and do not put a `well` board inside a filled box of your own — both are the double nesting the variants exist to avoid.
|
|
131
134
|
|
|
132
135
|
### Flags
|
|
133
136
|
| Value | Looks like | Use when | Default |
|
|
@@ -167,11 +170,11 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
167
170
|
- Give the root a height (`className`): columns span it and every column body scrolls on its own; without one the columns take the height of the tallest.
|
|
168
171
|
- The board is a size container. From 40rem of its own width columns keep a fixed 18rem; below it a column takes 85% of the board and snaps (`scroll-snap-type: x mandatory`), so the next column peeks. Snapping is off while a drag auto-scrolls the strip.
|
|
169
172
|
- The strip scrolls sideways inside the board, never the page. Cards sit at the top of their column; the drop area runs to the bottom of the column, so a card can be released anywhere in it.
|
|
170
|
-
- `
|
|
173
|
+
- `plain` / `line`: column gap `--prime-space-3`, cards inset `--prime-space-1` (room for the focus ring); a card sets `data-depth` one above the board's host and paints `--prime-color-layer-current`, so it keeps its color in flight too. `well`: column gap `--prime-space-3`, cards inset `--prime-space-2` from the column edge, column radius `l`. Card gap `--prime-space-2`, card padding `--prime-space-3`, card radius `m`.
|
|
171
174
|
- The column title is `title-m`; the header is at least a control `m` tall, its text starts where the card text does.
|
|
172
|
-
-
|
|
175
|
+
- `well` and `line`: a faint inset hairline (`--prime-color-border-faint`, as in a dialog header) separates the header from the cards. `plain`: the header is a filled plate (`--prime-color-fill-muted`, `--prime-radius-l`) as wide as the cards, in place of the hairline.
|
|
173
176
|
- The header is a grid: the title, the count, the actions and the grip at the end on the first row; the description under the title on the second, the meta line under it (`--prime-space-1` above, caption, `text-secondary`).
|
|
174
|
-
- A card is `--prime-color-control-selected` (two ladder steps off its
|
|
177
|
+
- A card is on `--prime-shadow-raised`: in `plain` / `line` the next layer of the board's host (`--prime-color-layer-current` of its own depth), in `well` `--prime-color-control-selected` (two ladder steps off its well; white in light).
|
|
175
178
|
- `Kanban.ItemHeader` is a muted caption row over a faint hairline (`--prime-color-border-faint`); a card without it starts with its title or a full-width `Thumbnail` cover.
|
|
176
179
|
- Header actions get the `s` tier; inside a card, kit controls take the `m` host tier (a `Badge` is `s`). Pick a small `Avatar` (`size="s"`).
|
|
177
180
|
|
|
@@ -215,10 +218,10 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
215
218
|
| Example | Shows |
|
|
216
219
|
|---|---|
|
|
217
220
|
| [overview.tsx](examples/overview.tsx) | A release board: cards move within and between columns by drag, a touch hold or Alt + arrows; a card shows its meta line, labels, comments and assignee — `Kanban.Root`, `Kanban.Item`, `renderItem`, `renderColumnActions`. |
|
|
218
|
-
| [variants.tsx](examples/variants.tsx) | The same release board in
|
|
221
|
+
| [variants.tsx](examples/variants.tsx) | The same release board in three looks: no wells with a header plate, no wells with a header over a line, and columns as filled wells holding their cards — `variant`. |
|
|
219
222
|
| [structure.tsx](examples/structure.tsx) | Cards take only the parts they need: a title alone, a meta line, labels, a footer with counts and an assignee — `Kanban.ItemTitle`, `Kanban.ItemDescription`, `Kanban.ItemBadges`, `Kanban.ItemFooter`, `Kanban.ItemCount`. |
|
|
220
223
|
| [cards.tsx](examples/cards.tsx) | Three kinds of cards on one board: a plain note, a ticket with a header row of key and priority, a design task with a cover — `Kanban.ItemHeader`, `Thumbnail`, `Kanban.ItemFooter`. |
|
|
221
|
-
| [limits.tsx](examples/limits.tsx) | A support queue with a work-in-progress limit and a workflow rule: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`. |
|
|
224
|
+
| [limits.tsx](examples/limits.tsx) | A support queue with a work-in-progress limit and a workflow rule, its columns drawn as wells so each reads as a bounded queue: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`, `variant`. |
|
|
222
225
|
| [columns.tsx](examples/columns.tsx) | A sales pipeline whose stages move by the grip in their header while the first and the last stay locked; each stage carries its color on the count, a rule and its figures under the title, and is edited in a popover — `reorderableColumns`, `onColumnOrderChange`, `locked`, `color`, `description`, `renderColumnMeta`, `renderColumnActions`. |
|
|
223
226
|
| [saving.tsx](examples/saving.tsx) | Every move is saved on the server: the card waits busy in its new place, and an invoice the server refuses to close glides back to its column — `onValueChange`. |
|
|
224
227
|
| [item-link.tsx](examples/item-link.tsx) | Tickets that open: each card is a link to its page, so ⌘-click or a middle click opens a new tab, a plain click, Enter or Space opens it in place, and a drag only moves it — `getItemHref`, `onItemClick`. |
|
|
@@ -234,6 +237,7 @@ A card's own `onKeyDown` runs before the board's: `preventDefault()` in it keeps
|
|
|
234
237
|
- A hover-only action on a card → touch has no hover; put the action in the card or open the card on click (`onItemClick`).
|
|
235
238
|
- `onClick` / `onKeyDown` on `Kanban.Item` to open a card → `onItemClick` (click, Enter, Space, never after a drag) or `getItemHref` for a page of its own.
|
|
236
239
|
- `getItemHref` without `getLabel` → the link is named by the card id; pass a human name.
|
|
240
|
+
- Drawing your own box (a Card, a filled `div`) around each column, or around a `well` board → pick the `variant`: `plain` or `line` for one level of nesting, `well` when columns must read as bounded queues.
|
|
237
241
|
- Styling a card from an ancestor (`.board .card`) → the flying clone is portalled to `<body>` and loses it; put the class on `Kanban.Item`.
|
|
238
242
|
- Keeping the column order in `columns` and reordering that array → pass `columnOrder` / `onColumnOrderChange`; `columns` stays the data.
|
|
239
243
|
- Putting a system stage back first after every move → `locked: true` on that column.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A support queue with a work-in-progress limit and a workflow rule: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`. */
|
|
1
|
+
/** A support queue with a work-in-progress limit and a workflow rule, its columns drawn as wells so each reads as a bounded queue: a full column and a blocked ticket are refused before the release with the reason under the card, from the keyboard too — `limit`, `canDrop`, `variant`. */
|
|
2
2
|
import { Badge, Kanban } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
@@ -33,6 +33,7 @@ export default function KanbanLimitsExample() {
|
|
|
33
33
|
<Kanban.Root
|
|
34
34
|
aria-label="Очередь поддержки"
|
|
35
35
|
className={styles.board}
|
|
36
|
+
variant="well"
|
|
36
37
|
columns={COLUMNS}
|
|
37
38
|
items={TICKETS}
|
|
38
39
|
getId={(ticket) => ticket.id}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
/** The same release board in
|
|
2
|
-
import { Kanban, type KanbanVariant, Typography } from "prime-ui-kit";
|
|
1
|
+
/** The same release board in three looks: no wells with a header plate, no wells with a header over a line, and columns as filled wells holding their cards — `variant`. */
|
|
2
|
+
import { Badge, Kanban, type KanbanVariant, Typography } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
5
5
|
|
|
6
|
-
type Task = { id: string; title: string; due: string };
|
|
6
|
+
type Task = { id: string; title: string; due: string; label: string };
|
|
7
7
|
|
|
8
|
-
const VARIANTS: KanbanVariant[] = ["
|
|
8
|
+
const VARIANTS: KanbanVariant[] = ["plain", "line", "well"];
|
|
9
9
|
|
|
10
10
|
const COLUMNS = [
|
|
11
11
|
{ id: "todo", title: "К выполнению", description: "Берём после планирования" },
|
|
@@ -14,10 +14,15 @@ const COLUMNS = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
const TASKS: Task[] = [
|
|
17
|
-
{ id: "PRJ-214", title: "Экспорт счетов в CSV для бухгалтерии", due: "14 окт" },
|
|
18
|
-
{ id: "PRJ-221", title: "Фильтр заказов по складу", due: "17 окт" },
|
|
19
|
-
{
|
|
20
|
-
|
|
17
|
+
{ id: "PRJ-214", title: "Экспорт счетов в CSV для бухгалтерии", due: "14 окт", label: "Финансы" },
|
|
18
|
+
{ id: "PRJ-221", title: "Фильтр заказов по складу", due: "17 окт", label: "Заказы" },
|
|
19
|
+
{
|
|
20
|
+
id: "PRJ-198",
|
|
21
|
+
title: "Повторная отправка приглашений в команду",
|
|
22
|
+
due: "10 окт",
|
|
23
|
+
label: "Команда",
|
|
24
|
+
},
|
|
25
|
+
{ id: "PRJ-176", title: "Онбординг нового проекта", due: "3 окт", label: "Проекты" },
|
|
21
26
|
];
|
|
22
27
|
|
|
23
28
|
const PLACEMENT = {
|
|
@@ -45,6 +50,9 @@ export default function KanbanVariantsExample() {
|
|
|
45
50
|
<Kanban.ItemDescription>
|
|
46
51
|
{task.id} · до {task.due}
|
|
47
52
|
</Kanban.ItemDescription>
|
|
53
|
+
<Kanban.ItemBadges>
|
|
54
|
+
<Badge.Root>{task.label}</Badge.Root>
|
|
55
|
+
</Kanban.ItemBadges>
|
|
48
56
|
</Kanban.Item>
|
|
49
57
|
)}
|
|
50
58
|
/>
|
|
@@ -134,7 +134,7 @@ A second line or an avatar makes a row taller than the one-line height; the vert
|
|
|
134
134
|
| current | `current` | `aria-current="true"` (button) or `"page"` (link); `fill-muted-hover` fill, primary text |
|
|
135
135
|
| focus-visible | keyboard | the focus ring inside the row edge |
|
|
136
136
|
| disabled | `disabled` | `disabled` on a button; `aria-disabled="true"`, no `href` on a link, `tabindex="-1"` on an `asChild` element; `data-disabled="true"`; disabled text; in a reorderable list the handle is disabled and the row holds its place |
|
|
137
|
-
| reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a leading `Dnd.Handle`; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap, its neighbours glide, the copy follows the pointer and sets down in its new place |
|
|
137
|
+
| reorderable | `onReorder` | `data-reorderable="true"` on the root (a `Dnd.Sortable` `<ul>`); each `<li>` is a sortable item (`data-dnd-item` = `value`) with a leading `Dnd.Handle`; drag by the handle (a touch hold on touch), Alt + ↑ / ↓ from the handle or the row; a lifted row leaves a dashed gap in the row's radius, its neighbours glide, the copy (handle and row, in the row's padding and radius) follows the pointer and sets down in its new place without a jump |
|
|
138
138
|
| saving a move | `onReorder` returned a pending promise | the row stays in its new place, `aria-busy="true"`, a `Spinner` stands in for the handle, the row content dims to 0.6 (`--prime-motion-duration-fast` both ways); a rejection glides the row back |
|
|
139
139
|
|
|
140
140
|
Root attribute: `data-size`. The fill changes on `background-color` with `--prime-motion-duration-xfast` both ways (0 under reduced motion).
|
|
@@ -93,8 +93,8 @@ MediaViewer has no `variant`, `size` or `tone`: the stage is always near black,
|
|
|
93
93
|
| gesture | a finger or the mouse dragging | `data-gesture="page \| pan \| pinch \| dismiss"` on the dialog |
|
|
94
94
|
| chrome hidden | a tap, a pinch or a double tap (Tab brings it back); a playing video after 2.5 s without the pointer moving | `data-chrome="hidden"`: the bars fade and drift aside, take no presses; while a video plays the pointer hides too (`data-playing`). A video standing still always keeps its bar |
|
|
95
95
|
| flight | open and close | `data-phase="opening \| open \| closing"`: only the picture moves; the play disk and the bars come after it lands and go before it takes off |
|
|
96
|
-
| loading | the full picture is on its way or decoding | `data-status="loading"` on the page; a picture shows only once it is decoded, and the `thumbnail` stands in, blurred, until the picture has fully shown over it, so no frame is blank; without one a Spinner |
|
|
97
|
-
| error | the picture or the video failed | `data-status="error"`: a «Не удалось загрузить» panel with
|
|
96
|
+
| loading | the full picture is on its way or decoding | `data-status="loading"` on the page; a picture shows only once it is decoded, and the `thumbnail` stands in, blurred, until the picture has fully shown over it, so no frame is blank; without one a Spinner that fades out as the picture arrives |
|
|
97
|
+
| error | the picture or the video failed | `data-status="error"`: a «Не удалось загрузить» panel with «Повторить»; it fades in, and on a retry cross-fades with the Spinner in the same place |
|
|
98
98
|
| sound | the sound button, its level panel (hover, focus, a tap), ↑ / ↓, M | one `volume` and mute for every video of the viewer; `data-open` on the sound control while the level shows; `autoPlay` refused with sound starts muted |
|
|
99
99
|
|
|
100
100
|
Motion: it opens out of the thumbnail it was opened from — the picture grows from the thumbnail's shape to its fit over `base` on the `emphasized` curve while the stage fades in — and closes back into the thumbnail of the item on screen (`getOrigin`; without it the opener for the item it opened at, and for any other item the picture on the page that shows its `thumbnail`, `src` or `poster`, nearest the opener — paging on in a grid still closes into the right tile); without one, or with less than a fifth of it in view, it rises from a slight shrink and fades. The shape is what the page shows of the thumbnail: its media box, cut by every clipping ancestor (a scroller, a rounded album, a bubble) and the viewport, and the radius of each corner taken from the frame whose corner it is — an album's inner tiles fly square, its corner tiles round only on the outer side, and the corners glide all the way, never snap. During the flight only the picture moves; a video flies as the frame of the thumbnail it opened from (drawn before the first paint), starts only after it lands (`autoPlay`) and pauses before it takes off. Released gestures settle with a spring: a page turns past half its width or on a flick, and any other item — a thumbnail far along the strip, `Home` / `End` — arrives by the same slide, never a cut or a fade; the filmstrip follows the track on every frame, so its glide and the emphasis of its thumbnails move with the finger and settle with the page; a zoomed picture coasts with iOS momentum (deceleration 0.998 / ms) and springs back from its edges, and at its edge a sideways drag turns the page; a pinch past its range resists like a rubber band and settles inside it, a swipe away shrinks the picture and thins the stage and closes past 12% of the height or on a flick. Under `prefers-reduced-motion` the open and close are instant and the springs land at once; the picture still follows the finger.
|
|
@@ -116,7 +116,7 @@ Content parts in short: `Message.Media` — a [Gallery](../gallery/COMPONENT.md)
|
|
|
116
116
|
|---|---|---|---|
|
|
117
117
|
| `author` | `ReactNode` | — (required) | Who wrote the quoted message. |
|
|
118
118
|
| `color` | `"gray" \| "blue" \| "green" \| "orange" \| "red" \| "yellow" \| "purple" \| "sky" \| "pink" \| "teal"` | `"blue"` | The author's hue (the same as their `Message.Author`). |
|
|
119
|
-
| `target` | `string` | — | The `value` of the quoted message: a press brings the chat to it (rendering it first in a window), the
|
|
119
|
+
| `target` | `string` | — | The `value` of the quoted message: a press brings the chat to it (rendering it first in a window), the message blinks, and the focus moves to it. Makes the quote a button. |
|
|
120
120
|
| `onClick` | `(event: MouseEvent<HTMLButtonElement>) => void` | — | A press on the quote, after `target` is shown: load the original when it is not in the list yet. Makes the quote a button. |
|
|
121
121
|
| `labels` | `Partial<MessageReplyLabels>` | — | `reply` — the hidden prefix read before the quote. |
|
|
122
122
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "color" \| "onClick">` | — | `children` (the quoted text, one line), `className`. |
|
|
@@ -302,7 +302,7 @@ The list-level settings a message reads — the group chat's author header and a
|
|
|
302
302
|
| author header | `Message.Author` in a group chat (`Chat.Messages type="group"`) on `single` / `first`; any message on its own | the header line at the top of the bubble; over a picture or a circle on the media pill at the top-start corner; hidden on `middle` / `last` and in a direct chat (the author still names the message) |
|
|
303
303
|
| avatar | `Message.Avatar` in a group chat on `single` / `last` | the avatar in the gutter at the bubble's bottom; the gutter stays empty on the others |
|
|
304
304
|
| new at the end | the chat's list decided it (`data-enter="incoming"` / `"outgoing"`) | the message column floats up `--prime-space-3` from below and fades in (`fast`, enter easing, `transform` + `opacity` only; the row box clips it); never on a first render, history or a status change. See Chat |
|
|
305
|
-
| pointed at / stepping back | the chat's jump, search or selection (`data-pointed`, `data-selected` on the message; `data-pointing`, `data-selecting` on the list) | nothing is drawn on the message: it stays at full strength while the others — the bubble, picture or circle with its tail and reactions, the avatar, the retry — step back to `--prime-message-dim-opacity` with a slight desaturation (selection: `fast`, both ways; a jump
|
|
305
|
+
| pointed at / stepping back | the chat's jump, search or selection (`data-pointed`, `data-selected` on the message; `data-pointing`, `data-selecting` on the list) | nothing is drawn on the message: it stays at full strength while the others — the bubble, picture or circle with its tail and reactions, the avatar, the retry — step back to `--prime-message-dim-opacity` with a slight desaturation (selection: `fast`, both ways; the current search match: while it is current); a jump instead blinks the message itself three times, `base` each, the others stay still. Reduced motion: no blink |
|
|
306
306
|
| search match | the chat's search query | every case-insensitive match in `Message.Text` and the transcripts under a soft accent wash (`<mark>`) |
|
|
307
307
|
| typing | `Message.Typing active` | an incoming bubble of three dots rising and fading in turn on the loading loop (reduced motion slows it); enters floating up while its height opens (`fast`), leaves closing its height (`fast`) — or at once when a reply arrives at the end of the list in the same update; its block axis clips, so a closed row leaves nothing under the list |
|
|
308
308
|
| status change | `status` | the ticks cross-fade and scale (`base`, `emphasized`), interruptible |
|
|
@@ -152,6 +152,7 @@ Collapsed stacks show up to 3 cards peeking behind each other. Hover or keyboard
|
|
|
152
152
|
### ARIA
|
|
153
153
|
- Each stack is an `<ol>` named by the region label of its position; each card is an `<article>`: `role="status"` + `aria-live="polite"`, or `role="alert"` + `aria-live="assertive"` for `warning` / `danger`.
|
|
154
154
|
- The icon and the countdown are `aria-hidden`; the close button is named by `labels.close`.
|
|
155
|
+
- A toast is a non-modal live region, not a layer of the overlay stack: Escape and a press elsewhere belong to the page and to the topmost open layer, so a toast takes no `closeOnEscape` / `closeOnOutsideClick` — it leaves by its timer, the close button, a swipe or `dismiss()`.
|
|
155
156
|
- Do not keep the only copy of important information in a toast with a timer — use `persistent` or a Banner.
|
|
156
157
|
|
|
157
158
|
### Labels
|
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
**Category:** page
|
|
4
4
|
**Kind:** layout
|
|
5
5
|
|
|
6
|
-
> Page structure inside the main column: title,
|
|
6
|
+
> Page structure inside the main column: title, page actions and content blocks, titled sections and the tile grid of a dashboard; a page as high as the panel and the list + detail layout.
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
9
|
- Every route rendered inside `AppShell.Main`: a page heading (`<h1>`), an intro line, page-level actions and the page body.
|
|
10
10
|
- A text page held to a reading measure (`PageContent.Root maxWidth="readable"`).
|
|
11
11
|
- A wide dashboard page capped at the content width (`maxWidth="wide"`).
|
|
12
|
+
- The tiles of a dashboard — KPI cards, charts, panels — as one board (`PageContent.Grid` + `PageContent.Cell`).
|
|
13
|
+
- A new topic of the page with its own heading and a link or button beside it (`PageContent.Section`); a settings page with a heading column beside each panel (`layout="aside"`).
|
|
12
14
|
- A page whose body takes the rest of the panel's height — a log table, a board, a chat (`fill`).
|
|
13
15
|
- Mail, a messenger, a ticket queue: a list and the chosen record side by side, one at a time on a narrow panel (`PageContent.Split`).
|
|
14
16
|
|
|
@@ -16,7 +18,8 @@
|
|
|
16
18
|
- The app frame (navigation rail, header, scrolling main column) → use [AppShell](../../layout/app-shell/COMPONENT.md).
|
|
17
19
|
- A bounded block inside the page → use [Card](../card/COMPONENT.md).
|
|
18
20
|
- An empty / error / not-found page → use [EmptyPage](../empty-page/COMPONENT.md).
|
|
19
|
-
-
|
|
21
|
+
- A heading inside a card → `Card.Title`; text headings inside prose → use [Typography](../typography/COMPONENT.md).
|
|
22
|
+
- A grid inside one block (thumbnails, fields, bars in a panel) → the block's own CSS grid; `PageContent.Grid` places the page's tiles.
|
|
20
23
|
- Details that open over the list and close again (a record from a table) → use [Drawer](../drawer/COMPONENT.md); `Split` is for a list people work through record by record.
|
|
21
24
|
|
|
22
25
|
## Import
|
|
@@ -26,12 +29,16 @@ import { PageContent } from "prime-ui-kit";
|
|
|
26
29
|
|
|
27
30
|
## Anatomy
|
|
28
31
|
```
|
|
29
|
-
PageContent.Root | PageContent.
|
|
32
|
+
PageContent.Root | PageContent.Page column; Root adds maxWidth and fill, Page renders <section>
|
|
30
33
|
├─ PageContent.Header heading column + actions in one wrapping row
|
|
31
34
|
│ ├─ PageContent.Title <h1>, heading-m
|
|
32
35
|
│ ├─ PageContent.Description <p>, secondary body-m
|
|
33
36
|
│ └─ PageContent.Actions page buttons (moved to the end of the header row)
|
|
34
|
-
└─ PageContent.Body page content, blocks
|
|
37
|
+
└─ PageContent.Body page content, blocks 24 apart
|
|
38
|
+
├─ PageContent.Grid 12-column tile grid, 16 on both axes; a container (prime-grid)
|
|
39
|
+
│ └─ PageContent.Cell one tile place: span, rows; its child fills it
|
|
40
|
+
├─ PageContent.Section <section> + <h2> label, 40 from its neighbours
|
|
41
|
+
│ └─ PageContent.Actions a link or button at the end of the section heading
|
|
35
42
|
└─ PageContent.Split list + detail; a container (prime-split)
|
|
36
43
|
├─ PageContent.List list pane: 16–20rem column, or the whole split while closed
|
|
37
44
|
└─ PageContent.Detail detail pane: the rest, or the whole split while open
|
|
@@ -43,7 +50,7 @@ PageContent.Root | PageContent.Section column; Root adds maxWidth and fill,
|
|
|
43
50
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
44
51
|
|
|
45
52
|
### PageContent.Root
|
|
46
|
-
`ref` → `HTMLDivElement`. The page column inside `main`: header → body
|
|
53
|
+
`ref` → `HTMLDivElement`. The page column inside `main`: header → body 24 apart, centred under its cap.
|
|
47
54
|
|
|
48
55
|
| Prop | Type | Default | Description |
|
|
49
56
|
|---|---|---|---|
|
|
@@ -51,8 +58,8 @@ PageContent.Root | PageContent.Section column; Root adds maxWidth and fill,
|
|
|
51
58
|
| `fill` | `boolean` | `false` | The page takes the height of main (`AppShell.Root fillViewport`) and the body takes what the header leaves: for a full-height table, board, chat or `PageContent.Split`. |
|
|
52
59
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (Header, Body), `className` and the other div attributes. |
|
|
53
60
|
|
|
54
|
-
### PageContent.
|
|
55
|
-
`ref` → `HTMLElement`. The
|
|
61
|
+
### PageContent.Page
|
|
62
|
+
`ref` → `HTMLElement`. The whole app page as a `<section>` (the same column, the full panel width); name it with `aria-labelledby` → the Title `id`.
|
|
56
63
|
|
|
57
64
|
| Prop | Type | Default | Description |
|
|
58
65
|
|---|---|---|---|
|
|
@@ -88,12 +95,38 @@ PageContent.Root | PageContent.Section column; Root adds maxWidth and fill,
|
|
|
88
95
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (Buttons), `className` and the other div attributes. |
|
|
89
96
|
|
|
90
97
|
### PageContent.Body
|
|
91
|
-
`ref` → `HTMLDivElement`. The page content
|
|
98
|
+
`ref` → `HTMLDivElement`. The page content: blocks of one screen 24 apart, a `PageContent.Section` 40 from its neighbours.
|
|
92
99
|
|
|
93
100
|
| Prop | Type | Default | Description |
|
|
94
101
|
|---|---|---|---|
|
|
95
102
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children`, `className` and the other div attributes. |
|
|
96
103
|
|
|
104
|
+
### PageContent.Section
|
|
105
|
+
`ref` → `HTMLElement`. A titled section of the page — a `<section>` named by its `h2` (title-m): the heading with an optional description and `PageContent.Actions` at its end, then the content 16 below. Stands 40 from its neighbours in Body.
|
|
106
|
+
|
|
107
|
+
| Prop | Type | Default | Description |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| `label` | `ReactNode` | — (required) | The visible heading (`h2`); it also names the section. |
|
|
110
|
+
| `description` | `ReactNode` | — | One line of context under the heading, in secondary body-s. |
|
|
111
|
+
| `layout` | `"stack" \| "aside"` | `"stack"` | `stack` — the heading over the content; `aside` — a ~16rem heading column beside it (a settings page), above it once the content would get narrower than 30rem. |
|
|
112
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content; `PageContent.Actions` goes to the heading row), `className` and the other section attributes. |
|
|
113
|
+
|
|
114
|
+
### PageContent.Grid
|
|
115
|
+
`ref` → `HTMLDivElement`. The grid of the page's tiles (KPI cards, charts, panels): 12 columns, 16 apart on both axes, so its rows read as one board. A size container: cells fold by its own width (see Grid columns).
|
|
116
|
+
|
|
117
|
+
| Prop | Type | Default | Description |
|
|
118
|
+
|---|---|---|---|
|
|
119
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (`PageContent.Cell`), `className` and the other div attributes. |
|
|
120
|
+
|
|
121
|
+
### PageContent.Cell
|
|
122
|
+
`ref` → `HTMLDivElement`. One tile place in `PageContent.Grid`; its child (a Card, a ChartFrame) stretches to it, so the tiles of a row share one height.
|
|
123
|
+
|
|
124
|
+
| Prop | Type | Default | Description |
|
|
125
|
+
|---|---|---|---|
|
|
126
|
+
| `span` | `1 \| 2 \| … \| 12` | `12` | Columns of 12 on a wide grid (64rem+). Narrower grids round it: see Grid columns in COMPONENT.md. |
|
|
127
|
+
| `rows` | `1 \| 2 \| 3 \| 4` | `1` | Rows the cell spans from 40rem: a chart beside two stacked tiles (`ChartFrame.Plot height="fill"` fills the height). |
|
|
128
|
+
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `children` (one tile), `className` and the other div attributes. |
|
|
129
|
+
|
|
97
130
|
### PageContent.Split
|
|
98
131
|
`ref` → `HTMLDivElement`. List + detail (mail, a messenger, a ticket queue): side by side from 48rem of its own width, one pane at a time below it, focus following the pane that comes into view.
|
|
99
132
|
|
|
@@ -141,13 +174,27 @@ PageContent.Root | PageContent.Section column; Root adds maxWidth and fill,
|
|
|
141
174
|
| `readable` | Intro line wraps at the reading width | Default, most pages | yes |
|
|
142
175
|
| `full` | Intro uses the full header width | The column is already narrow | |
|
|
143
176
|
|
|
144
|
-
One `PageContent.Title` (`<h1>`) per page;
|
|
177
|
+
One `PageContent.Title` (`<h1>`) per page; a topic inside Body is a `PageContent.Section` (its `h2`). One primary button in Actions, the rest `soft` / `ghost` neutral.
|
|
145
178
|
|
|
146
179
|
### Flags
|
|
147
180
|
| Flag | Looks like when on | Use when | Default |
|
|
148
181
|
|---|---|---|---|
|
|
149
182
|
| `fill` (Root) | the page is as high as main; the body takes what the header leaves; main keeps gutter-high air under it | a full-height table, board, chat or `PageContent.Split` in an `AppShell.Root fillViewport` | `false` |
|
|
150
183
|
|
|
184
|
+
### layout (Section)
|
|
185
|
+
| Value | Looks like | Use when | Default |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| `stack` | the heading row over the content, 16 apart | a topic of a dashboard, a list or a detail page | ✓ |
|
|
188
|
+
| `aside` | a ~16rem heading column beside the content, above it once the content would get < 30rem | a settings page: a heading column beside each panel | |
|
|
189
|
+
|
|
190
|
+
### Grid columns
|
|
191
|
+
| Grid width | Cell `span` 1–3 | `span` 4 | `span` 5–6 | `span` 7–12 | `rows` |
|
|
192
|
+
|---|---|---|---|---|---|
|
|
193
|
+
| < 30rem | the row | the row | the row | the row | — |
|
|
194
|
+
| 30–40rem | half | the row | the row | the row | — |
|
|
195
|
+
| 40–64rem | half | a third | half | the row | spans |
|
|
196
|
+
| ≥ 64rem | its span | its span | its span | its span | spans |
|
|
197
|
+
|
|
151
198
|
### Split layout
|
|
152
199
|
| Value | Looks like | Use when | Default |
|
|
153
200
|
|---|---|---|---|
|
|
@@ -161,14 +208,18 @@ One `PageContent.Title` (`<h1>`) per page; sub-sections inside Body use `title-s
|
|
|
161
208
|
| filling | `fill` | `data-fill="true"` on Root; `AppShell.Main` drops its long bottom air for it |
|
|
162
209
|
| full-width intro | `measure="full"` | `data-measure="full"` on Description |
|
|
163
210
|
| with actions | `PageContent.Actions` child | `data-has-actions="true"` on Header |
|
|
211
|
+
| section heading column | `layout="aside"` on Section | `data-layout="aside"` on Section (absent for `stack`) |
|
|
212
|
+
| section with actions | `PageContent.Actions` child of Section | `data-has-actions="true"` on the section heading row |
|
|
213
|
+
| tile span | `span` / `rows` on Cell | `data-span="1"…"12"` (always), `data-rows="2"…"4"` (absent for 1) |
|
|
164
214
|
| detail open | `open` / `defaultOpen` on Split | `data-open="true"` on Split; narrow: the list is `display: none`, else the detail is |
|
|
165
215
|
| pane swap | `open` changes on a narrow split | the pane that comes into view enters once (`enterMotion.enterBase`: base, opacity + a 4 px rise); focus moves into the detail, or back to the list control; a wide split moves nothing |
|
|
166
216
|
|
|
167
217
|
## Layout & spacing
|
|
168
218
|
- No outer padding: edge gutters come from `AppShell.Main`.
|
|
169
|
-
- Header → Body `--prime-space-
|
|
219
|
+
- Header → Body `--prime-space-6` (24); blocks inside Body `--prime-space-6` (24); a `PageContent.Section` 40 from its neighbours (the body's 24 + `--prime-space-4`); tile → tile in a Grid `--prime-space-4` (16) on both axes; section heading → content `--prime-space-4` (16), heading → description `--prime-space-1` (4); Title → Description `--prime-space-2` (8); buttons in Actions `--prime-space-2`; heading → Actions `--prime-space-6` (24) side by side, `--prime-space-4` (16) once they wrap below.
|
|
170
220
|
- Header is a wrapping row: actions stay to the right of the title while the heading keeps half a reading measure, otherwise they wrap below it — no breakpoint needed, works from 320px.
|
|
171
221
|
- Title and Description wrap long words (`overflow-wrap: anywhere`).
|
|
222
|
+
- Grid lays itself out from its own width (container `prime-grid`), never from the window, so a board beside an open Sidebar folds on its own. A Cell stretches its child: the tiles of a row share one height; a chart in a `rows={2}` cell fills it with `ChartFrame.Plot height="fill"`. `dense` flow lets a small tile fill a hole left by a wide one.
|
|
172
223
|
- `fill` needs a main with a height: `AppShell.Root fillViewport` (main scrolls inside the panel). The body passes its free height on to a filling child — DataTable `scrollHeight="fill"`, Crossfade `fill`, `PageContent.Split`.
|
|
173
224
|
- Split takes the free height of a filling page and lays itself out from its own width (container `prime-split`), never from the window: a split beside an open Sidebar may be narrow on a wide screen. Each pane is a column whose last child takes the free height — put a Card with its own `ScrollContainer` there, so the list and the thread scroll separately. The records of the list pane are a [List](../list/COMPONENT.md).
|
|
174
225
|
- The detail pane is on screen all the time on a wide split: while nothing is chosen it shows a short hint («Выберите диалог»), never an empty card.
|
|
@@ -182,7 +233,8 @@ One `PageContent.Title` (`<h1>`) per page; sub-sections inside Body use `title-s
|
|
|
182
233
|
|
|
183
234
|
### ARIA
|
|
184
235
|
- Title is an `<h1>`; use one per page.
|
|
185
|
-
- Give `PageContent.
|
|
236
|
+
- Give `PageContent.Page` an `aria-labelledby` that points at the Title `id` to name the region.
|
|
237
|
+
- `PageContent.Section` is a `<section>` named by its own `<h2>` (`label`) through `aria-labelledby`, so it is a region landmark with the heading's name; the heading levels go h1 (page) → h2 (section) → h3 (a card title inside it).
|
|
186
238
|
- Name `PageContent.List` and `PageContent.Detail` with `aria-label`. On a narrow split opening the detail moves focus into it and going back returns focus to the list control; the hidden pane is `display: none`, out of Tab and the accessibility tree. The panes take focus from code only (`tabindex="-1"`, no ring).
|
|
187
239
|
- `PageContent.BackButton` is named by `labels.back` and is shown only while the split shows one pane.
|
|
188
240
|
|
|
@@ -198,18 +250,22 @@ One `PageContent.Title` (`<h1>`) per page; sub-sections inside Body use `title-s
|
|
|
198
250
|
|---|---|
|
|
199
251
|
| [overview.tsx](examples/overview.tsx) | A page in the main column: title, description and page actions, then the body blocks — `PageContent.Actions`. |
|
|
200
252
|
| [variants.tsx](examples/variants.tsx) | The same page column at three caps: the whole main, a wide dashboard column, a reading column — `maxWidth`. |
|
|
253
|
+
| [grid.tsx](examples/grid.tsx) | A dashboard board: four KPI tiles, a chart two rows high beside two panels, one gap on both axes — `PageContent.Grid`, `PageContent.Cell`, `span`, `rows`. |
|
|
254
|
+
| [sections.tsx](examples/sections.tsx) | A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading, a heading column beside a panel — `PageContent.Section`, `label`, `description`, `PageContent.Actions`, `layout`. |
|
|
201
255
|
| [list-detail.tsx](examples/list-detail.tsx) | A support inbox as list + detail on a full-height page: side by side on a wide panel, one pane at a time with a back button on a narrow one — `PageContent.Split`, `PageContent.BackButton`, `fill`. |
|
|
202
256
|
| [narrow.tsx](examples/narrow.tsx) | In a phone-width column the page actions wrap under the heading instead of squeezing it. |
|
|
203
257
|
|
|
204
258
|
## Mistakes
|
|
205
259
|
- Padding on `PageContent.Root` to imitate gutters → gutters come from `AppShell.Main`.
|
|
206
260
|
- Actions placed outside Header → put `PageContent.Actions` inside `PageContent.Header` so it aligns with the title and wraps.
|
|
207
|
-
- Several `PageContent.Title` on one page → one `<h1
|
|
208
|
-
-
|
|
261
|
+
- Several `PageContent.Title` on one page → one `<h1>`; a topic of the page is a `PageContent.Section` with its `<h2>`.
|
|
262
|
+
- A hand-made `<section>` + `Typography as="h2"` + own gap CSS → `PageContent.Section label`.
|
|
263
|
+
- Each row of cards in its own auto-fit grid → the rows stand 24 apart instead of 16; put every tile of the screen in one `PageContent.Grid`.
|
|
264
|
+
- Margins between blocks in Body → Body already sets the 24 gap and the 40 around a Section.
|
|
209
265
|
- Computing a height from `100vh` minus the header for a full-height table or chat → `PageContent.Root fill` and a filling child.
|
|
210
266
|
- A list + detail built with window media queries → `PageContent.Split` follows its own width.
|
|
211
267
|
- Unmounting the detail content the moment the narrow split goes back → keep the last record until another is chosen (the pane leaves with it on screen).
|
|
212
268
|
|
|
213
269
|
## Related
|
|
214
270
|
- **Built from:** [Button](../button/COMPONENT.md) (`PageContent.BackButton`), Icon (`nav.chevronLeft`)
|
|
215
|
-
- **See also:** [AppShell](../../layout/app-shell/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Card](../card/COMPONENT.md), [List](../list/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [Drawer](../drawer/COMPONENT.md)
|
|
271
|
+
- **See also:** [AppShell](../../layout/app-shell/COMPONENT.md), [Sidebar](../../layout/sidebar/COMPONENT.md), [Card](../card/COMPONENT.md), [ChartFrame](../chart-frame/COMPONENT.md), [List](../list/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [Drawer](../drawer/COMPONENT.md)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** A dashboard board: four KPI tiles, a chart two rows high beside two panels, one gap on both axes — `PageContent.Grid`, `PageContent.Cell`, `span`, `rows`. */
|
|
2
|
+
import { Card, ChartFrame, type ChartFrameSeries, chartColors, PageContent } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const KPIS = [
|
|
7
|
+
{ label: "Выручка", value: "4,8 млн ₽", delta: "+12% к сентябрю", tone: "success" },
|
|
8
|
+
{ label: "Заказы", value: "1 284", delta: "+6% к сентябрю", tone: "success" },
|
|
9
|
+
{ label: "Средний чек", value: "3 740 ₽", delta: "−2% к сентябрю", tone: "danger" },
|
|
10
|
+
{ label: "Возвраты", value: "1,8%", delta: "без изменений", tone: "neutral" },
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
const SERIES: ChartFrameSeries[] = [{ id: "orders", label: "Заказы" }];
|
|
14
|
+
|
|
15
|
+
const WEEKS = [
|
|
16
|
+
{ week: "1 нед", orders: 268 },
|
|
17
|
+
{ week: "2 нед", orders: 301 },
|
|
18
|
+
{ week: "3 нед", orders: 287 },
|
|
19
|
+
{ week: "4 нед", orders: 334 },
|
|
20
|
+
{ week: "5 нед", orders: 352 },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const MARGIN = { top: 8, right: 8, bottom: 24, left: 8 };
|
|
24
|
+
|
|
25
|
+
const formatOrders = (value: number) => `${value.toLocaleString("ru-RU")} заказов`;
|
|
26
|
+
|
|
27
|
+
export default function PageContentGridExample() {
|
|
28
|
+
return (
|
|
29
|
+
<div className={styles.main}>
|
|
30
|
+
<PageContent.Body>
|
|
31
|
+
<PageContent.Grid>
|
|
32
|
+
{KPIS.map((kpi) => (
|
|
33
|
+
<PageContent.Cell key={kpi.label} span={3}>
|
|
34
|
+
<Card.Root variant="stat-trend">
|
|
35
|
+
<Card.Label>{kpi.label}</Card.Label>
|
|
36
|
+
<Card.Value>{kpi.value}</Card.Value>
|
|
37
|
+
<Card.Delta tone={kpi.tone}>{kpi.delta}</Card.Delta>
|
|
38
|
+
</Card.Root>
|
|
39
|
+
</PageContent.Cell>
|
|
40
|
+
))}
|
|
41
|
+
<PageContent.Cell span={8} rows={2}>
|
|
42
|
+
<ChartFrame.Root series={SERIES} formatValue={formatOrders}>
|
|
43
|
+
<ChartFrame.Header>
|
|
44
|
+
<ChartFrame.Title>Заказы по неделям</ChartFrame.Title>
|
|
45
|
+
<ChartFrame.Description>Октябрь</ChartFrame.Description>
|
|
46
|
+
</ChartFrame.Header>
|
|
47
|
+
<ChartFrame.Plot
|
|
48
|
+
height="fill"
|
|
49
|
+
renderPlot={({ width, height, series }) => {
|
|
50
|
+
const max = Math.max(...WEEKS.map((w) => w.orders));
|
|
51
|
+
const innerW = width - MARGIN.left - MARGIN.right;
|
|
52
|
+
const innerH = height - MARGIN.top - MARGIN.bottom;
|
|
53
|
+
const slot = innerW / WEEKS.length;
|
|
54
|
+
const bar = slot * 0.56;
|
|
55
|
+
return (
|
|
56
|
+
<svg width={width} height={height} aria-hidden="true">
|
|
57
|
+
<line
|
|
58
|
+
x1={MARGIN.left}
|
|
59
|
+
x2={width - MARGIN.right}
|
|
60
|
+
y1={MARGIN.top + innerH}
|
|
61
|
+
y2={MARGIN.top + innerH}
|
|
62
|
+
stroke={chartColors.axis}
|
|
63
|
+
/>
|
|
64
|
+
{WEEKS.map((w, i) => {
|
|
65
|
+
const h = (innerH * w.orders) / max;
|
|
66
|
+
const x = MARGIN.left + slot * i + (slot - bar) / 2;
|
|
67
|
+
return (
|
|
68
|
+
<g key={w.week} data-hidden={series[0].hidden}>
|
|
69
|
+
<rect
|
|
70
|
+
x={x}
|
|
71
|
+
y={MARGIN.top + innerH - h}
|
|
72
|
+
width={bar}
|
|
73
|
+
height={h}
|
|
74
|
+
rx={4}
|
|
75
|
+
fill={series[0].color}
|
|
76
|
+
/>
|
|
77
|
+
<text x={x + bar / 2} y={height - 4} textAnchor="middle">
|
|
78
|
+
{w.week}
|
|
79
|
+
</text>
|
|
80
|
+
</g>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
</svg>
|
|
84
|
+
);
|
|
85
|
+
}}
|
|
86
|
+
/>
|
|
87
|
+
</ChartFrame.Root>
|
|
88
|
+
</PageContent.Cell>
|
|
89
|
+
<PageContent.Cell span={4}>
|
|
90
|
+
<Card.Root variant="stat-trend">
|
|
91
|
+
<Card.Label>Новые клиенты</Card.Label>
|
|
92
|
+
<Card.Value>86</Card.Value>
|
|
93
|
+
<Card.Delta tone="success">+14 к сентябрю</Card.Delta>
|
|
94
|
+
</Card.Root>
|
|
95
|
+
</PageContent.Cell>
|
|
96
|
+
<PageContent.Cell span={4}>
|
|
97
|
+
<Card.Root variant="stat-trend">
|
|
98
|
+
<Card.Label>Ждут оплаты</Card.Label>
|
|
99
|
+
<Card.Value>312 400 ₽</Card.Value>
|
|
100
|
+
<Card.Delta tone="warning">7 счетов</Card.Delta>
|
|
101
|
+
</Card.Root>
|
|
102
|
+
</PageContent.Cell>
|
|
103
|
+
</PageContent.Grid>
|
|
104
|
+
</PageContent.Body>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -6,7 +6,7 @@ import styles from "./examples.module.css";
|
|
|
6
6
|
export default function PageContentOverviewExample() {
|
|
7
7
|
return (
|
|
8
8
|
<div className={styles.main}>
|
|
9
|
-
<PageContent.
|
|
9
|
+
<PageContent.Page aria-labelledby="settings-heading">
|
|
10
10
|
<PageContent.Header>
|
|
11
11
|
<PageContent.Title id="settings-heading">Настройки</PageContent.Title>
|
|
12
12
|
<PageContent.Description>
|
|
@@ -35,7 +35,7 @@ export default function PageContentOverviewExample() {
|
|
|
35
35
|
</Card.Body>
|
|
36
36
|
</Card.Root>
|
|
37
37
|
</PageContent.Body>
|
|
38
|
-
</PageContent.
|
|
38
|
+
</PageContent.Page>
|
|
39
39
|
</div>
|
|
40
40
|
);
|
|
41
41
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading, a heading column beside a panel — `PageContent.Section`, `label`, `description`, `PageContent.Actions`, `layout`. */
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
DataTable,
|
|
5
|
+
type DataTableColumn,
|
|
6
|
+
LinkButton,
|
|
7
|
+
PageContent,
|
|
8
|
+
Typography,
|
|
9
|
+
} from "prime-ui-kit";
|
|
10
|
+
|
|
11
|
+
import styles from "./examples.module.css";
|
|
12
|
+
|
|
13
|
+
type Invoice = { id: string; client: string; amount: number };
|
|
14
|
+
|
|
15
|
+
const INVOICES: Invoice[] = [
|
|
16
|
+
{ id: "СЧ-1042", client: "ООО «Север»", amount: 184_000 },
|
|
17
|
+
{ id: "СЧ-1041", client: "ИП Лаптева", amount: 42_500 },
|
|
18
|
+
{ id: "СЧ-1039", client: "АО «Техносфера»", amount: 610_000 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const MONEY = new Intl.NumberFormat("ru-RU", {
|
|
22
|
+
style: "currency",
|
|
23
|
+
currency: "RUB",
|
|
24
|
+
maximumFractionDigits: 0,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const COLUMNS: DataTableColumn<Invoice>[] = [
|
|
28
|
+
{ id: "id", header: "Счёт", cell: (row) => row.id },
|
|
29
|
+
{ id: "client", header: "Клиент", cell: (row) => row.client },
|
|
30
|
+
{
|
|
31
|
+
id: "amount",
|
|
32
|
+
header: "Сумма",
|
|
33
|
+
numeric: true,
|
|
34
|
+
headerAlign: "end",
|
|
35
|
+
cell: (row) => MONEY.format(row.amount),
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export default function PageContentSectionsExample() {
|
|
40
|
+
return (
|
|
41
|
+
<div className={styles.main}>
|
|
42
|
+
<PageContent.Body>
|
|
43
|
+
<PageContent.Grid>
|
|
44
|
+
<PageContent.Cell span={6}>
|
|
45
|
+
<Card.Root variant="stat-trend">
|
|
46
|
+
<Card.Label>Выставлено</Card.Label>
|
|
47
|
+
<Card.Value>2,4 млн ₽</Card.Value>
|
|
48
|
+
<Card.Delta tone="success">+9% к сентябрю</Card.Delta>
|
|
49
|
+
</Card.Root>
|
|
50
|
+
</PageContent.Cell>
|
|
51
|
+
<PageContent.Cell span={6}>
|
|
52
|
+
<Card.Root variant="stat-trend">
|
|
53
|
+
<Card.Label>Оплачено</Card.Label>
|
|
54
|
+
<Card.Value>1,9 млн ₽</Card.Value>
|
|
55
|
+
<Card.Delta tone="neutral">79% выставленного</Card.Delta>
|
|
56
|
+
</Card.Root>
|
|
57
|
+
</PageContent.Cell>
|
|
58
|
+
</PageContent.Grid>
|
|
59
|
+
<PageContent.Section label="Последние счета" description="Три последних за октябрь">
|
|
60
|
+
<PageContent.Actions>
|
|
61
|
+
<LinkButton href="#invoices">Все счета</LinkButton>
|
|
62
|
+
</PageContent.Actions>
|
|
63
|
+
<DataTable columns={COLUMNS} rows={INVOICES} getRowKey={(row) => row.id} paging="none" />
|
|
64
|
+
</PageContent.Section>
|
|
65
|
+
<PageContent.Section
|
|
66
|
+
layout="aside"
|
|
67
|
+
label="Реквизиты в счетах"
|
|
68
|
+
description="Попадают в каждый новый счёт."
|
|
69
|
+
>
|
|
70
|
+
<Card.Root variant="panel">
|
|
71
|
+
<Card.Body>
|
|
72
|
+
<Typography variant="body-m" tone="secondary">
|
|
73
|
+
ООО «Прайм Софт», ИНН 7701234567, р/с в Т-Банке.
|
|
74
|
+
</Typography>
|
|
75
|
+
</Card.Body>
|
|
76
|
+
</Card.Root>
|
|
77
|
+
</PageContent.Section>
|
|
78
|
+
</PageContent.Body>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -111,7 +111,7 @@ PageToolbar has no variants: the layout follows the toolbar's width.
|
|
|
111
111
|
No keyboard interaction.
|
|
112
112
|
|
|
113
113
|
### ARIA
|
|
114
|
-
- A plain `<div>`:
|
|
114
|
+
- A plain `<div>`: when a page has more than one panel, give each `role="group"` and an `aria-label` (a name on a `<div>` without a role is ignored by assistive tech).
|
|
115
115
|
- Slots move only visually (`order`); the Tab order is the JSX order. Write the slots in the reading order of the wide layout — Tools, Sections, View, Actions, Chips. In the narrow layout the action is shown beside the tools but reached after the view.
|
|
116
116
|
- An icon-only primary action on a narrow page carries `aria-label`.
|
|
117
117
|
|