prime-ui-kit 0.20.1 → 0.21.1
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/page/Page.d.ts +1 -1
- 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 +20 -6
- package/dist/skill/choosing.md +82 -8
- package/dist/skill/components.md +1 -1
- package/dist/skill/composition.md +268 -51
- package/dist/skill/foundations.md +17 -6
- package/dist/skill/layouts.md +65 -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 +10 -6
- package/dist/skill/reference/layout/page/examples/router.tsx +124 -0
- package/dist/skill/reference/layout/page/examples/tools.tsx +12 -22
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +8 -3
- 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 +10 -6
- package/src/layout/page/examples/router.tsx +124 -0
- package/src/layout/page/examples/tools.tsx +12 -22
- package/src/layout/sidebar/COMPONENT.md +8 -3
- 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
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
## When to use
|
|
9
9
|
- Editing settings or a record without leaving the page.
|
|
10
10
|
- Filters or navigation on narrow screens (`side="left"`).
|
|
11
|
-
- Read-only details of a list row (order, user, event), with its status and a ⋯ menu in the header (`Drawer.Actions`).
|
|
11
|
+
- Read-only details of a list row (order, user, event), with its status under the title (`Drawer.Description`) and a ⋯ menu in the header (`Drawer.Actions`).
|
|
12
12
|
- Long content (history, logs) that needs a tall scrolling area.
|
|
13
13
|
|
|
14
14
|
## When not to use
|
|
@@ -30,13 +30,14 @@ Drawer.Root state and dismiss policy (no DOM)
|
|
|
30
30
|
└─ Drawer.Content portal + scrim + role="dialog" panel at the side
|
|
31
31
|
├─ Drawer.Guard no DOM: holds the drawer (busy / dirty) from inside
|
|
32
32
|
├─ Drawer.Header grid: [Icon] [Title + Description] [Actions] [close Button]
|
|
33
|
-
│ ├─ Drawer.Icon tone tile around an Icon, or
|
|
34
|
-
│ ├─ Drawer.Title <h2
|
|
35
|
-
│ ├─ Drawer.Description <p
|
|
36
|
-
│ └─ Drawer.Actions
|
|
33
|
+
│ ├─ Drawer.Icon tone tile around an Icon, or the record's Avatar / Thumbnail, no tile, 40
|
|
34
|
+
│ ├─ Drawer.Title line 1: <h2>; `loading` holds it with a skeleton
|
|
35
|
+
│ ├─ Drawer.Description line 2: <p>; the record's status Badges lead it (tier s), then the text
|
|
36
|
+
│ └─ Drawer.Actions buttons only (icon buttons, a ⋯ Dropdown), tier s, on the title's line
|
|
37
37
|
├─ Drawer.Body the only scrolling zone (ScrollContainer)
|
|
38
38
|
│ └─ Drawer.Section <section>: <h3> label, description, Drawer.Actions at the heading's end
|
|
39
39
|
└─ Drawer.Footer always: Close or Cancel + the main action, primary last
|
|
40
|
+
├─ Drawer.Actions the start group: an action apart from the main pair
|
|
40
41
|
├─ Drawer.Meta <p>, muted status at the start
|
|
41
42
|
└─ Drawer.Close clones its child; closes on click
|
|
42
43
|
```
|
|
@@ -81,19 +82,19 @@ No DOM, no ref. State and dismiss policy.
|
|
|
81
82
|
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
|
|
82
83
|
|
|
83
84
|
### Drawer.Actions
|
|
84
|
-
`ref` → `HTMLDivElement`.
|
|
85
|
+
`ref` → `HTMLDivElement`. Header buttons only (icon buttons, a ⋯ Dropdown; a status goes in `Drawer.Description`), tier `s`, on the title's line; under it below 12rem; at the end of a `Drawer.Section` heading. In `Drawer.Footer`: the start group at the footer tier.
|
|
85
86
|
|
|
86
87
|
| Prop | Type | Default | Description |
|
|
87
88
|
|---|---|---|---|
|
|
88
89
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `children` and the other attributes of the row. |
|
|
89
90
|
|
|
90
91
|
### Drawer.Icon
|
|
91
|
-
`ref` → `HTMLSpanElement`. An `aria-hidden` 40 px tile with a tone fill; around an `Avatar.Root` or a `Thumbnail.Root`
|
|
92
|
+
`ref` → `HTMLSpanElement`. An `aria-hidden` 40 px tile with a tone fill; around the record's picture (an `Avatar.Root` or a `Thumbnail.Root` anywhere inside, also from your own component) no tile (`data-media`), the picture at 40 px.
|
|
92
93
|
|
|
93
94
|
| Prop | Type | Default | Description |
|
|
94
95
|
|---|---|---|---|
|
|
95
96
|
| `tone` | `"neutral" \| "accent" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Soft fill and icon color; not used around a picture. |
|
|
96
|
-
| `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (
|
|
97
|
+
| `children` | `ReactNode` | — (required) | An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (size `l`) or a `Thumbnail.Root` (size `m`), set by the slot. |
|
|
97
98
|
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other attributes of the tile. |
|
|
98
99
|
|
|
99
100
|
### Drawer.Title
|
|
@@ -101,10 +102,11 @@ No DOM, no ref. State and dismiss policy.
|
|
|
101
102
|
|
|
102
103
|
| Prop | Type | Default | Description |
|
|
103
104
|
|---|---|---|---|
|
|
105
|
+
| `loading` | `boolean` | `false` | The name loads (a record opened by a link): a `Skeleton` holds the title line and `aria-busy` is set, so the header never moves; the name fades in. |
|
|
104
106
|
| `…rest` | `Omit<HTMLAttributes<HTMLHeadingElement>, "id">` | — | `className`, `children` and the other attributes except `id` (the dialog owns it). |
|
|
105
107
|
|
|
106
108
|
### Drawer.Description
|
|
107
|
-
`ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog.
|
|
109
|
+
`ref` → `HTMLParagraphElement`. `<p>` (body-s, muted) under the title; its id describes the dialog. The record's status `Badge.Root`s go here: first in the line, chip tier `s` (20 px), the text wraps under them when narrow.
|
|
108
110
|
|
|
109
111
|
| Prop | Type | Default | Description |
|
|
110
112
|
|---|---|---|---|
|
|
@@ -128,7 +130,7 @@ No DOM, no ref. State and dismiss policy.
|
|
|
128
130
|
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content and an optional `Drawer.Actions`), `className` and the other attributes of the `<section>`. |
|
|
129
131
|
|
|
130
132
|
### Drawer.Footer
|
|
131
|
-
`ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last
|
|
133
|
+
`ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last; at the start an optional `Drawer.Actions` (an action apart from the main pair) and `Meta`. Stacked, the start comes first and the primary stays last.
|
|
132
134
|
|
|
133
135
|
| Prop | Type | Default | Description |
|
|
134
136
|
|---|---|---|---|
|
|
@@ -182,7 +184,7 @@ Below 640px of viewport every size is full width and loses its corners.
|
|
|
182
184
|
| `fill` | equal-width buttons in one row | one full-width action («Понятно») | |
|
|
183
185
|
|
|
184
186
|
### tone (Icon)
|
|
185
|
-
`neutral` (default) · `accent` · `success` · `warning` · `danger` · `info` — soft tile fill and icon color, as in Modal. Around the record's picture — an `Avatar.Root` or a `Thumbnail.Root`
|
|
187
|
+
`neutral` (default) · `accent` · `success` · `warning` · `danger` · `info` — soft tile fill and icon color, as in Modal. Around the record's picture — an `Avatar.Root` or a `Thumbnail.Root` anywhere inside, also from your own wrapper component (`<ClientAvatar />`) — there is no tile and no tone: `data-media`, and the picture takes the tile's 40 px (avatar `l`, thumbnail `m`) whatever `size` it was given, so every record header has the same mark.
|
|
186
188
|
|
|
187
189
|
## States
|
|
188
190
|
| State | Driven by | DOM |
|
|
@@ -195,16 +197,29 @@ Below 640px of viewport every size is full width and loses its corners.
|
|
|
195
197
|
| busy | `Drawer.Guard busy` | Escape, the scrim, the close button and `Drawer.Close` do nothing; the close button and the `Close` child are `disabled` in place |
|
|
196
198
|
| dirty | `Drawer.Guard dirty` | a dismiss opens the «Не сохранять изменения?» ConfirmDialog (`labels.discard*`); its confirm closes the drawer |
|
|
197
199
|
| leaving the tab | `Drawer.Guard busy` or `dirty` | the browser asks before the tab closes or reloads (`beforeunload`), as with `Page.Guard` |
|
|
200
|
+
| title loading | `Drawer.Title loading` | a `Skeleton` holds the title line, `aria-busy` on the `<h2>`; the name fades in under the same element |
|
|
198
201
|
| description loading | `Drawer.Description loading` | a `Skeleton` holds the line, `aria-busy` on the `<p>`; the data fades in under the same element |
|
|
199
202
|
| actions below | the title would get less than 12rem beside `Drawer.Actions` | `data-actions-below` on the header: the actions move under the title and the description |
|
|
203
|
+
| picture mark | an `Avatar.Root` or a `Thumbnail.Root` inside `Drawer.Icon` | `data-media` on the icon, no tile; the picture's `data-size` is the slot's (`l` / `m`) |
|
|
204
|
+
| status in the line | a `Badge.Root` inside `Drawer.Description` | the line is a wrapping flex row, the badges first (`order`), each `data-tier="s"` |
|
|
200
205
|
|
|
201
206
|
Motion: the scrim fades, the panel slides from its side over `slow` and leaves over `base` (`overlayMotion`); under `prefers-reduced-motion` it unmounts at once. The drawer has no touch gestures: it is not dragged or swiped away — it closes by its buttons, Escape and the scrim, with the same slide-out.
|
|
202
207
|
|
|
203
208
|
## Layout & spacing
|
|
209
|
+
- The header has one anatomy, the same in every drawer, modal and sheet:
|
|
210
|
+
```
|
|
211
|
+
[mark] Title [icon buttons] [⋯] [×]
|
|
212
|
+
[status badges] description text
|
|
213
|
+
```
|
|
214
|
+
- mark — `Drawer.Icon`: a glyph on the tone tile, or the record's picture (`Avatar.Root`, `Thumbnail.Root`) with no tile; both are 40 px, so the header never changes its height between a person and a setting.
|
|
215
|
+
- line 1 — `Drawer.Title`, the record's name.
|
|
216
|
+
- line 2 — `Drawer.Description`: the record's statuses as `Badge.Root`s first, then its text (an email, a handle, «Заказ № 1042 · 3 коробки»). The kit sizes the badges (chip tier `s`, the line's own 20 px), keeps them first and one gap apart, and wraps the text under them on a narrow panel. A status belongs to the record, so it lives on its line, never next to the buttons.
|
|
217
|
+
- end — `Drawer.Actions`: only buttons — ghost icon buttons, each named and with a Tooltip, and a ⋯ Dropdown for the rest — at the close button's tier, centred on the title's line.
|
|
204
218
|
- A drawer has three zones: `Drawer.Header` (title, the × close button), `Drawer.Body` (the content, the only zone that scrolls) and `Drawer.Footer` (the actions). The footer is not optional decoration — it is where the drawer is finished:
|
|
205
219
|
- read-only details: `Drawer.Close` around `<Button.Root variant="outline" tone="neutral">Закрыть</Button.Root>`, plus the record's main action (solid, last) when it has one;
|
|
206
220
|
- a form: «Отмена» (`Drawer.Close`, outline neutral) and the submit button (solid, last; `type="submit" form={formId}`), `Drawer.Meta` for «Есть несохранённые изменения»;
|
|
207
|
-
- one acknowledgement: `<Drawer.Footer layout="fill"
|
|
221
|
+
- one acknowledgement: `<Drawer.Footer layout="fill">`;
|
|
222
|
+
- an action apart from the main pair («Выйти», «Удалить» — the latter through a ConfirmDialog): `Drawer.Actions` inside the footer puts it at the start, at the footer's tier; stacked on a phone it comes first and the primary stays last.
|
|
208
223
|
Both the header × and the footer button stay: the × is the quick way out at the top, the footer is the end of the reading or editing path, where the hand already is.
|
|
209
224
|
- Zone padding `--prime-drawer-padding`; hairlines between header, body and footer.
|
|
210
225
|
- Full viewport height; only the body scrolls, with its own side padding.
|
|
@@ -212,8 +227,9 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
212
227
|
- Fields inside get the surface field fill.
|
|
213
228
|
- Titled groups of the body (a record's contacts, its deals, its history): `Drawer.Section label` — an `<h3>` under the drawer's `<h2>` title, an optional `description`, a `Drawer.Actions` at the end of its heading row; content 16 below, sections 32 apart — directly in the body, in a `Tabs.Panel` or in a form, with no spacing wrapper of your own. A group of fields is a `Fieldset`; a page section is `Page.Section`.
|
|
214
229
|
- With a `Drawer.Meta` the buttons stay one row: when the footer runs short the status goes above them first.
|
|
215
|
-
- `Drawer.Actions` keep their place beside the title while it has 12rem; on a phone, where
|
|
216
|
-
-
|
|
230
|
+
- `Drawer.Actions` keep their place beside the title while it has 12rem; on a phone, where the buttons beside the × would fold the title into letters, they move under it by themselves.
|
|
231
|
+
- Opening a record: the header and its tabs come at once, the body as a `Skeleton` in the data's geometry. A record opened from a list row comes with the data the row already has (its title, often its status) at once, and only the rest loads (`Drawer.Description loading`); a record opened by a link knows only its id, so `Drawer.Title loading` holds the title line too. Read-only data swaps under a `Crossfade`; a form gets no `Crossfade` around it — its fields render once the record is there, so typing and focus are never cross-faded. Never a skeleton of your own in the header.
|
|
232
|
+
- Focus on open lands on the panel itself, so a header action (a ⋯ with a Tooltip) is not focused and its tooltip does not pop up; a form that should start typing at once puts `autoFocus` on its first field.
|
|
217
233
|
|
|
218
234
|
## Accessibility
|
|
219
235
|
|
|
@@ -229,7 +245,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
229
245
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
230
246
|
|
|
231
247
|
- `role="dialog"`, `aria-modal="true"`, named by `Drawer.Title` (or `aria-label`), described by `Drawer.Description`.
|
|
232
|
-
-
|
|
248
|
+
- On open focus moves to the panel itself (its title and description are read; an `autoFocus` field wins), without the ring after a click; Tab goes on to the first control. On close it returns to the opener, including after a scrim click.
|
|
233
249
|
- The page behind is `inert`, scroll is locked; only the topmost layer reacts.
|
|
234
250
|
- A drawer opened from a Modal stacks above it.
|
|
235
251
|
- No swipe: the close button, `Drawer.Close`, Escape and the scrim are the ways out.
|
|
@@ -253,9 +269,11 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
253
269
|
| [structure.tsx](examples/structure.tsx) | Optional parts: an icon tile in the header and one full-width action in the footer — `Drawer.Icon`, `Drawer.Footer`, `layout`. |
|
|
254
270
|
| [sizes.tsx](examples/sizes.tsx) | Every panel width, 360 to 800 px; below 640 px of viewport the panel takes the full width — `size`. |
|
|
255
271
|
| [placement.tsx](examples/placement.tsx) | The panel slides from the right for details and from the left for filters, rounded only on the edge facing the page — `side`. |
|
|
256
|
-
| [header-actions.tsx](examples/header-actions.tsx) |
|
|
272
|
+
| [header-actions.tsx](examples/header-actions.tsx) | The one header of a record: the avatar, the name, its statuses leading the line under it, and only buttons at the end — `Drawer.Icon`, `Drawer.Description`, `Drawer.Actions`. |
|
|
273
|
+
| [footer-actions.tsx](examples/footer-actions.tsx) | A profile panel: Sign out stands apart at the start of the footer, the main pair keeps the end; on a phone the column puts it first and the primary last — `Drawer.Footer`, `Drawer.Actions`. |
|
|
257
274
|
| [client-card.tsx](examples/client-card.tsx) | A client card from the list: the contact's avatar heads the panel in place of an icon tile, then the details in titled sections, one with its own action — `Drawer.Icon`, `Avatar.Root`, `Drawer.Section`, `Drawer.Actions`. |
|
|
258
275
|
| [loading.tsx](examples/loading.tsx) | A record card that opens before its data arrives: the title comes from the list row, the line under it and the body hold their place in skeletons, then the data fades in — `Drawer.Description`, `loading`. |
|
|
276
|
+
| [link-loading.tsx](examples/link-loading.tsx) | A record opened by a link knows only its id: the title and the line under it hold their place in skeletons, the body keeps the data's geometry, then the name and the data fade in — `Drawer.Title`, `loading`. |
|
|
259
277
|
| [guard.tsx](examples/guard.tsx) | A form that holds the drawer from inside: after an edit Escape, the scrim and Cancel ask before discarding, the footer says so, and saving locks every way out — `Drawer.Guard`, `Drawer.Meta`. |
|
|
260
278
|
| [long-content.tsx](examples/long-content.tsx) | A long body scrolls on its own while the header and the footer stay in place — `Drawer.Body`. |
|
|
261
279
|
| [dismiss.tsx](examples/dismiss.tsx) | An import closes only from its buttons, and not at all while it runs — `closeOnOutsideClick`, `closeOnEscape`. |
|
|
@@ -269,15 +287,19 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
269
287
|
- A delete confirmation in a Drawer → use ConfirmDialog.
|
|
270
288
|
- A hand-made heading row (`Typography as="h3"` + buttons) over a group in the body → `Drawer.Section label` with `Drawer.Actions`.
|
|
271
289
|
- A wrapper with its own gap around `Drawer.Section`s inside a `Tabs.Panel` or a form → not needed: sections keep 32 between them in any wrapper.
|
|
272
|
-
- A `Skeleton` placed by hand in `Drawer.Description` while the record loads → `Drawer.
|
|
290
|
+
- A `Skeleton` placed by hand in `Drawer.Title` or `Drawer.Description` while the record loads → their `loading`; a placeholder title that shifts the header when the name arrives → `Drawer.Title loading`.
|
|
291
|
+
- A start-side button («Выйти») put inside `Drawer.Meta` → `Drawer.Actions` inside `Drawer.Footer`; `Meta` is status text only.
|
|
273
292
|
- Busy or unsaved state lifted out of the form into Root props → put `Drawer.Guard` next to the form, inside Content.
|
|
274
293
|
- Clearing the record together with `open` and rendering a skeleton for «no record» → not needed: the panel keeps its last content while it slides out.
|
|
275
|
-
- A Badge
|
|
294
|
+
- A status `Badge` in the header's `Drawer.Actions`, beside the buttons → put it in `Drawer.Description`, before the text: the kit sizes it to the line and keeps it first.
|
|
295
|
+
- A menu or a button as a custom child of the header text column → put it in `Drawer.Actions`.
|
|
296
|
+
- A Badge with its own `size` in `Drawer.Description` → leave `size` out: the line gives every header the same chip tier.
|
|
297
|
+
- An avatar wrapped in its own box, or given its own `size`, in `Drawer.Icon` → pass the `Avatar.Root` (or your component that renders it) as it is: the slot drops the tile and sets 40 px.
|
|
276
298
|
- Expecting Enter to submit via `Drawer.Confirm` → Drawer has no Confirm; use a `<form>` with a submit button.
|
|
277
299
|
- `side="top"` or `side="bottom"` → only `left` and `right`; a panel from the bottom edge is a [Sheet](../sheet/COMPONENT.md).
|
|
278
300
|
- Expecting a swipe to close a drawer → it has none; keep the header × and a footer button. A panel that swipes away is a [Sheet](../sheet/COMPONENT.md).
|
|
279
301
|
- Padding on a wrapper inside Body → Body already pads and scrolls.
|
|
280
302
|
|
|
281
303
|
## Related
|
|
282
|
-
- **Built from:** the dialog shared with Modal and Sheet (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Drawer.Body`),
|
|
304
|
+
- **Built from:** the dialog shared with Modal and Sheet (`internal/overlay/DialogParts`), [Button](../button/COMPONENT.md) (close button), [ScrollContainer](../scroll-container/COMPONENT.md) (`Drawer.Body`), `internal/mediaSlot` (a record's Avatar or Thumbnail in `Drawer.Icon`), [Skeleton](../skeleton/COMPONENT.md) (`Drawer.Title loading`, `Drawer.Description loading`), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (the `Drawer.Guard` discard confirm), `Icon` (the close glyph)
|
|
283
305
|
- **See also:** [Modal](../modal/COMPONENT.md) (the same header, body, footer and `Guard` in the centre), [Sheet](../sheet/COMPONENT.md) (the same parts raised from the bottom edge for a short task), [ConfirmDialog](../confirm-dialog/COMPONENT.md) (a destructive confirm), [Sidebar](../../layout/sidebar/COMPONENT.md), [Popover](../popover/COMPONENT.md), [Avatar](../avatar/COMPONENT.md) (a person's picture in `Drawer.Icon`)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** A profile panel: Sign out stands apart at the start of the footer, the main pair keeps the end; on a phone the column puts it first and the primary last — `Drawer.Footer`, `Drawer.Actions`. */
|
|
2
|
+
import { Avatar, Button, DescriptionList, Drawer, Icon } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
const PROFILE = {
|
|
5
|
+
name: "Игорь Лебедев",
|
|
6
|
+
role: "Менеджер по продажам",
|
|
7
|
+
email: "i.lebedev@sev-veter.ru",
|
|
8
|
+
team: "Продажи · Москва",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default function DrawerFooterActionsExample() {
|
|
12
|
+
return (
|
|
13
|
+
<Drawer.Root>
|
|
14
|
+
<Drawer.Trigger>
|
|
15
|
+
<Button.Root variant="soft" tone="neutral">
|
|
16
|
+
Открыть профиль
|
|
17
|
+
</Button.Root>
|
|
18
|
+
</Drawer.Trigger>
|
|
19
|
+
<Drawer.Content size="s">
|
|
20
|
+
<Drawer.Header>
|
|
21
|
+
<Drawer.Icon>
|
|
22
|
+
<Avatar.Root color="green">
|
|
23
|
+
<Avatar.Fallback>ИЛ</Avatar.Fallback>
|
|
24
|
+
</Avatar.Root>
|
|
25
|
+
</Drawer.Icon>
|
|
26
|
+
<Drawer.Title>{PROFILE.name}</Drawer.Title>
|
|
27
|
+
<Drawer.Description>{PROFILE.role}</Drawer.Description>
|
|
28
|
+
</Drawer.Header>
|
|
29
|
+
<Drawer.Body>
|
|
30
|
+
<DescriptionList.Root>
|
|
31
|
+
<DescriptionList.Item label="Почта">{PROFILE.email}</DescriptionList.Item>
|
|
32
|
+
<DescriptionList.Item label="Команда">{PROFILE.team}</DescriptionList.Item>
|
|
33
|
+
</DescriptionList.Root>
|
|
34
|
+
</Drawer.Body>
|
|
35
|
+
<Drawer.Footer>
|
|
36
|
+
<Drawer.Actions>
|
|
37
|
+
<Button.Root variant="ghost" tone="danger">
|
|
38
|
+
<Button.Icon>
|
|
39
|
+
<Icon name="action.logout" />
|
|
40
|
+
</Button.Icon>
|
|
41
|
+
Выйти
|
|
42
|
+
</Button.Root>
|
|
43
|
+
</Drawer.Actions>
|
|
44
|
+
<Drawer.Close>
|
|
45
|
+
<Button.Root variant="outline" tone="neutral">
|
|
46
|
+
Закрыть
|
|
47
|
+
</Button.Root>
|
|
48
|
+
</Drawer.Close>
|
|
49
|
+
<Button.Root>Изменить</Button.Root>
|
|
50
|
+
</Drawer.Footer>
|
|
51
|
+
</Drawer.Content>
|
|
52
|
+
</Drawer.Root>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -1,11 +1,22 @@
|
|
|
1
|
-
/**
|
|
2
|
-
import {
|
|
1
|
+
/** The one header of a record: the avatar, the name, its statuses leading the line under it, and only buttons at the end — `Drawer.Icon`, `Drawer.Description`, `Drawer.Actions`. */
|
|
2
|
+
import {
|
|
3
|
+
Avatar,
|
|
4
|
+
Badge,
|
|
5
|
+
Button,
|
|
6
|
+
DescriptionList,
|
|
7
|
+
Drawer,
|
|
8
|
+
Dropdown,
|
|
9
|
+
Icon,
|
|
10
|
+
Tooltip,
|
|
11
|
+
} from "prime-ui-kit";
|
|
3
12
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
13
|
+
const MEMBER = {
|
|
14
|
+
name: "Анна Соколова",
|
|
15
|
+
initials: "АС",
|
|
16
|
+
email: "a.sokolova@severnyveter.ru",
|
|
17
|
+
role: "Менеджер",
|
|
18
|
+
team: "Продажи",
|
|
19
|
+
since: "12 марта 2024",
|
|
9
20
|
};
|
|
10
21
|
|
|
11
22
|
export default function DrawerHeaderActionsExample() {
|
|
@@ -13,46 +24,56 @@ export default function DrawerHeaderActionsExample() {
|
|
|
13
24
|
<Drawer.Root>
|
|
14
25
|
<Drawer.Trigger>
|
|
15
26
|
<Button.Root variant="soft" tone="neutral">
|
|
16
|
-
Открыть
|
|
27
|
+
Открыть сотрудника
|
|
17
28
|
</Button.Root>
|
|
18
29
|
</Drawer.Trigger>
|
|
19
30
|
<Drawer.Content>
|
|
20
31
|
<Drawer.Header>
|
|
21
|
-
<Drawer.
|
|
22
|
-
|
|
32
|
+
<Drawer.Icon>
|
|
33
|
+
<Avatar.Root color="purple">
|
|
34
|
+
<Avatar.Fallback>{MEMBER.initials}</Avatar.Fallback>
|
|
35
|
+
</Avatar.Root>
|
|
36
|
+
</Drawer.Icon>
|
|
37
|
+
<Drawer.Title>{MEMBER.name}</Drawer.Title>
|
|
38
|
+
<Drawer.Description>
|
|
39
|
+
<Badge.Root color="blue">{MEMBER.role}</Badge.Root>
|
|
40
|
+
<Badge.Root tone="success">Активна</Badge.Root>
|
|
41
|
+
{MEMBER.email}
|
|
42
|
+
</Drawer.Description>
|
|
23
43
|
<Drawer.Actions>
|
|
24
|
-
<
|
|
44
|
+
<Tooltip.Root>
|
|
45
|
+
<Tooltip.Trigger>
|
|
46
|
+
<Button.Root variant="ghost" tone="neutral" aria-label="Скопировать ссылку">
|
|
47
|
+
<Button.Icon>
|
|
48
|
+
<Icon name="action.copy" />
|
|
49
|
+
</Button.Icon>
|
|
50
|
+
</Button.Root>
|
|
51
|
+
</Tooltip.Trigger>
|
|
52
|
+
<Tooltip.Content>Скопировать ссылку</Tooltip.Content>
|
|
53
|
+
</Tooltip.Root>
|
|
25
54
|
<Dropdown.Root>
|
|
26
55
|
<Dropdown.Trigger>
|
|
27
|
-
<Button.Root variant="ghost" tone="neutral" aria-label="Действия
|
|
56
|
+
<Button.Root variant="ghost" tone="neutral" aria-label="Действия с сотрудником">
|
|
28
57
|
<Button.Icon>
|
|
29
58
|
<Icon name="action.more" />
|
|
30
59
|
</Button.Icon>
|
|
31
60
|
</Button.Root>
|
|
32
61
|
</Dropdown.Trigger>
|
|
33
62
|
<Dropdown.Content>
|
|
34
|
-
<Dropdown.Item
|
|
35
|
-
<Dropdown.Item
|
|
63
|
+
<Dropdown.Item>Сменить роль</Dropdown.Item>
|
|
64
|
+
<Dropdown.Item>Сбросить пароль</Dropdown.Item>
|
|
36
65
|
<Dropdown.Separator />
|
|
37
|
-
<Dropdown.Item tone="danger"
|
|
66
|
+
<Dropdown.Item tone="danger">Отключить доступ</Dropdown.Item>
|
|
38
67
|
</Dropdown.Content>
|
|
39
68
|
</Dropdown.Root>
|
|
40
69
|
</Drawer.Actions>
|
|
41
70
|
</Drawer.Header>
|
|
42
71
|
<Drawer.Body>
|
|
43
72
|
<DescriptionList.Root>
|
|
44
|
-
<DescriptionList.Item label="
|
|
45
|
-
<DescriptionList.Item label="
|
|
73
|
+
<DescriptionList.Item label="Команда">{MEMBER.team}</DescriptionList.Item>
|
|
74
|
+
<DescriptionList.Item label="В компании с">{MEMBER.since}</DescriptionList.Item>
|
|
46
75
|
</DescriptionList.Root>
|
|
47
76
|
</Drawer.Body>
|
|
48
|
-
<Drawer.Footer>
|
|
49
|
-
<Drawer.Close>
|
|
50
|
-
<Button.Root variant="outline" tone="neutral">
|
|
51
|
-
Закрыть
|
|
52
|
-
</Button.Root>
|
|
53
|
-
</Drawer.Close>
|
|
54
|
-
<Button.Root>Выставить счёт</Button.Root>
|
|
55
|
-
</Drawer.Footer>
|
|
56
77
|
</Drawer.Content>
|
|
57
78
|
</Drawer.Root>
|
|
58
79
|
);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** A record opened by a link knows only its id: the title and the line under it hold their place in skeletons, the body keeps the data's geometry, then the name and the data fade in — `Drawer.Title`, `loading`. */
|
|
2
|
+
import { Button, Crossfade, DescriptionList, Drawer, Skeleton } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
const INVOICE = {
|
|
6
|
+
title: "Счёт №5521",
|
|
7
|
+
summary: "ООО «Северный ветер» · до 30 октября",
|
|
8
|
+
facts: [
|
|
9
|
+
["Сумма", "248 000 ₽"],
|
|
10
|
+
["Статус", "Ждёт оплаты"],
|
|
11
|
+
["Выставлен", "16 октября"],
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default function DrawerLinkLoadingExample() {
|
|
16
|
+
const [loading, setLoading] = React.useState(true);
|
|
17
|
+
|
|
18
|
+
const openChange = (open: boolean) => {
|
|
19
|
+
if (!open) return;
|
|
20
|
+
setLoading(true);
|
|
21
|
+
window.setTimeout(() => setLoading(false), 1200);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<Drawer.Root onOpenChange={openChange}>
|
|
26
|
+
<Drawer.Trigger>
|
|
27
|
+
<Button.Root variant="soft" tone="neutral">
|
|
28
|
+
Открыть по ссылке
|
|
29
|
+
</Button.Root>
|
|
30
|
+
</Drawer.Trigger>
|
|
31
|
+
<Drawer.Content>
|
|
32
|
+
<Drawer.Header>
|
|
33
|
+
<Drawer.Title loading={loading}>{INVOICE.title}</Drawer.Title>
|
|
34
|
+
<Drawer.Description loading={loading}>{INVOICE.summary}</Drawer.Description>
|
|
35
|
+
</Drawer.Header>
|
|
36
|
+
<Drawer.Body>
|
|
37
|
+
<Crossfade state={loading ? "loading" : "data"} aria-busy={loading}>
|
|
38
|
+
<DescriptionList.Root>
|
|
39
|
+
{INVOICE.facts.map(([term, value]) => (
|
|
40
|
+
<DescriptionList.Item key={term} label={term}>
|
|
41
|
+
{loading ? <Skeleton /> : value}
|
|
42
|
+
</DescriptionList.Item>
|
|
43
|
+
))}
|
|
44
|
+
</DescriptionList.Root>
|
|
45
|
+
</Crossfade>
|
|
46
|
+
</Drawer.Body>
|
|
47
|
+
<Drawer.Footer>
|
|
48
|
+
<Drawer.Close>
|
|
49
|
+
<Button.Root variant="outline" tone="neutral">
|
|
50
|
+
Закрыть
|
|
51
|
+
</Button.Root>
|
|
52
|
+
</Drawer.Close>
|
|
53
|
+
</Drawer.Footer>
|
|
54
|
+
</Drawer.Content>
|
|
55
|
+
</Drawer.Root>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -128,8 +128,8 @@ export default function DrawerScreenExample() {
|
|
|
128
128
|
return (
|
|
129
129
|
<SmartFilter.Root fields={[]} search={search} onSearchChange={setSearch}>
|
|
130
130
|
<Page.Root>
|
|
131
|
-
{/* A work screen titles itself in its top bar: the only h1, the counts under it
|
|
132
|
-
|
|
131
|
+
{/* A work screen titles itself in its top bar: the only h1, the counts under it and the one
|
|
132
|
+
primary action — the Drawer trigger. The table's search lives in the table's toolbar. */}
|
|
133
133
|
<Page.TopBar>
|
|
134
134
|
<Page.Heading>
|
|
135
135
|
<Page.Title>Клиенты</Page.Title>
|
|
@@ -138,9 +138,6 @@ export default function DrawerScreenExample() {
|
|
|
138
138
|
{plural(clients.length - people, COMPANY_WORDS)} и {people} ИП
|
|
139
139
|
</Page.Description>
|
|
140
140
|
</Page.Heading>
|
|
141
|
-
<Page.Tools aria-label="Клиенты">
|
|
142
|
-
<SmartFilter.Toolbar />
|
|
143
|
-
</Page.Tools>
|
|
144
141
|
<Page.Actions>
|
|
145
142
|
<Drawer.Root open={open} onOpenChange={changeOpen}>
|
|
146
143
|
<Drawer.Trigger>
|
|
@@ -266,6 +263,7 @@ export default function DrawerScreenExample() {
|
|
|
266
263
|
rows={shown}
|
|
267
264
|
getRowKey={(row) => row.id}
|
|
268
265
|
paging="none"
|
|
266
|
+
toolbar={<SmartFilter.Toolbar />}
|
|
269
267
|
empty="Клиентов не найдено"
|
|
270
268
|
/>
|
|
271
269
|
</Page.Body>
|
|
@@ -36,7 +36,7 @@ EmptyPage.Root centered column; size, layout, tone (role="alert" w
|
|
|
36
36
|
<!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
|
|
37
37
|
|
|
38
38
|
### EmptyPage.Root
|
|
39
|
-
`ref` → `HTMLDivElement`. A centered column: icon tile, title, description, actions; parts rise in one after another on
|
|
39
|
+
`ref` → `HTMLDivElement`. A centered column: icon tile, title, description, actions; parts rise in one after another when it appears on a page already shown (a search or a filter that came to nothing); one that mounts with its page stands still.
|
|
40
40
|
|
|
41
41
|
| Prop | Type | Default | Description |
|
|
42
42
|
|---|---|---|---|
|
|
@@ -116,7 +116,7 @@ EmptyPage.Root centered column; size, layout, tone (role="alert" w
|
|
|
116
116
|
## States
|
|
117
117
|
| State | Driven by | DOM |
|
|
118
118
|
|---|---|---|
|
|
119
|
-
|
|
|
119
|
+
| appearing | mounted on its own into a page already shown — a search or a filter that came to nothing (not `compact`) | parts rise in (`enterMotion`), `--prime-motion-stagger` apart; collapses under reduced motion. Mounted with its page, or inside a region that swaps in (`Crossfade`), it stands still |
|
|
120
120
|
| layout | `layout` | `data-layout="fill" \| "compact"` (none for `default`) |
|
|
121
121
|
| size | `size` | `data-size`; controls inside get the same tier |
|
|
122
122
|
| error | `tone="danger"` | `data-tone="danger"`, `role="alert"` (unless a `role` is passed), the icon tile danger |
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- A form part where a person adds, edits and removes several records of the same fields: invoice lines, contacts of a client, specs of a plan, recipients.
|
|
10
10
|
- A base value with overrides (a price and its changes by month): the base is the first row, pinned with `getLocked`, each override is a row.
|
|
11
11
|
- A list of short texts in the order a person sets (selling points, checklist items): one column, `reorderable`; each row ends with its remove button and its handle.
|
|
12
|
-
-
|
|
12
|
+
- Every row shows its actions in sight — the remove «×» and, with `reorderable`, the grip after it — however many columns it has (the default `rowActions="buttons"`). `rowActions="menu"` only for a `reorderable` list on a touch-first screen, where «Выше» / «Ниже» in one «⋯» replace a drag; never for a list without `reorderable`, whose menu would hold one item.
|
|
13
13
|
- Small attributes of a record beside its text (an icon, a color): `width="compact"` columns with an icon-only `Select` (`Select.TriggerIcon` and no `Select.Value`) and a `ColorPresets` swatch.
|
|
14
14
|
|
|
15
15
|
## When not to use
|
|
@@ -83,8 +83,8 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
83
83
|
### rowActions
|
|
84
84
|
| Value | Looks like | Use when | Default |
|
|
85
85
|
|---|---|---|---|
|
|
86
|
-
| `buttons` | a ghost «×» at the row's end and, with `reorderable`, the grip after it; a pinned row: «×» off, then the lock |
|
|
87
|
-
| `menu` | one ghost «⋯» per row; its Dropdown holds «Выше», «Ниже» (with `reorderable`), a separator and «Удалить» (danger) |
|
|
86
|
+
| `buttons` | a ghost «×» at the row's end and, with `reorderable`, the grip after it; a pinned row: «×» off, then the lock | every list: one action (remove) or remove and move, any number of columns | yes |
|
|
87
|
+
| `menu` | one ghost «⋯» per row; its Dropdown holds «Выше», «Ниже» (with `reorderable`), a separator and «Удалить» (danger) | a `reorderable` list on a touch-first screen, where moving by the menu replaces a drag; never without `reorderable` | |
|
|
88
88
|
|
|
89
89
|
### width (column)
|
|
90
90
|
| Value | Looks like | Use when | Default |
|
|
@@ -182,7 +182,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
182
182
|
| [variants.tsx](examples/variants.tsx) | The two looks of the same list: the rows on the host surface, or on a fill of their own with side padding — `variant`. |
|
|
183
183
|
| [sizes.tsx](examples/sizes.tsx) | Every tier: the headings, the fields, the handle and the buttons follow the list's size — `size`. |
|
|
184
184
|
| [reorder.tsx](examples/reorder.tsx) | Selling points of a plan in the order of the pricing page: a one-column list has no headings and no numbers, each row ends with its remove button and then its handle, a point moves by the handle or Alt+arrows — `reorderable`, `getLabel`. |
|
|
185
|
-
| [row-menu.tsx](examples/row-menu.tsx) | Payment stages of a project
|
|
185
|
+
| [row-menu.tsx](examples/row-menu.tsx) | Payment stages of a project on a touch-first screen, where a drag is awkward: one more-button per row holds move up, move down and remove, the moves are off at the ends and next to the pinned advance, and Alt+arrows on the button move the row too — `rowActions`, `reorderable`, `getLocked`. |
|
|
186
186
|
| [locked.tsx](examples/locked.tsx) | Specs of a plan on one line each: an icon picker and its color side by side in compact columns, the first three specs are pinned with a lock in the handle's place and a remove button that is off and says why, the rest reorder below them — `getLocked`, `width`, `reorderable`. |
|
|
187
187
|
| [base-row.tsx](examples/base-row.tsx) | A plan price and its changes by month in one table: the base price is the first, pinned row whose remove button is off and says why, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `labels`, `width`. |
|
|
188
188
|
| [limits.tsx](examples/limits.tsx) | Recipients of invoice reminders, from one to three: the last row cannot be removed and the add button says why it is off once the list is full — `min`, `max`, `hint`. |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Payment stages of a project
|
|
1
|
+
/** Payment stages of a project on a touch-first screen, where a drag is awkward: one more-button per row holds move up, move down and remove, the moves are off at the ends and next to the pinned advance, and Alt+arrows on the button move the row too — `rowActions`, `reorderable`, `getLocked`. */
|
|
2
2
|
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
@@ -84,7 +84,7 @@ The hint is always smaller than the field text; use the field's size.
|
|
|
84
84
|
| State | Driven by | DOM |
|
|
85
85
|
|---|---|---|
|
|
86
86
|
| status | `tone` | `data-tone` with the shared text tone (none for `neutral`, and none while `invalid` or `disabled`); the color eases between tones |
|
|
87
|
-
| error | `invalid` | `data-invalid="true"`; the message drops in over `fast` (fade + `--prime-space-1` from above, no shake, no layout shift) and the color eases from the hint color |
|
|
87
|
+
| error | `invalid` | `data-invalid="true"`; the message drops in over `fast` (fade + `--prime-space-1` from above, no shake, no layout shift) when it arrives on a field already shown — an error the page mounts with stands still and the color eases from the hint color |
|
|
88
88
|
| disabled | `disabled`, or a disabled `Fieldset` around it | `data-disabled="true"` |
|
|
89
89
|
| size | `size`, else the host tier | `data-size` |
|
|
90
90
|
|
|
@@ -64,12 +64,14 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
|
|
|
64
64
|
| `hintTone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of `hint` («Осталось 12 дней» in `warning`). The words carry the status, the color repeats it; an `error` replaces the hint and its tone. A validation error is `error`, never a `danger` hint. |
|
|
65
65
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
66
66
|
| `invalid` | `boolean` | `false` | Danger inset ring on the field, `aria-invalid` on the input. A non-empty `error` implies it. |
|
|
67
|
+
| `valid` | `boolean` | — | A confirmed value (a matching repeat, a verified code): a success ring and a drawn check at the field's end, `labels.valid` spoken. `invalid` wins. Pass `false` too: the live region exists only with it. |
|
|
67
68
|
| `focusRing` | `boolean` | `true` | `false` hides only the visual focus ring on `Input.Wrapper` (`data-focus-ring="false"`); focus, keyboard, ARIA and the invalid ring stay. |
|
|
68
69
|
| `counter` | `ReactNode` | — | Right side of the support row, usually `<Input.Counter current max />`. |
|
|
69
70
|
| `reserveSupportRow` | `boolean` | `false` | Always render the support row (min height = hint line height), so an appearing error does not shift the layout. |
|
|
70
71
|
| `id` | `string` | — | Id of the `<input>` (auto-generated when omitted); hint id is `<id>-hint`, error id is `<id>-error`. |
|
|
71
72
|
| `strength` | `boolean` | `false` | A password strength meter for new-password fields: a stepped `ProgressBar` under the field and the level word (weak … hard) in the support row. Follows the value of `Input.Field`. |
|
|
72
|
-
| `getStrength` | `(value: string) => 0 \| 1 \| 2 \| 3 \| 4` | `getPasswordStrength` | Replaces the kit's estimate of `strength`. Return 0 for empty, 1 weak … 4 hard.
|
|
73
|
+
| `getStrength` | `(value: string) => 0 \| 1 \| 2 \| 3 \| 4` | `getPasswordStrength` | Replaces the kit's estimate of `strength`. Return 0 for empty, 1 weak … 4 hard. |
|
|
74
|
+
| `minStrength` | `1 \| 2 \| 3 \| 4` | — | The policy of `strength`: below this level the meter turns danger and the form refuses to submit (`setCustomValidity`), then shows `labels.strengthTooWeak` as the error. |
|
|
73
75
|
| `onCommit` | `(value: string) => void \| Promise<void>` | — | Inline save: fires with a changed value on blur and Enter; Escape reverts. Return a Promise: the field shows a spinner and stays editable; a rejection restores the value — show why through `error`. |
|
|
74
76
|
| `suggestions` | `readonly string[]` | — | Suggestions for free text (a combobox): a list under the box shows those containing the text; a pick goes into the field as typing would. Any text stays allowed; a value from a list only is `Select`. |
|
|
75
77
|
| `closeOnEscape` | `boolean` | `true` | Escape closes the `suggestions` list; the text and the focus stay in the field. |
|
|
@@ -202,7 +204,7 @@ Input has no `variant` or `tone`: there is one field treatment (fill, no visible
|
|
|
202
204
|
- Pointless: `required` and `optional` together; `hint` and `error` together (the error replaces the hint); `invalid` without any message in a form where the user needs to know what is wrong.
|
|
203
205
|
- Forbidden: `focusRing={false}` on regular form fields (WCAG 2.4.7).
|
|
204
206
|
|
|
205
|
-
**Hierarchy:** keep one size for all fields of a form; mark required/optional consistently; a status of the value goes into the hint, not into the field.
|
|
207
|
+
**Hierarchy:** keep one size for all fields of a form; mark required/optional consistently; a status of the value goes into the hint, not into the field — except a confirmed value (`valid`: a repeated password that matches, a verified email or code), which is a check mark in the field, never a «совпадает» text.
|
|
206
208
|
|
|
207
209
|
## States
|
|
208
210
|
| State | Driven by | DOM |
|
|
@@ -211,6 +213,7 @@ Input has no `variant` or `tone`: there is one field treatment (fill, no visible
|
|
|
211
213
|
| focus | keyboard focus on the input | wrapper gets the focus fill and an inset focus ring (`:has(input:focus-visible)`) |
|
|
212
214
|
| no focus ring | `focusRing={false}` | `data-focus-ring="false"` on the wrapper |
|
|
213
215
|
| invalid | `invalid` or non-empty `error` | `data-invalid="true"` on root and wrapper, `aria-invalid="true"` on the input, danger inset ring, danger focus ring; the control shakes once when an error arrives (`data-shake` on the frame) |
|
|
216
|
+
| valid | `valid` (and not invalid: `invalid` or `error` wins) | `data-valid="true"` on the wrapper, a success inset ring; a check (`aria-hidden`) at the end of the field before the end icon and segments pops in and draws its stroke when the value becomes valid, and closes up in place when it goes; a polite live region says `labels.valid` and is in the input's `aria-describedby` while valid |
|
|
214
217
|
| disabled | native `disabled` on `Input.Field`, or a disabled `Fieldset` around it | disabled fill, disabled text and icons, `cursor: not-allowed`, clear button disabled look |
|
|
215
218
|
| read-only | native `readOnly` on `Input.Field` | no hover, default cursor |
|
|
216
219
|
| reserved support row | `reserveSupportRow` | `data-reserve="true"` on the support row |
|
|
@@ -218,6 +221,7 @@ Input has no `variant` or `tone`: there is one field treatment (fill, no visible
|
|
|
218
221
|
| suggesting | `suggestions` + typing, ↓ / ↑ or a click in the field | `role="combobox"`, `aria-expanded="true"` and `aria-controls` on the input; the list fades in under the box (the floating panel's motion) with the matching part of each suggestion marked; the highlight is a fill (`data-highlighted`, `aria-selected`) named by `aria-activedescendant`; closes on a pick, Escape, Tab, blur or a press outside |
|
|
219
222
|
| select segment | a `Select` inside `Input.Wrapper` | `data-field-segment` on the select root, `data-segment="true"` on its trigger: a tinted section (`fill-subtle`) flush with the start edge, a stronger fill on hover and while open, its own inset focus ring; the box's ring stays for the text |
|
|
220
223
|
| strength | `strength` + the value of `Input.Field` | a stepped `ProgressBar` (4 cells, `aria-label` = `labels.strength`) under the field fills one cell after another, weak · easy · medium · hard, toned danger → warning → accent → success; the level word at the end of the support row flows in letter by letter and is announced (`aria-live`); the support row is reserved |
|
|
224
|
+
| below the policy | `strength` + `minStrength` above the level of a non-empty value | the meter and the level word turn danger; `setCustomValidity(labels.strengthTooWeak)` on the input, so the form refuses to submit; after a refused submit the same text is the field's `error` (`aria-invalid`) until the level reaches `minStrength` |
|
|
221
225
|
| saving | `onCommit` returned a pending Promise | `aria-busy="true"` on the input; a `Spinner` at the end of the field (`data-commit="pending"`); the field stays editable and focusable, neighbours are untouched; a live region says `labels.saving` |
|
|
222
226
|
| saved | the Promise resolved (or `onCommit` returned nothing) | the spinner cross-fades into a success check (`data-commit="saved"`) that fades out after a moment; the live region says `labels.saved` |
|
|
223
227
|
| save refused | the Promise rejected | the committed value is put back (unless the person typed on), the status leaves; the owner shows why through `error` |
|
|
@@ -230,6 +234,8 @@ Inline save (`onCommit`): the value at focus is the committed one. Blur and Ente
|
|
|
230
234
|
|
|
231
235
|
Strength estimate (`strength`, replaced by `getStrength`): every trait adds a point on its own, in any order — lowercase, uppercase, digits, symbols, 10+ and 14+ characters; 0–2 points weak, 3 easy, 4 medium, 5+ hard, and under 8 characters at most easy. The meter follows `Input.Field` controlled or not.
|
|
232
236
|
|
|
237
|
+
Strength policy (`minStrength`): a non-empty value below the level is invalid for the form (native constraint validation, `labels.strengthTooWeak`), so a submit stops at the field and its error appears; an empty value is left to `required`. The meter tells the person typing; the server still decides what it accepts.
|
|
238
|
+
|
|
233
239
|
Suggestions (`suggestions`): the list shows the suggestions that contain the typed text (case-insensitive), and nothing when the text already is the only match. A pick writes the suggestion into the input the way typing does, so `onValueChange` (and `onCommit` on blur) receive it; the text is never restricted to the list.
|
|
234
240
|
|
|
235
241
|
Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontrolled: `defaultValue`. Both are native input props.
|
|
@@ -262,6 +268,8 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
262
268
|
|
|
263
269
|
- `label` renders a `<label htmlFor>` bound to the input. Without `label`, set `aria-label` on `Input.Field`. A placeholder never replaces the label.
|
|
264
270
|
- Hint and error are linked through `aria-describedby`; the error replaces the hint and sets `aria-invalid="true"`.
|
|
271
|
+
- `valid` adds the id of a polite live region holding `labels.valid` to the input's `aria-describedby`: the state is heard when it arrives and on focus, never by colour alone; the check is `aria-hidden`.
|
|
272
|
+
- `minStrength` uses native constraint validation: below the level the input's `validationMessage` is `labels.strengthTooWeak`, the form refuses to submit, and the message becomes the field's error (`aria-invalid`).
|
|
265
273
|
- `Input.Icon` is decorative (`aria-hidden`). `Input.Affix` and `Input.InlineAffix` are `aria-hidden` in the reading order but describe the input (`aria-describedby`, before the hint): the unit is heard with the field, so the label never repeats it. An `aria-label` on the affix is said instead of its text («рублей» for `₽`).
|
|
266
274
|
- `suggestions` make the input a `combobox` (`aria-autocomplete="list"`, `aria-expanded`, `aria-controls` on the open `listbox`, which the label names); the highlight is `aria-activedescendant`, so focus never leaves the field.
|
|
267
275
|
- A `Select` inside `Input.Wrapper` is the box's start segment: its trigger is named by the select root's `aria-label` and the picked value («Иконка: Звезда»).
|
|
@@ -290,6 +298,8 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
290
298
|
| `strengthEasy` | `"Лёгкий"` | Level word for strength 2. |
|
|
291
299
|
| `strengthMedium` | `"Средний"` | Level word for strength 3. |
|
|
292
300
|
| `strengthHard` | `"Сложный"` | Level word for strength 4. |
|
|
301
|
+
| `strengthTooWeak` | `"Пароль недостаточно надёжный"` | Validation message of a `strength` field below `minStrength`, and its error once a submit was refused. |
|
|
302
|
+
| `valid` | `"Значение подходит"` | Spoken when the field is `valid`; the check mark itself is `aria-hidden`. |
|
|
293
303
|
|
|
294
304
|
## Examples
|
|
295
305
|
| Example | Shows |
|
|
@@ -302,6 +312,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
302
312
|
| [affixes.tsx](examples/affixes.tsx) | A fixed prefix and suffix flush with the edges and a unit next to the value, heard as a word by a screen reader — `Input.Affix`, `Input.InlineAffix`, `aria-label`. |
|
|
303
313
|
| [without-focus-ring.tsx](examples/without-focus-ring.tsx) | A single search field where the caret and the lighter fill show focus — `focusRing`. |
|
|
304
314
|
| [password-strength.tsx](examples/password-strength.tsx) | A new password with a show toggle and a meter that fills step by step and names the level beside the hint, and a service password measured by the company's own rule — `Input.RevealButton`, `strength`, `getStrength`. |
|
|
315
|
+
| [password-policy.tsx](examples/password-policy.tsx) | A password change under a policy: a new password below the required level turns the meter danger and stops the submit, the repeat gets a check once it matches — `strength`, `minStrength`, `valid`. |
|
|
305
316
|
| [edge-actions.tsx](examples/edge-actions.tsx) | Actions at the field's end: a mail link with a tooltip beside copying the value, and a profile link on a read-only field — `Input.Action`, `Input.CopyButton`. |
|
|
306
317
|
| [inline-edit.tsx](examples/inline-edit.tsx) | Record card fields that save on blur and Enter, revert on Escape and show the server's refusal — `onCommit`, `error`. |
|
|
307
318
|
| [suggestions.tsx](examples/suggestions.tsx) | Where a returned order is kept: free text with the places already used as suggestions — `suggestions`. |
|
|
@@ -315,6 +326,8 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
315
326
|
- `<Input.Field placeholder="Email" />` without a label → add `label` on `Input.Root` or `aria-label` on the field.
|
|
316
327
|
- `<Input.Root disabled>` → `disabled` goes on `Input.Field`.
|
|
317
328
|
- `<Input.Root fullWidth>` → there is no such prop; the root already fills its parent.
|
|
329
|
+
- A success icon in `Input.Icon side="end"` or a «Пароли совпадают» hint for a matching value → `valid` on `Input.Root` (ring, check, spoken state).
|
|
330
|
+
- Checking `strength` levels yourself and setting `error` → `minStrength`.
|
|
318
331
|
- Passing both `hint` and `error` expecting both to show → only the error is shown.
|
|
319
332
|
- Rendering `Input.ClearButton` for an empty field → render it only when there is a value.
|
|
320
333
|
- Setting `className` with a custom border or background on `Input.Wrapper` → the fill comes from the surface context; fields inside Card/Modal already switch.
|
|
@@ -327,5 +340,5 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
|
|
|
327
340
|
- Returning `false` from `onCommit` to refuse → reject the Promise (throw) and set `error`.
|
|
328
341
|
|
|
329
342
|
## Related
|
|
330
|
-
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
|
|
343
|
+
- **Built from:** [Label](../label/COMPONENT.md) (`label`), [Hint](../hint/COMPONENT.md) (`hint`, `error`), [Tooltip](../tooltip/COMPONENT.md) (`Input.Action`, `Input.CopyButton`), [Spinner](../spinner/COMPONENT.md) (`onCommit`), [ProgressBar](../progress-bar/COMPONENT.md) (`strength`), `Icon` (segments, the `valid` check), [VisuallyHidden](../visually-hidden/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md) (the `suggestions` list)
|
|
331
344
|
- **See also:** [Textarea](../textarea/COMPONENT.md), [Select](../select/COMPONENT.md), [Datepicker](../datepicker/COMPONENT.md), [TagSelect](../tag-select/COMPONENT.md) — fields with the same contract
|