prime-ui-kit 0.20.1 → 0.21.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 +1 -1
- package/dist/chunks/{chunk-4KONMO7Z.js → chunk-6L7VWEY6.js} +1 -1
- package/dist/chunks/{chunk-3PY52JP3.js → chunk-S27VR4ON.js} +5828 -5596
- package/dist/chunks/{chunk-4374EBWV.js → chunk-S4H2ZKWJ.js} +1049 -644
- package/dist/color-picker.js +2 -2
- package/dist/components/badge/Badge.d.ts +19 -2
- package/dist/components/card/Card.d.ts +10 -3
- package/dist/components/composer/Composer.d.ts +49 -1
- package/dist/components/crossfade/Crossfade.d.ts +6 -4
- package/dist/components/data-table/DataTable.d.ts +10 -2
- package/dist/components/data-table/Row.d.ts +12 -1
- package/dist/components/data-table/SelectionMenu.d.ts +20 -0
- package/dist/components/data-table/useRowSelection.d.ts +2 -0
- package/dist/components/empty-page/EmptyPage.d.ts +5 -2
- package/dist/components/hint/Hint.d.ts +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/input/Input.d.ts +15 -3
- package/dist/components/message/Message.d.ts +6 -3
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -1
- package/dist/components/smart-filter/SmartFilter.d.ts +4 -0
- package/dist/components/spinner/Spinner.d.ts +3 -2
- package/dist/components/tabs/Tabs.d.ts +10 -1
- package/dist/hooks/useModalLayer.d.ts +15 -8
- package/dist/index.css +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/RegionSwap.d.ts +18 -10
- package/dist/internal/RollingNumber.d.ts +7 -5
- package/dist/internal/ToolRow.d.ts +6 -0
- package/dist/internal/mediaSlot.d.ts +29 -0
- package/dist/internal/overlay/DialogParts.d.ts +30 -6
- package/dist/internal/overlay/focus.d.ts +18 -3
- package/dist/internal/partPresence.d.ts +19 -2
- package/dist/layout/sidebar/Sidebar.d.ts +3 -2
- package/dist/skill/anti-slop.md +35 -37
- package/dist/skill/api-contract.md +50 -2
- package/dist/skill/cheatsheet.md +15 -10
- package/dist/skill/checklist.md +19 -6
- package/dist/skill/choosing.md +79 -6
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +211 -51
- package/dist/skill/foundations.md +17 -6
- package/dist/skill/layouts.md +64 -14
- package/dist/skill/motion.md +9 -3
- package/dist/skill/reference/components/avatar/COMPONENT.md +1 -0
- package/dist/skill/reference/components/badge/COMPONENT.md +29 -5
- package/dist/skill/reference/components/badge/examples/examples.module.css +14 -0
- package/dist/skill/reference/components/badge/examples/overflow-count.tsx +32 -0
- package/dist/skill/reference/components/banner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/card/COMPONENT.md +8 -4
- package/dist/skill/reference/components/card/examples/person-header.tsx +38 -0
- package/dist/skill/reference/components/composer/COMPONENT.md +35 -5
- package/dist/skill/reference/components/composer/examples/bot-keyboard.tsx +43 -0
- package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/crossfade/COMPONENT.md +18 -10
- package/dist/skill/reference/components/data-table/COMPONENT.md +17 -11
- package/dist/skill/reference/components/data-table/examples/screen.tsx +6 -9
- package/dist/skill/reference/components/data-table/examples/selection.tsx +60 -18
- package/dist/skill/reference/components/drawer/COMPONENT.md +41 -19
- package/dist/skill/reference/components/drawer/examples/footer-actions.tsx +54 -0
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +46 -25
- package/dist/skill/reference/components/drawer/examples/link-loading.tsx +57 -0
- package/dist/skill/reference/components/drawer/examples/screen.tsx +3 -5
- package/dist/skill/reference/components/empty-page/COMPONENT.md +2 -2
- package/dist/skill/reference/components/field-list/COMPONENT.md +4 -4
- package/dist/skill/reference/components/field-list/examples/row-menu.tsx +1 -1
- package/dist/skill/reference/components/hint/COMPONENT.md +1 -1
- package/dist/skill/reference/components/input/COMPONENT.md +16 -3
- package/dist/skill/reference/components/input/examples/password-policy.tsx +65 -0
- package/dist/skill/reference/components/list/COMPONENT.md +1 -1
- package/dist/skill/reference/components/login-form/COMPONENT.md +13 -11
- package/dist/skill/reference/components/login-form/examples/overview.tsx +5 -5
- package/dist/skill/reference/components/login-form/examples/states.tsx +7 -6
- package/dist/skill/reference/components/message/COMPONENT.md +5 -5
- package/dist/skill/reference/components/message/examples/bot-actions.tsx +47 -28
- package/dist/skill/reference/components/modal/COMPONENT.md +31 -12
- package/dist/skill/reference/components/modal/examples/structure.tsx +2 -4
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +11 -6
- package/dist/skill/reference/components/segmented-control/examples/router.tsx +22 -0
- package/dist/skill/reference/components/select/COMPONENT.md +4 -1
- package/dist/skill/reference/components/sheet/COMPONENT.md +31 -12
- package/dist/skill/reference/components/sheet/examples/header-actions.tsx +5 -3
- package/dist/skill/reference/components/skeleton/COMPONENT.md +3 -2
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -1
- package/dist/skill/reference/components/smart-filter/examples/ranges.tsx +8 -2
- package/dist/skill/reference/components/spinner/COMPONENT.md +1 -1
- package/dist/skill/reference/components/tabs/COMPONENT.md +7 -4
- package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +18 -2
- package/dist/skill/reference/components/thumbnail/COMPONENT.md +1 -0
- package/dist/skill/reference/components/tooltip/COMPONENT.md +3 -3
- package/dist/skill/reference/foundation.md +15 -9
- package/dist/skill/reference/layout/page/COMPONENT.md +1 -0
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +4 -2
- package/dist/skill/reference/layout/sidebar/examples/account-profile.tsx +66 -0
- package/dist/skill/responsive.md +28 -6
- package/dist/skill/ux.md +97 -2
- package/dist/tokens/semantic.d.ts +2 -1
- package/dist/tokens/themes/dark.d.ts +1 -0
- package/package.json +1 -1
- package/src/components/avatar/COMPONENT.md +1 -0
- package/src/components/badge/COMPONENT.md +29 -5
- package/src/components/badge/examples/examples.module.css +14 -0
- package/src/components/badge/examples/overflow-count.tsx +32 -0
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/card/COMPONENT.md +8 -4
- package/src/components/card/examples/person-header.tsx +38 -0
- package/src/components/composer/COMPONENT.md +35 -5
- package/src/components/composer/examples/bot-keyboard.tsx +43 -0
- package/src/components/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +18 -10
- package/src/components/data-table/COMPONENT.md +17 -11
- package/src/components/data-table/examples/screen.tsx +6 -9
- package/src/components/data-table/examples/selection.tsx +60 -18
- package/src/components/drawer/COMPONENT.md +41 -19
- package/src/components/drawer/examples/footer-actions.tsx +54 -0
- package/src/components/drawer/examples/header-actions.tsx +46 -25
- package/src/components/drawer/examples/link-loading.tsx +57 -0
- package/src/components/drawer/examples/screen.tsx +3 -5
- package/src/components/empty-page/COMPONENT.md +2 -2
- package/src/components/field-list/COMPONENT.md +4 -4
- package/src/components/field-list/examples/row-menu.tsx +1 -1
- package/src/components/hint/COMPONENT.md +1 -1
- package/src/components/input/COMPONENT.md +16 -3
- package/src/components/input/examples/password-policy.tsx +65 -0
- package/src/components/list/COMPONENT.md +1 -1
- package/src/components/login-form/COMPONENT.md +13 -11
- package/src/components/login-form/examples/overview.tsx +5 -5
- package/src/components/login-form/examples/states.tsx +7 -6
- package/src/components/message/COMPONENT.md +5 -5
- package/src/components/message/examples/bot-actions.tsx +47 -28
- package/src/components/modal/COMPONENT.md +31 -12
- package/src/components/modal/examples/structure.tsx +2 -4
- package/src/components/segmented-control/COMPONENT.md +11 -6
- package/src/components/segmented-control/examples/router.tsx +22 -0
- package/src/components/select/COMPONENT.md +4 -1
- package/src/components/sheet/COMPONENT.md +31 -12
- package/src/components/sheet/examples/header-actions.tsx +5 -3
- package/src/components/skeleton/COMPONENT.md +3 -2
- package/src/components/smart-filter/COMPONENT.md +2 -1
- package/src/components/smart-filter/examples/ranges.tsx +8 -2
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +7 -4
- package/src/components/tabs/examples/in-drawer.tsx +18 -2
- package/src/components/thumbnail/COMPONENT.md +1 -0
- package/src/components/tooltip/COMPONENT.md +3 -3
- package/src/layout/page/COMPONENT.md +1 -0
- package/src/layout/sidebar/COMPONENT.md +4 -2
- package/src/layout/sidebar/examples/account-profile.tsx +66 -0
- package/src/styles/theme-dark.css +1 -0
- package/src/styles/theme-light.css +1 -0
|
@@ -93,6 +93,7 @@ One entry of `fields`: data, not a part. Selected values missing from `options`
|
|
|
93
93
|
| `finite` | `boolean` | `true` | `options` fields: a fixed set — hiding every value is refused. `false` for an open set (services, users). |
|
|
94
94
|
| `multiple` | `boolean` | `true` | `options` fields: many values at once. `false` — one value and no hiding: a press picks only that value, a second press clears it (an API that takes one value per field). |
|
|
95
95
|
| `formatValue` | `(value: number) => string` | Russian digit groups | `range` fields: a bound in the chip («1 000 ₽»). |
|
|
96
|
+
| `calendar` | `Pick<DatepickerPanelProps, "months" \| "presets" \| "prompt" \| "footer" \| "withTime" \| "isDayDisabled" \| "disableFuture" \| "today" \| "locale" \| "weekStartsOn" \| "labels">` | — | `date-range` fields: the calendar of the period, as on a report's own Datepicker — `presets`, `months`, `prompt`, `footer`, `withTime`; the chip names a matching preset («Период: Этот месяц»). Without it the panel opens one month with no presets. |
|
|
96
97
|
|
|
97
98
|
### Functions
|
|
98
99
|
|
|
@@ -233,7 +234,7 @@ same height and type.
|
|
|
233
234
|
| [overview.tsx](examples/overview.tsx) | Filters above a request list: the filter button and the search, applied filters as tags, rows narrowed by the value and the text — `fields`, `value`, `search`, `matchesSmartFilter`. |
|
|
234
235
|
| [sizes.tsx](examples/sizes.tsx) | Every size tier: the filter button, the search, the tags and the panel follow one tier — `size`. |
|
|
235
236
|
| [many-values.tsx](examples/many-values.tsx) | An open set of twenty services, degraded ones marked with an icon, collapsed after eight; typing narrows the panel and marks the match in accent — `finite`, `icon`, `collapsedLimit`. |
|
|
236
|
-
| [ranges.tsx](examples/ranges.tsx) | Deals narrowed by an amount from–to and a period from
|
|
237
|
+
| [ranges.tsx](examples/ranges.tsx) | Deals narrowed by an amount from–to and a period from a calendar with presets, two months and an Apply footer; each range is one readable chip, and the bounds go to the server as from / to parameters — `kind`, `formatValue`, `calendar`, `matchesSmartFilter`, `isSmartFilterRange`. |
|
|
237
238
|
| [single-value.tsx](examples/single-value.tsx) | For an API that takes one value per field: the filter button without a search, a press picks only that value and nothing can be hidden — `searchable`, `multiple`. |
|
|
238
239
|
| [option-groups.tsx](examples/option-groups.tsx) | Values of different kinds in one field: owner types and people under their own headings — `options`, `group`. |
|
|
239
240
|
| [controlled.tsx](examples/controlled.tsx) | A saved view sets the value from outside; the selection is read back as the list to ask the server for — `value`, `onValueChange`, `resolveSmartFilterValues`. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
/** Deals narrowed by an amount from–to and a period from
|
|
1
|
+
/** Deals narrowed by an amount from–to and a period from a calendar with presets, two months and an Apply footer; each range is one readable chip, and the bounds go to the server as from / to parameters — `kind`, `formatValue`, `calendar`, `matchesSmartFilter`, `isSmartFilterRange`. */
|
|
2
2
|
import {
|
|
3
3
|
Card,
|
|
4
|
+
DEFAULT_DATEPICKER_PRESETS,
|
|
4
5
|
formatDateValue,
|
|
5
6
|
isSmartFilterRange,
|
|
6
7
|
matchesSmartFilter,
|
|
@@ -17,7 +18,12 @@ const formatRubles = (value: number) => `${value.toLocaleString("ru-RU")} ₽`;
|
|
|
17
18
|
|
|
18
19
|
const FIELDS: SmartFilterField[] = [
|
|
19
20
|
{ key: "amount", label: "Сумма", kind: "range", formatValue: formatRubles },
|
|
20
|
-
{
|
|
21
|
+
{
|
|
22
|
+
key: "closed",
|
|
23
|
+
label: "Период",
|
|
24
|
+
kind: "date-range",
|
|
25
|
+
calendar: { presets: DEFAULT_DATEPICKER_PRESETS, months: 2, prompt: true, footer: true },
|
|
26
|
+
},
|
|
21
27
|
{
|
|
22
28
|
key: "stage",
|
|
23
29
|
label: "Этап",
|
|
@@ -68,7 +68,7 @@ Spinner <span role="status">; size and tone
|
|
|
68
68
|
## States
|
|
69
69
|
| State | Driven by | DOM |
|
|
70
70
|
|---|---|---|
|
|
71
|
-
| appearing | mounted | fades in over `fast` (`internal/enterMotion`), so a spinner that comes and goes never blinks |
|
|
71
|
+
| appearing | mounted into a host already shown | fades in over `fast` (`internal/enterMotion`), so a spinner that comes and goes never blinks; one that mounts with its page stands still |
|
|
72
72
|
| spinning | mounted | the ring turns at a constant speed (`--prime-motion-loop × 3` per turn, linear) |
|
|
73
73
|
| reduced motion | `prefers-reduced-motion` | the ring keeps turning, twice as slow (`--prime-motion-loop × 6` per turn) and linear: a loading cue is information, so the token is not zeroed |
|
|
74
74
|
| size / tone | `size`, `tone` | `data-size` (the explicit size, else the host control tier, else `m`), `data-tone` (only for a non-default tone) |
|
|
@@ -35,7 +35,7 @@ Tabs.Root value, size (else the host tier), orientation, tone, f
|
|
|
35
35
|
│ │ ├─ Tabs.Count counter Badge, one tier below the tabs size
|
|
36
36
|
│ │ └─ Tabs.Description muted second line; makes the tab two-line
|
|
37
37
|
│ └─ Tabs.Separator Divider hairline between groups of tabs
|
|
38
|
-
└─ Tabs.Panel role="tabpanel", rendered only while its tab is active; no fill, no padding
|
|
38
|
+
└─ Tabs.Panel role="tabpanel", rendered only while its tab is active (keepMounted: kept hidden once opened); no fill, no padding
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
## API
|
|
@@ -120,11 +120,12 @@ Tabs.Root value, size (else the host tier), orientation, tone, f
|
|
|
120
120
|
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "children">` | — | `className` and the other div attributes. |
|
|
121
121
|
|
|
122
122
|
### Tabs.Panel
|
|
123
|
-
`ref` → `HTMLDivElement`. `<div role="tabpanel">`, focusable, rendered only while its tab is active. No fill and no padding: its content is the host's content, aligned with the strip's edges.
|
|
123
|
+
`ref` → `HTMLDivElement`. `<div role="tabpanel">`, focusable, rendered only while its tab is active (with `keepMounted`, kept hidden once opened). No fill and no padding: its content is the host's content, aligned with the strip's edges. Content opened after mount enters.
|
|
124
124
|
|
|
125
125
|
| Prop | Type | Default | Description |
|
|
126
126
|
|---|---|---|---|
|
|
127
127
|
| `value` | `string` | — (required) | Tab value this panel belongs to. |
|
|
128
|
+
| `keepMounted` | `boolean` | `false` | Once opened, the panel stays mounted and `hidden` while another tab is active: its data, form draft and scroll survive a switch. A panel never opened is not mounted. |
|
|
128
129
|
| `children` | `ReactNode` | — | Panel content. |
|
|
129
130
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className` and the other div attributes. |
|
|
130
131
|
|
|
@@ -187,13 +188,14 @@ Avoid icons or descriptions on only some tabs (uneven rows; without an icon on e
|
|
|
187
188
|
| disabled | `disabled` on Item | native `disabled`, `data-disabled="true"`, `text-disabled`; a removable tab loses its close button |
|
|
188
189
|
| removable | `onRemove` on Item | item wrapper `data-removable="true"`, close button, tab `aria-keyshortcuts="Delete"` |
|
|
189
190
|
| closing, confirm pending | `onRemove` returned a promise that has not settled | the close button is `loading` (`aria-busy`); the tab, its selection and focus stay; resolved → the neighbour takes over, rejected → nothing changes |
|
|
191
|
+
| kept | `keepMounted` on Panel, its tab once opened and now inactive | the panel stays mounted with `hidden` (out of the layout, the tab order and the accessibility tree); its tab keeps `aria-controls`; shown again, its content enters as on a first open |
|
|
190
192
|
| two-line | `Tabs.Description` child | `data-two-line="true"` |
|
|
191
193
|
| collapsed | tabs wider than the list, or a label that would be cut (horizontal) | list `data-collapse="full" \| "compact" \| "icon"`: `compact` hides icons and descriptions; `icon` takes labels, counts and close buttons out of the row — a `VisuallyHidden` copy of the label names the tab and a `Tooltip` shows it — only when every tab has an icon |
|
|
192
194
|
| overflow | tabs at their narrowest (content width, `minItemWidth` or icon squares) still wider than the list | `data-overflow-start` / `data-overflow-end` on the list (the switcher row, a ScrollContainer), faded edges as deep as the hidden part at most (they grow and melt with the scroll, never switch); when the end edge would cut only a tab's empty side, the tabs at `minItemWidth` widen a little (`--tabs-peek`, up to 30%) so a label fades out at the edge and shows that more tabs follow; the active tab is kept in view and clear of the fades on mount and on every value change, the first and the last tab scroll all the way to the end — only the list scrolls, never the page. A column (a wide vertical list) has plain edges |
|
|
193
195
|
|
|
194
196
|
The indicator glides (`emphasized` + `base`, `data-animate="true"`) only into a choice by click, arrow keys or closing the active tab; on mount, a value set from outside, a resize, font loading, tabs added or removed and collapse it is placed without motion, and it stays still under `prefers-reduced-motion`. The list scrolls smoothly to the active tab only after such a choice too.
|
|
195
197
|
|
|
196
|
-
Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-width`, inline `--tabs-item-min` / `--tabs-item-max` when set; the folder (the list's first child, `data-indicator`) `data-axis="x" | "y"` (follows the list's direction, so a vertical list in a narrow container gets the top folder); Item `data-value` on the tab, `data-state` / `data-disabled` on its wrapper too; Label `data-text`. Inactive panels are unmounted, and a tab points at its panel (`aria-controls`) only while the panel is in the document; a panel's content enters each time it opens.
|
|
198
|
+
Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-width`, inline `--tabs-item-min` / `--tabs-item-max` when set; the folder (the list's first child, `data-indicator`) `data-axis="x" | "y"` (follows the list's direction, so a vertical list in a narrow container gets the top folder); Item `data-value` on the tab, `data-state` / `data-disabled` on its wrapper too; Label `data-text`. Inactive panels are unmounted (unless `keepMounted`), and a tab points at its panel (`aria-controls`) only while the panel is in the document; a panel's content enters each time it opens after mount (the panel shown on mount stands still).
|
|
197
199
|
|
|
198
200
|
## Layout & spacing
|
|
199
201
|
- Tabs are a bar, not a surface: the root has no fill, padding, radius or `data-depth`. On a page or in a `Card.Body`, put it straight into the host's padded region and the panel content straight into `Tabs.Panel`. The strip's edges and the panel content share the host's content edge; the panel stands one tier gap below the strip (`--prime-space-3` … `--prime-space-8`).
|
|
@@ -261,7 +263,7 @@ Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-
|
|
|
261
263
|
| [overflow.tsx](examples/overflow.tsx) | Drag the frame narrower: icons go first, then labels, and icons with tooltips stay; then the list scrolls — `Tabs.Icon`. |
|
|
262
264
|
| [closable.tsx](examples/closable.tsx) | Open order cards as browser tabs: each closes by its button, Delete or a middle click; tabs stay between two widths, and the list scrolls when they do not fit — `onRemove`, `minItemWidth`, `maxItemWidth`. |
|
|
263
265
|
| [two-line.tsx](examples/two-line.tsx) | A label and a counter on the first line, a summary on the second — `Tabs.Count`, `Tabs.Description`. |
|
|
264
|
-
| [in-drawer.tsx](examples/in-drawer.tsx) | Order details in a Drawer: the tab bar runs along the bottom of the header, the active tab is the body rising into it,
|
|
266
|
+
| [in-drawer.tsx](examples/in-drawer.tsx) | Order details in a Drawer: the tab bar runs along the bottom of the header and scrolls sideways when the tabs outgrow the drawer, the active tab is the body rising into it, every panel is the body's own content, and the delivery form keeps its edits across tabs — `Tabs.Root`, `Tabs.List`, `Tabs.Panel`, `keepMounted`. |
|
|
265
267
|
| [controlled.tsx](examples/controlled.tsx) | The active tab lives in parent state, e.g. synced with the URL — `value`, `onValueChange`. |
|
|
266
268
|
|
|
267
269
|
## Mistakes
|
|
@@ -274,6 +276,7 @@ Other attributes: Root `data-orientation`, `data-size`, `data-tone`, `data-full-
|
|
|
274
276
|
- A hand-made × inside `Tabs.Item` → a button inside a tab button; pass `onRemove` instead.
|
|
275
277
|
- `onRemove` that does not drop the tab from your list → the tab stays; the kit only moves the selection.
|
|
276
278
|
- `Tabs.List` without `aria-label` → add one.
|
|
279
|
+
- Lifting a panel's data or form draft to the parent only so it survives a tab switch → `keepMounted` on that `Tabs.Panel`.
|
|
277
280
|
- Tabs inside `Drawer.Body` / `Modal.Body` under the header → a floating strip under the header's hairline; wrap the header and the body in `Tabs.Root` and put `Tabs.List` into the header.
|
|
278
281
|
|
|
279
282
|
## Related
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Order details in a Drawer: the tab bar runs along the bottom of the header, the active tab is the body rising into it,
|
|
1
|
+
/** Order details in a Drawer: the tab bar runs along the bottom of the header and scrolls sideways when the tabs outgrow the drawer, the active tab is the body rising into it, every panel is the body's own content, and the delivery form keeps its edits across tabs — `Tabs.Root`, `Tabs.List`, `Tabs.Panel`, `keepMounted`. */
|
|
2
2
|
import { Badge, Button, DescriptionList, Drawer, Fieldset, Input, Tabs } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
const ITEMS = [
|
|
@@ -7,6 +7,11 @@ const ITEMS = [
|
|
|
7
7
|
{ name: "Сборка и подъём на этаж", amount: "1 × 3 200 ₽" },
|
|
8
8
|
];
|
|
9
9
|
|
|
10
|
+
const DOCUMENTS = [
|
|
11
|
+
{ name: "Счёт № 1042-С", date: "3 октября" },
|
|
12
|
+
{ name: "Накладная № 1042-Н", date: "4 октября" },
|
|
13
|
+
];
|
|
14
|
+
|
|
10
15
|
const HISTORY = [
|
|
11
16
|
{ time: "3 октября, 10:14", event: "Заказ оформлен" },
|
|
12
17
|
{ time: "3 октября, 10:20", event: "Оплата прошла" },
|
|
@@ -33,6 +38,7 @@ export default function TabsInDrawerExample() {
|
|
|
33
38
|
<Tabs.Count>{ITEMS.length}</Tabs.Count>
|
|
34
39
|
</Tabs.Item>
|
|
35
40
|
<Tabs.Item value="delivery">Доставка</Tabs.Item>
|
|
41
|
+
<Tabs.Item value="documents">Документы</Tabs.Item>
|
|
36
42
|
<Tabs.Item value="history">История</Tabs.Item>
|
|
37
43
|
</Tabs.List>
|
|
38
44
|
</Drawer.Header>
|
|
@@ -58,7 +64,7 @@ export default function TabsInDrawerExample() {
|
|
|
58
64
|
</DescriptionList.Root>
|
|
59
65
|
</Tabs.Panel>
|
|
60
66
|
|
|
61
|
-
<Tabs.Panel value="delivery">
|
|
67
|
+
<Tabs.Panel value="delivery" keepMounted>
|
|
62
68
|
<Fieldset aria-label="Доставка">
|
|
63
69
|
<Input.Root label="Адрес">
|
|
64
70
|
<Input.Wrapper>
|
|
@@ -73,6 +79,16 @@ export default function TabsInDrawerExample() {
|
|
|
73
79
|
</Fieldset>
|
|
74
80
|
</Tabs.Panel>
|
|
75
81
|
|
|
82
|
+
<Tabs.Panel value="documents">
|
|
83
|
+
<DescriptionList.Root>
|
|
84
|
+
{DOCUMENTS.map((document) => (
|
|
85
|
+
<DescriptionList.Item key={document.name} label={document.name}>
|
|
86
|
+
{document.date}
|
|
87
|
+
</DescriptionList.Item>
|
|
88
|
+
))}
|
|
89
|
+
</DescriptionList.Root>
|
|
90
|
+
</Tabs.Panel>
|
|
91
|
+
|
|
76
92
|
<Tabs.Panel value="history">
|
|
77
93
|
<DescriptionList.Root>
|
|
78
94
|
{HISTORY.map((entry) => (
|
|
@@ -142,6 +142,7 @@ Thumbnail.Root frame (a <div>; the child with asChild; a <button role="
|
|
|
142
142
|
- In a table the row grows to the thumbnail; never shrink the thumbnail to the row.
|
|
143
143
|
- Card covers: `fullWidth` inside the card body, the body's gap to the title.
|
|
144
144
|
- No outline by default: the fill separates the preview.
|
|
145
|
+
- In a leading media slot (`Drawer.Icon`, `Modal.Icon`, `Sheet.Icon`, `Card.Icon`) the thumbnail takes the slot's size (`m`, 40px) whatever `size` it was given, also through a wrapper component of yours; the slot then draws no tile.
|
|
145
146
|
|
|
146
147
|
## Accessibility
|
|
147
148
|
|
|
@@ -110,7 +110,7 @@ A chip of tier T is as tall as a menu item of tier T; text, padding and radius s
|
|
|
110
110
|
## States
|
|
111
111
|
| State | Driven by | DOM |
|
|
112
112
|
|---|---|---|
|
|
113
|
-
| open / closed | hover (mouse, pen) or focus after `delayDuration
|
|
113
|
+
| open / closed | hover (mouse, pen) or keyboard focus after `delayDuration` (not the focus a click causes or a dialog moves in after a click); `open` / `defaultOpen` / `onOpenChange` | `data-state="open" \| "closed"` on the chip (closed during the exit) and on the trigger |
|
|
114
114
|
| closing | blur, a press on the trigger, Escape, pointer leaving (unless it moves onto the chip) | — |
|
|
115
115
|
| warm group | another tooltip of the Provider was just shown | `data-instant="true"`: opens and leaves without motion |
|
|
116
116
|
| side | `side` and the room around the trigger | `data-side` (resolved), `data-align` |
|
|
@@ -128,7 +128,7 @@ A chip of tier T is as tall as a menu item of tier T; text, padding and radius s
|
|
|
128
128
|
|
|
129
129
|
| Key | Action |
|
|
130
130
|
|---|---|
|
|
131
|
-
| `Tab` |
|
|
131
|
+
| `Tab` | Keyboard focus on the trigger opens the tooltip after the delay; focus a click causes, or a layer moves in after a click, does not. |
|
|
132
132
|
| `Escape` | Hides the tooltip; focus stays on the trigger. |
|
|
133
133
|
|
|
134
134
|
### ARIA
|
|
@@ -137,7 +137,7 @@ A chip of tier T is as tall as a menu item of tier T; text, padding and radius s
|
|
|
137
137
|
- The chip is `role="tooltip"`; while open the trigger points at it with `aria-describedby` (appended to the child's own).
|
|
138
138
|
- `Tooltip.Trigger` composes the child's `ref`, sets `data-state` and chains the child's `onPointerEnter`, `onPointerLeave`, `onPointerDown`, `onFocus` and `onBlur`.
|
|
139
139
|
- The tooltip is a description, not the name: an icon-only button needs its own `aria-label`.
|
|
140
|
-
- The pointer may move onto the chip without closing it (WCAG 1.4.13); a touch does not open it, focus does.
|
|
140
|
+
- The pointer may move onto the chip without closing it (WCAG 1.4.13); a touch does not open it, keyboard focus does.
|
|
141
141
|
- Tooltip has no `closeOnEscape` / `closeOnOutsideClick` (foundation §8): it closes on pointer-leave, blur, a press on the trigger and Escape, and Escape cannot be turned off — WCAG 1.4.13 requires the chip to be dismissable without moving the pointer or focus.
|
|
142
142
|
- A `disabled` control (its own prop or a disabled `Fieldset`) is wrapped by the kit in a focusable `<span>`: the host takes hover and Tab, the focus ring is drawn on the control. Around a button the host is that button for assistive tech — `role="button"`, `aria-disabled`, named by the control through `aria-labelledby` — and the control itself leaves the tree.
|
|
143
143
|
- A trigger that is no tab stop (Badge, Avatar, ProgressCircle, `Icon` with `label`) gets `tabIndex={0}` and the focus ring; its name comes from `aria-label` or the icon `label`.
|
|
@@ -292,6 +292,7 @@ A wide cell (`span` 7–11) written next to a narrow one (`span` 4–5) is a pai
|
|
|
292
292
|
- The edge air comes from the host: `AppShell.Main` sets `--page-gutter` (the page gutter, 16 → 24 → 32) and `--page-top` (24 → 32 from 1024px); `Page.Root` pads itself with them. Outside a host the page brings no air. The page resets both for what it holds, so a page nested in it starts clean.
|
|
293
293
|
- `Page.TopBar` steps out of the gutter to the panel's edges and keeps the gutter inside; padding 12 above (or the top safe-area inset) and below; what follows starts 24 under its controls (header → body; the bar's own 12 counts in). The first line is as high as the Sidebar brand row (control m + 8), so the two read as one line. Groups 16 apart on a line, lines 12 apart; tools 8 apart; buttons 8 apart.
|
|
294
294
|
- `Page.Tools` and `Page.Chips` are the kit's one tool row and chips line (`ToolRow`), the same as the DataTable `toolbar` and `toolbarChips`: the bar only places them. On the first line every tool keeps its natural width; on a line of their own they take the line and lay out from its width (container `prime-tool-row`), never from the window.
|
|
295
|
+
- The tool row lays out the elements it is given, each in a cell: pass the tools as direct children of `Page.Tools` or a fragment. A wrapper component that returns several controls is one cell — no gaps between them, no growing search.
|
|
295
296
|
- In the bar only the heading's column gives (`minmax(0, 1fr)`): the tools and the actions keep their width. The heading keeps its natural width until every button step is taken; then a heading with a path stacks (the path over the title, the title and the description wrapping), so no level is cut to a fragment; a heading without a path truncates its title.
|
|
296
297
|
- The plan is measured, never chosen by breakpoints: the bar lays itself out from its own width.
|
|
297
298
|
- Header (flow) → Body 24; blocks inside Body 24; a `Page.Section` 40 from its neighbours (the body's 24 + 16); tile → tile in a Grid 16 on both axes; section heading → content 16, heading → description 4; flow Title → Description 8; heading → Actions 24 side by side, 16 once they wrap below.
|
|
@@ -70,7 +70,7 @@ Sidebar.Root rail wrapper; renders <nav> inside; size tier
|
|
|
70
70
|
│ ├─ Sidebar.SubTrigger disclosure row, chevron at the end
|
|
71
71
|
│ └─ Sidebar.SubContent child Items on a guide line (a flyout on the compact rail)
|
|
72
72
|
└─ Sidebar.Footer bottom items, the item Toggle, the account
|
|
73
|
-
└─ Sidebar.Account avatar + name + muted line; a Dropdown.Trigger child
|
|
73
|
+
└─ Sidebar.Account avatar + name + muted line; a ↕ chevron as a Dropdown.Trigger child
|
|
74
74
|
useSidebar() state hook for custom parts inside Root (useAppShellNav outside it, in a shell)
|
|
75
75
|
```
|
|
76
76
|
|
|
@@ -220,7 +220,7 @@ useSidebar() state hook for custom parts inside Root (useA
|
|
|
220
220
|
| `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "role">` | — | `children` (Items), `className` (on the list) and the other div attributes. |
|
|
221
221
|
|
|
222
222
|
### Sidebar.Account
|
|
223
|
-
`ref` → `HTMLButtonElement`. The signed-in person at the bottom of `Sidebar.Footer`: avatar, name, a muted line
|
|
223
|
+
`ref` → `HTMLButtonElement`. The signed-in person at the bottom of `Sidebar.Footer`: avatar, name, a muted line; a ↕ chevron only as a `Dropdown.Trigger` child. In compact mode only the avatar stays, with the name as a tooltip.
|
|
224
224
|
|
|
225
225
|
| Prop | Type | Default | Description |
|
|
226
226
|
|---|---|---|---|
|
|
@@ -311,6 +311,7 @@ Put `<Sidebar.Toggle variant="header" />` next to `Sidebar.Brand` in the header,
|
|
|
311
311
|
| flyout | hover / click / keyboard on a SubTrigger, or on the `…` row of a folded group, in compact mode | the row `aria-haspopup="dialog"`, `aria-expanded`; a Popover `role="dialog"` |
|
|
312
312
|
| attention dot | a coloured `Sidebar.ItemCount` inside a folded Sub (always on the rail) or a folded Group | `Badge.Dot` with `data-attention="shown" \| "hidden"` on the parent row / heading / `…` row |
|
|
313
313
|
| persisted | `persistKey` on Root | the mode and the open state of collapsible groups and sub-lists are read from and written to `localStorage` |
|
|
314
|
+
| account menu | `Sidebar.Account` inside a `Dropdown.Trigger` (`aria-haspopup="menu"`) | the ↕ chevron at the end of the row; without a menu (a plain `onClick`, a `Drawer.Trigger`) no chevron |
|
|
314
315
|
| disabled | `disabled` | Item `disabled` / `aria-disabled`, `data-disabled` |
|
|
315
316
|
|
|
316
317
|
`persistKey` stores collapsible groups and sub-lists by their `id`, else by their label text — give an `id` when labels are translated or change. The stored state wins over `defaultMode` / `defaultOpen`; controlled `mode` / `open` are not stored, and unavailable storage is ignored. A hidden rail is stored as the mode it left, so a reload never strands a person on a hidden rail.
|
|
@@ -388,6 +389,7 @@ Leaving the narrow viewport closes the off-canvas panel; activating an item (a l
|
|
|
388
389
|
| [collapsible-groups.tsx](examples/collapsible-groups.tsx) | Group headings that fold their items away, one group open at a time, with a chevron at the end of the heading; on the compact rail a folded group becomes one row whose items open in a flyout — `collapsible`, `open`, `onOpenChange`. |
|
|
389
390
|
| [nested-items.tsx](examples/nested-items.tsx) | A parent item with child items on a guide line: a current child opens it and marks the parent; on the compact rail the children open in a flyout — `Sidebar.Sub`, `Sidebar.SubTrigger`, `Sidebar.SubContent`. |
|
|
390
391
|
| [account.tsx](examples/account.tsx) | Footer items and the collapse row above the signed-in person: avatar, name and email open the account menu; collapse the rail from that row and only the avatar stays, on the icon axis — `Sidebar.Footer`, `Sidebar.Account`, `description`, `Sidebar.Toggle`. |
|
|
392
|
+
| [account-profile.tsx](examples/account-profile.tsx) | The signed-in person opens a profile panel, not a menu, so the row draws no chevron — `Sidebar.Account`, `description`. |
|
|
391
393
|
| [router.tsx](examples/router.tsx) | Router links as the brand and the items: the router marks the current link and the item shows it as current; render it inside a router — `asChild`. |
|
|
392
394
|
| [persist.tsx](examples/persist.tsx) | The first visit opens on the icon rail; the mode and the folded groups are remembered and come back after a reload — `persistKey`, `defaultMode`. |
|
|
393
395
|
| [custom-part.tsx](examples/custom-part.tsx) | A part of your own in the footer reads the rail state: the storage meter shows on the full rail and in the off-canvas panel and steps aside on the icon rail — `useSidebar`. |
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** The signed-in person opens a profile panel, not a menu, so the row draws no chevron — `Sidebar.Account`, `description`. */
|
|
2
|
+
import { Avatar, Button, DescriptionList, Drawer, Icon, Sidebar } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
import styles from "./examples.module.css";
|
|
5
|
+
|
|
6
|
+
const PROFILE = [
|
|
7
|
+
{ term: "Почта", value: "anna@company.ru" },
|
|
8
|
+
{ term: "Роль", value: "Руководитель отдела продаж" },
|
|
9
|
+
{ term: "Часовой пояс", value: "Москва, UTC+3" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export default function SidebarAccountProfileExample() {
|
|
13
|
+
return (
|
|
14
|
+
<div className={styles.stage}>
|
|
15
|
+
<Sidebar.Root offCanvas="never">
|
|
16
|
+
<Sidebar.Content>
|
|
17
|
+
<Sidebar.Item current>
|
|
18
|
+
<Sidebar.ItemIcon>
|
|
19
|
+
<Icon name="nav.home" />
|
|
20
|
+
</Sidebar.ItemIcon>
|
|
21
|
+
Обзор
|
|
22
|
+
</Sidebar.Item>
|
|
23
|
+
<Sidebar.Item>
|
|
24
|
+
<Sidebar.ItemIcon>
|
|
25
|
+
<Icon name="object.document" />
|
|
26
|
+
</Sidebar.ItemIcon>
|
|
27
|
+
Сделки
|
|
28
|
+
</Sidebar.Item>
|
|
29
|
+
</Sidebar.Content>
|
|
30
|
+
<Sidebar.Footer>
|
|
31
|
+
<Drawer.Root>
|
|
32
|
+
<Drawer.Trigger>
|
|
33
|
+
<Sidebar.Account description="anna@company.ru">
|
|
34
|
+
<Avatar.Root color="purple">
|
|
35
|
+
<Avatar.Fallback>АС</Avatar.Fallback>
|
|
36
|
+
</Avatar.Root>
|
|
37
|
+
Анна Смирнова
|
|
38
|
+
</Sidebar.Account>
|
|
39
|
+
</Drawer.Trigger>
|
|
40
|
+
<Drawer.Content size="s">
|
|
41
|
+
<Drawer.Header>
|
|
42
|
+
<Drawer.Title>Анна Смирнова</Drawer.Title>
|
|
43
|
+
<Drawer.Description>Профиль</Drawer.Description>
|
|
44
|
+
</Drawer.Header>
|
|
45
|
+
<Drawer.Body>
|
|
46
|
+
<DescriptionList.Root>
|
|
47
|
+
{PROFILE.map(({ term, value }) => (
|
|
48
|
+
<DescriptionList.Item key={term} label={term}>
|
|
49
|
+
{value}
|
|
50
|
+
</DescriptionList.Item>
|
|
51
|
+
))}
|
|
52
|
+
</DescriptionList.Root>
|
|
53
|
+
</Drawer.Body>
|
|
54
|
+
<Drawer.Footer>
|
|
55
|
+
<Button.Root variant="outline" tone="neutral">
|
|
56
|
+
Выйти
|
|
57
|
+
</Button.Root>
|
|
58
|
+
</Drawer.Footer>
|
|
59
|
+
</Drawer.Content>
|
|
60
|
+
</Drawer.Root>
|
|
61
|
+
</Sidebar.Footer>
|
|
62
|
+
</Sidebar.Root>
|
|
63
|
+
<div className={styles.content} />
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
--prime-color-palette-yellow-solid-fg: var(--prime-ref-color-gray-950);
|
|
131
131
|
--prime-color-palette-yellow-text: var(--prime-ref-color-yellow-300);
|
|
132
132
|
--prime-color-skeleton-fill: color-mix(in srgb, var(--prime-ref-color-gray-100) 10%, transparent);
|
|
133
|
+
--prime-color-success-border: var(--prime-ref-color-green-400);
|
|
133
134
|
--prime-color-success-default: var(--prime-ref-color-green-400);
|
|
134
135
|
--prime-color-success-fg: var(--prime-ref-color-gray-950);
|
|
135
136
|
--prime-color-success-hover: var(--prime-ref-color-green-300);
|
|
@@ -205,6 +205,7 @@
|
|
|
205
205
|
--prime-color-palette-yellow-solid-fg: var(--prime-ref-color-gray-925);
|
|
206
206
|
--prime-color-palette-yellow-text: var(--prime-ref-color-yellow-800);
|
|
207
207
|
--prime-color-skeleton-fill: color-mix(in srgb, var(--prime-ref-color-gray-925) 6%, transparent);
|
|
208
|
+
--prime-color-success-border: var(--prime-ref-color-green-500);
|
|
208
209
|
--prime-color-success-default: var(--prime-ref-color-green-700);
|
|
209
210
|
--prime-color-success-fg: var(--prime-ref-color-white);
|
|
210
211
|
--prime-color-success-hover: var(--prime-ref-color-green-800);
|