prime-ui-kit 0.17.0 → 0.18.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 +2 -1
- package/dist/chunks/{chunk-PRP4B6OG.js → chunk-IVS3J7AS.js} +1 -1
- package/dist/chunks/{chunk-A4WE3UNC.js → chunk-MB4CXSLL.js} +18473 -17911
- package/dist/chunks/{chunk-7WWZYGRC.js → chunk-OAXJWF3L.js} +4477 -3882
- package/dist/chunks/{chunk-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
- package/dist/chunks/{chunk-SVFWPLNW.js → chunk-X3JT5CLR.js} +14 -3
- package/dist/color-picker.js +3 -3
- package/dist/components/badge/Badge.d.ts +17 -0
- package/dist/components/breadcrumb/Breadcrumb.d.ts +11 -2
- package/dist/components/button/Button.d.ts +1 -1
- package/dist/components/button-group/ButtonGroup.d.ts +1 -1
- package/dist/components/card/Card.d.ts +8 -4
- package/dist/components/chat/Chat.d.ts +3 -3
- package/dist/components/chat/MessageList.d.ts +5 -1
- package/dist/components/chat/groupMessages.d.ts +12 -4
- package/dist/components/chat-list/ChatList.d.ts +18 -9
- package/dist/components/chat-list/formatChatTime.d.ts +3 -3
- package/dist/components/checkbox/Checkbox.d.ts +6 -1
- package/dist/components/color-field/ColorField.d.ts +1 -1
- package/dist/components/color-picker/ColorPicker.d.ts +6 -1
- package/dist/components/color-picker/ColorPresets.d.ts +9 -1
- package/dist/components/crossfade/Crossfade.d.ts +1 -1
- package/dist/components/data-table/DataTable.d.ts +2 -0
- package/dist/components/data-table/Head.d.ts +3 -1
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/digit-input/DigitInput.d.ts +1 -1
- package/dist/components/drawer/Drawer.d.ts +3 -2
- package/dist/components/field-list/FieldList.d.ts +18 -6
- package/dist/components/hint/Hint.d.ts +8 -2
- package/dist/components/index.d.ts +6 -3
- package/dist/components/input/Input.d.ts +27 -5
- package/dist/components/input/inputSuggestions.d.ts +39 -0
- package/dist/components/kanban/Kanban.d.ts +4 -9
- package/dist/components/list/List.d.ts +19 -5
- package/dist/components/message/Message.d.ts +15 -3
- package/dist/components/message/MessageSticker.d.ts +27 -0
- package/dist/components/message/formatMessageTime.d.ts +5 -2
- package/dist/components/modal/Modal.d.ts +3 -2
- package/dist/components/native-select/NativeSelect.d.ts +1 -1
- package/dist/components/radio/Radio.d.ts +11 -2
- package/dist/components/rich-text/RichText.d.ts +23 -0
- package/dist/components/scroll-container/ScrollContainer.d.ts +1 -1
- package/dist/components/scroll-container/scrollGlide.d.ts +26 -0
- package/dist/components/scroll-container/useScrollMotion.d.ts +25 -0
- package/dist/components/segmented-control/SegmentedControl.d.ts +1 -1
- package/dist/components/select/selectContext.d.ts +7 -0
- package/dist/components/slider/Slider.d.ts +1 -1
- package/dist/components/switch/Switch.d.ts +6 -1
- package/dist/components/tag-select/TagSelect.d.ts +1 -1
- package/dist/components/textarea/Textarea.d.ts +6 -1
- package/dist/components/timeline/Timeline.d.ts +1 -1
- package/dist/icon-set.js +2 -2
- package/dist/index.css +1 -1
- package/dist/index.js +5 -3
- package/dist/internal/ChoiceField.d.ts +3 -1
- package/dist/internal/FieldFrame.d.ts +8 -2
- package/dist/internal/fieldCell.d.ts +1 -1
- package/dist/internal/formatClock.d.ts +23 -2
- package/dist/internal/leaveGuard.d.ts +21 -0
- package/dist/internal/messageList.d.ts +2 -0
- package/dist/internal/overlay/DialogParts.d.ts +27 -2
- package/dist/internal/swatch.d.ts +5 -0
- package/dist/internal/tokenDefaults.d.ts +1 -0
- package/dist/layout/index.d.ts +1 -1
- package/dist/layout/page/Page.d.ts +47 -8
- package/dist/skill/anti-slop.md +5 -0
- package/dist/skill/api-contract.md +14 -7
- package/dist/skill/cheatsheet.md +22 -7
- package/dist/skill/checklist.md +5 -0
- package/dist/skill/choosing.md +25 -12
- package/dist/skill/components.md +1 -0
- package/dist/skill/composition.md +19 -5
- package/dist/skill/layouts.md +5 -3
- package/dist/skill/reference/components/badge/COMPONENT.md +14 -1
- package/dist/skill/reference/components/badge/examples/examples.module.css +7 -0
- package/dist/skill/reference/components/badge/examples/tag-row.tsx +34 -0
- package/dist/skill/reference/components/breadcrumb/COMPONENT.md +9 -4
- package/dist/skill/reference/components/breadcrumb/examples/router.tsx +17 -0
- package/dist/skill/reference/components/button/COMPONENT.md +1 -1
- package/dist/skill/reference/components/button-group/COMPONENT.md +1 -0
- package/dist/skill/reference/components/card/COMPONENT.md +7 -1
- package/dist/skill/reference/components/card/examples/states.tsx +59 -0
- package/dist/skill/reference/components/chat/COMPONENT.md +4 -1
- package/dist/skill/reference/components/chat/examples/time-zone.tsx +83 -0
- package/dist/skill/reference/components/chat-list/COMPONENT.md +12 -6
- package/dist/skill/reference/components/chat-list/examples/links.tsx +1 -1
- package/dist/skill/reference/components/chat-list/examples/router.tsx +63 -0
- package/dist/skill/reference/components/checkbox/COMPONENT.md +1 -0
- package/dist/skill/reference/components/color-field/COMPONENT.md +2 -1
- package/dist/skill/reference/components/color-picker/COMPONENT.md +14 -5
- package/dist/skill/reference/components/color-picker/examples/presets-custom.tsx +31 -0
- package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
- package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +7 -2
- package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
- package/dist/skill/reference/components/datepicker/COMPONENT.md +3 -1
- package/dist/skill/reference/components/datepicker/examples/deadline.tsx +33 -0
- package/dist/skill/reference/components/digit-input/COMPONENT.md +1 -0
- package/dist/skill/reference/components/drawer/COMPONENT.md +15 -2
- package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
- package/dist/skill/reference/components/field-list/COMPONENT.md +15 -3
- package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
- package/dist/skill/reference/components/field-list/examples/totals.tsx +82 -0
- package/dist/skill/reference/components/file-upload/COMPONENT.md +1 -0
- package/dist/skill/reference/components/hint/COMPONENT.md +16 -2
- package/dist/skill/reference/components/hint/examples/variants.tsx +48 -0
- package/dist/skill/reference/components/input/COMPONENT.md +39 -9
- package/dist/skill/reference/components/input/examples/affixes.tsx +4 -2
- package/dist/skill/reference/components/input/examples/dismiss.tsx +29 -0
- package/dist/skill/reference/components/input/examples/suggestions.tsx +26 -0
- package/dist/skill/reference/components/input/examples/time.tsx +24 -0
- package/dist/skill/reference/components/input/examples/with-select.tsx +44 -0
- package/dist/skill/reference/components/kanban/COMPONENT.md +6 -6
- package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
- package/dist/skill/reference/components/list/COMPONENT.md +23 -7
- package/dist/skill/reference/components/list/examples/row-controls.tsx +80 -0
- package/dist/skill/reference/components/list/examples/with-icon.tsx +23 -2
- package/dist/skill/reference/components/login-form/COMPONENT.md +8 -7
- package/dist/skill/reference/components/login-form/examples/states.tsx +12 -14
- package/dist/skill/reference/components/message/COMPONENT.md +30 -9
- package/dist/skill/reference/components/message/examples/preview.tsx +28 -0
- package/dist/skill/reference/components/message/examples/sticker.tsx +35 -0
- package/dist/skill/reference/components/modal/COMPONENT.md +15 -3
- package/dist/skill/reference/components/modal/examples/long-content.tsx +37 -18
- package/dist/skill/reference/components/native-select/COMPONENT.md +1 -0
- package/dist/skill/reference/components/notification/COMPONENT.md +1 -1
- package/dist/skill/reference/components/radio/COMPONENT.md +2 -0
- package/dist/skill/reference/components/rich-text/COMPONENT.md +93 -0
- package/dist/skill/reference/components/rich-text/examples/overview.tsx +19 -0
- package/dist/skill/reference/components/scroll-container/COMPONENT.md +8 -5
- package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
- package/dist/skill/reference/components/segmented-control/examples/in-form.tsx +1 -1
- package/dist/skill/reference/components/select/COMPONENT.md +7 -3
- package/dist/skill/reference/components/slider/COMPONENT.md +1 -0
- package/dist/skill/reference/components/slider/examples/controlled.tsx +1 -1
- package/dist/skill/reference/components/switch/COMPONENT.md +1 -0
- package/dist/skill/reference/components/tag-select/COMPONENT.md +1 -0
- package/dist/skill/reference/components/textarea/COMPONENT.md +1 -0
- package/dist/skill/reference/components/timeline/COMPONENT.md +3 -3
- package/dist/skill/reference/components/timeline/examples/structure.tsx +2 -1
- package/dist/skill/reference/layout/app-shell/COMPONENT.md +2 -2
- package/dist/skill/reference/layout/bottom-nav/COMPONENT.md +1 -0
- package/dist/skill/reference/layout/page/COMPONENT.md +37 -8
- package/dist/skill/reference/layout/page/examples/examples.module.css +7 -0
- package/dist/skill/reference/layout/page/examples/guard.tsx +87 -0
- package/dist/skill/reference/layout/page/examples/loading.tsx +57 -0
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
- package/dist/skill/responsive.md +12 -7
- package/dist/skill/ux.md +5 -3
- package/dist/tokens/semantic.d.ts +2 -0
- package/package.json +2 -2
- package/src/components/badge/COMPONENT.md +14 -1
- package/src/components/badge/examples/examples.module.css +7 -0
- package/src/components/badge/examples/tag-row.tsx +34 -0
- package/src/components/breadcrumb/COMPONENT.md +9 -4
- package/src/components/breadcrumb/examples/router.tsx +17 -0
- package/src/components/button/COMPONENT.md +1 -1
- package/src/components/button-group/COMPONENT.md +1 -0
- package/src/components/card/COMPONENT.md +7 -1
- package/src/components/card/examples/states.tsx +59 -0
- package/src/components/chat/COMPONENT.md +4 -1
- package/src/components/chat/examples/time-zone.tsx +83 -0
- package/src/components/chat-list/COMPONENT.md +12 -6
- package/src/components/chat-list/examples/links.tsx +1 -1
- package/src/components/chat-list/examples/router.tsx +63 -0
- package/src/components/checkbox/COMPONENT.md +1 -0
- package/src/components/color-field/COMPONENT.md +2 -1
- package/src/components/color-picker/COMPONENT.md +14 -5
- package/src/components/color-picker/examples/presets-custom.tsx +31 -0
- package/src/components/color-swatches/COMPONENT.md +1 -0
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +7 -2
- package/src/components/data-table/examples/numeric.tsx +9 -2
- package/src/components/datepicker/COMPONENT.md +3 -1
- package/src/components/datepicker/examples/deadline.tsx +33 -0
- package/src/components/digit-input/COMPONENT.md +1 -0
- package/src/components/drawer/COMPONENT.md +15 -2
- package/src/components/drawer/examples/client-card.tsx +32 -8
- package/src/components/field-list/COMPONENT.md +15 -3
- package/src/components/field-list/examples/saving.tsx +83 -0
- package/src/components/field-list/examples/totals.tsx +82 -0
- package/src/components/file-upload/COMPONENT.md +1 -0
- package/src/components/hint/COMPONENT.md +16 -2
- package/src/components/hint/examples/variants.tsx +48 -0
- package/src/components/input/COMPONENT.md +39 -9
- package/src/components/input/examples/affixes.tsx +4 -2
- package/src/components/input/examples/dismiss.tsx +29 -0
- package/src/components/input/examples/suggestions.tsx +26 -0
- package/src/components/input/examples/time.tsx +24 -0
- package/src/components/input/examples/with-select.tsx +44 -0
- package/src/components/kanban/COMPONENT.md +6 -6
- package/src/components/kanban/examples/columns.tsx +4 -2
- package/src/components/list/COMPONENT.md +23 -7
- package/src/components/list/examples/row-controls.tsx +80 -0
- package/src/components/list/examples/with-icon.tsx +23 -2
- package/src/components/login-form/COMPONENT.md +8 -7
- package/src/components/login-form/examples/states.tsx +12 -14
- package/src/components/message/COMPONENT.md +30 -9
- package/src/components/message/examples/preview.tsx +28 -0
- package/src/components/message/examples/sticker.tsx +35 -0
- package/src/components/modal/COMPONENT.md +15 -3
- package/src/components/modal/examples/long-content.tsx +37 -18
- package/src/components/native-select/COMPONENT.md +1 -0
- package/src/components/notification/COMPONENT.md +1 -1
- package/src/components/radio/COMPONENT.md +2 -0
- package/src/components/rich-text/COMPONENT.md +93 -0
- package/src/components/rich-text/examples/overview.tsx +19 -0
- package/src/components/scroll-container/COMPONENT.md +8 -5
- package/src/components/segmented-control/COMPONENT.md +1 -0
- package/src/components/segmented-control/examples/in-form.tsx +1 -1
- package/src/components/select/COMPONENT.md +7 -3
- package/src/components/slider/COMPONENT.md +1 -0
- package/src/components/slider/examples/controlled.tsx +1 -1
- package/src/components/switch/COMPONENT.md +1 -0
- package/src/components/tag-select/COMPONENT.md +1 -0
- package/src/components/textarea/COMPONENT.md +1 -0
- package/src/components/timeline/COMPONENT.md +3 -3
- package/src/components/timeline/examples/structure.tsx +2 -1
- package/src/layout/app-shell/COMPONENT.md +2 -2
- package/src/layout/bottom-nav/COMPONENT.md +1 -0
- package/src/layout/page/COMPONENT.md +37 -8
- package/src/layout/page/examples/examples.module.css +7 -0
- package/src/layout/page/examples/guard.tsx +87 -0
- package/src/layout/page/examples/loading.tsx +57 -0
- package/src/layout/sidebar/COMPONENT.md +1 -0
- package/src/styles/theme-light.css +1 -0
- package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
|
@@ -35,6 +35,7 @@ Drawer.Root state and dismiss policy (no DOM)
|
|
|
35
35
|
│ ├─ Drawer.Description <p>
|
|
36
36
|
│ └─ Drawer.Actions row of tier-s controls before the close button
|
|
37
37
|
├─ Drawer.Body the only scrolling zone (ScrollContainer)
|
|
38
|
+
│ └─ Drawer.Section <section>: <h3> label, description, Drawer.Actions at the heading's end
|
|
38
39
|
└─ Drawer.Footer always: Close or Cancel + the main action, primary last
|
|
39
40
|
├─ Drawer.Meta <p>, muted status at the start
|
|
40
41
|
└─ Drawer.Close clones its child; closes on click
|
|
@@ -80,7 +81,7 @@ No DOM, no ref. State and dismiss policy.
|
|
|
80
81
|
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `className` and the other attributes of the `<header>`. |
|
|
81
82
|
|
|
82
83
|
### Drawer.Actions
|
|
83
|
-
`ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown, an icon button. Controls inside take tier `s`.
|
|
84
|
+
`ref` → `HTMLDivElement`. A row of controls at the end of the header, before the close button: a status Badge, a ⋯ Dropdown, an icon button; or at the end of a `Drawer.Section` heading. Controls inside take tier `s`.
|
|
84
85
|
|
|
85
86
|
| Prop | Type | Default | Description |
|
|
86
87
|
|---|---|---|---|
|
|
@@ -109,6 +110,15 @@ No DOM, no ref. State and dismiss policy.
|
|
|
109
110
|
|---|---|---|---|
|
|
110
111
|
| `…rest` | `HTMLAttributes<HTMLDivElement>` | — | `className`, `children` and the other attributes of the zone. |
|
|
111
112
|
|
|
113
|
+
### Drawer.Section
|
|
114
|
+
`ref` → `HTMLElement`. A titled `<section>` of the body: an `<h3>` (title-s, under the `<h2>` title), an optional description, a `Drawer.Actions` at the end of the heading row; the content 16 below, sections 32 apart. Fields group in a `Fieldset`.
|
|
115
|
+
|
|
116
|
+
| Prop | Type | Default | Description |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| `label` | `ReactNode` | — (required) | The heading of the section; it names the `<section>`. |
|
|
119
|
+
| `description` | `ReactNode` | — | One line of context under the heading (body-s). |
|
|
120
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLElement>, "title">` | — | `children` (the content and an optional `Drawer.Actions`), `className` and the other attributes of the `<section>`. |
|
|
121
|
+
|
|
112
122
|
### Drawer.Footer
|
|
113
123
|
`ref` → `HTMLElement`. Renders `<footer>` with the actions, primary last, and an optional `Meta` at the start.
|
|
114
124
|
|
|
@@ -189,6 +199,8 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
189
199
|
- Full viewport height; only the body scrolls, with its own side padding.
|
|
190
200
|
- Tabs in the header: a `Tabs.List` inside `Drawer.Header` (with `Tabs.Root` around the header and the body) spans the header's last row. The header becomes the tab strip, edge to edge and without the hairline; the active tab is the body rising into it, the panels sit in the body.
|
|
191
201
|
- Fields inside get the surface field fill.
|
|
202
|
+
- 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. A group of fields is a `Fieldset`; a page section is `Page.Section`.
|
|
203
|
+
- With a `Drawer.Meta` the buttons stay one row: when the footer runs short the status goes above them first.
|
|
192
204
|
|
|
193
205
|
## Accessibility
|
|
194
206
|
|
|
@@ -229,7 +241,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
229
241
|
| [sizes.tsx](examples/sizes.tsx) | Every panel width, 360 to 800 px; below 640 px of viewport the panel takes the full width — `size`. |
|
|
230
242
|
| [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`. |
|
|
231
243
|
| [header-actions.tsx](examples/header-actions.tsx) | A record card: its status and a ⋯ menu sit in the header before the close button, one tier smaller — `Drawer.Actions`. |
|
|
232
|
-
| [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 — `Drawer.Icon`, `Avatar.Root`. |
|
|
244
|
+
| [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`. |
|
|
233
245
|
| [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`. |
|
|
234
246
|
| [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`. |
|
|
235
247
|
| [dismiss.tsx](examples/dismiss.tsx) | An import closes only from its buttons, and not at all while it runs — `closeOnOutsideClick`, `closeOnEscape`. |
|
|
@@ -241,6 +253,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
|
|
|
241
253
|
- A drawer without `Drawer.Footer` → add one: «Закрыть» for details, «Отмена» + the submit button for a form. The header × alone is not the end of the panel.
|
|
242
254
|
- Save or Cancel buttons at the end of the body, or in the header → they go in `Drawer.Footer`, which stays in place while the body scrolls.
|
|
243
255
|
- A delete confirmation in a Drawer → use ConfirmDialog.
|
|
256
|
+
- A hand-made heading row (`Typography as="h3"` + buttons) over a group in the body → `Drawer.Section label` with `Drawer.Actions`.
|
|
244
257
|
- Busy or unsaved state lifted out of the form into Root props → put `Drawer.Guard` next to the form, inside Content.
|
|
245
258
|
- 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.
|
|
246
259
|
- A Badge or a menu as a custom child of the header text column → put them in `Drawer.Actions`.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** A client card from the list: the contact's avatar heads the panel in place of an icon tile, then the details — `Drawer.Icon`, `Avatar.Root`. */
|
|
2
|
-
import { Avatar, Button, DescriptionList, Drawer } from "prime-ui-kit";
|
|
1
|
+
/** 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`. */
|
|
2
|
+
import { Avatar, Button, DescriptionList, Drawer, Icon } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
const CLIENT = {
|
|
5
5
|
name: "Анна Соколова",
|
|
@@ -10,6 +10,11 @@ const CLIENT = {
|
|
|
10
10
|
manager: "Игорь Лебедев",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
const DEALS = [
|
|
14
|
+
{ id: "D-2041", title: "Поставка серверов", amount: "1 240 000 ₽" },
|
|
15
|
+
{ id: "D-2057", title: "Сервисный контракт на год", amount: "380 000 ₽" },
|
|
16
|
+
];
|
|
17
|
+
|
|
13
18
|
export default function DrawerClientCardExample() {
|
|
14
19
|
return (
|
|
15
20
|
<Drawer.Root>
|
|
@@ -29,12 +34,31 @@ export default function DrawerClientCardExample() {
|
|
|
29
34
|
<Drawer.Description>{CLIENT.company}</Drawer.Description>
|
|
30
35
|
</Drawer.Header>
|
|
31
36
|
<Drawer.Body>
|
|
32
|
-
<
|
|
33
|
-
<DescriptionList.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
<Drawer.Section label="Контакт">
|
|
38
|
+
<DescriptionList.Root>
|
|
39
|
+
<DescriptionList.Item label="Должность">{CLIENT.role}</DescriptionList.Item>
|
|
40
|
+
<DescriptionList.Item label="Телефон">{CLIENT.phone}</DescriptionList.Item>
|
|
41
|
+
<DescriptionList.Item label="Почта">{CLIENT.email}</DescriptionList.Item>
|
|
42
|
+
<DescriptionList.Item label="Менеджер">{CLIENT.manager}</DescriptionList.Item>
|
|
43
|
+
</DescriptionList.Root>
|
|
44
|
+
</Drawer.Section>
|
|
45
|
+
<Drawer.Section label="Открытые сделки" description="2 сделки на 1 620 000 ₽">
|
|
46
|
+
<Drawer.Actions>
|
|
47
|
+
<Button.Root variant="ghost" tone="neutral">
|
|
48
|
+
<Button.Icon>
|
|
49
|
+
<Icon name="action.add" />
|
|
50
|
+
</Button.Icon>
|
|
51
|
+
Сделка
|
|
52
|
+
</Button.Root>
|
|
53
|
+
</Drawer.Actions>
|
|
54
|
+
<DescriptionList.Root>
|
|
55
|
+
{DEALS.map((deal) => (
|
|
56
|
+
<DescriptionList.Item key={deal.id} label={deal.title}>
|
|
57
|
+
{deal.amount}
|
|
58
|
+
</DescriptionList.Item>
|
|
59
|
+
))}
|
|
60
|
+
</DescriptionList.Root>
|
|
61
|
+
</Drawer.Section>
|
|
38
62
|
</Drawer.Body>
|
|
39
63
|
<Drawer.Footer>
|
|
40
64
|
<Drawer.Close>
|
|
@@ -30,6 +30,7 @@ FieldList field frame: label · the list · hint / error
|
|
|
30
30
|
├─ <ol> the rows (Dnd.Sortable with `reorderable`)
|
|
31
31
|
│ └─ <li> row [lead: handle | lock] [a cell per column] [end: remove]
|
|
32
32
|
│ └─ cell one kit field without `label`, named by its column heading
|
|
33
|
+
├─ totals row `renderTotals`: a group «Итого», a total per column on the same tracks
|
|
33
34
|
└─ add row Button «addLabel» (+ `labels.maxReached` at `max`)
|
|
34
35
|
```
|
|
35
36
|
A leaf: rows come from `value` through `renderItem`, which returns one element per column (a fragment). Every framed kit field (Input, Select, Datepicker, NativeSelect, ColorField…) in a cell takes its column heading as its label: read by screen readers, not shown while the heading row is on screen, shown above the field once the rows stack in a narrow container. `ColorPresets` takes it as the name of its trigger.
|
|
@@ -43,12 +44,13 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
43
44
|
|
|
44
45
|
| Prop | Type | Default | Description |
|
|
45
46
|
|---|---|---|---|
|
|
46
|
-
| `columns` | `readonly FieldListColumn[]` | — (required) | The columns in cell order: `label` (the heading; it names the field of the column in every row), `width` (`fill` \| `compact` \| `s` \| `m`), `required` (a `*` after the heading). |
|
|
47
|
+
| `columns` | `readonly FieldListColumn[]` | — (required) | The columns in cell order: `label` (the heading; it names the field of the column in every row), `width` (`fill` \| `compact` \| `xs` \| `s` \| `m`), `required` (a `*` after the heading). |
|
|
47
48
|
| `renderItem` | `(item: T, row: FieldListRow<T>) => ReactNode` | — (required) | The cells of a row, one element per column (a fragment): kit fields without `label`. `row` has `index`, `update(patch)` and `remove()`. |
|
|
49
|
+
| `renderTotals` | `(items: readonly T[]) => ReactNode` | — | The totals row under the rows: one node per column (a fragment), `null` where a column has none. The first cell without a total reads `labels.totals`, the row's group name. |
|
|
48
50
|
| `getId` | `(item: T) => string` | — (required) | Stable unique id of an item: the row key and the drag id. |
|
|
49
51
|
| `value` | `readonly T[]` | — | The rows (controlled). |
|
|
50
52
|
| `defaultValue` | `readonly T[]` | `[]` | The first rows (uncontrolled). |
|
|
51
|
-
| `onValueChange` | `(value: T[]) => void
|
|
53
|
+
| `onValueChange` | `(value: T[]) => void \| Promise<void>` | — | The new list after an edit (`row.update`), an add, a remove or a move. Return the save's promise: the new list shows while it is pending, the owner's again on a reject (say why with `error`). |
|
|
52
54
|
| `createItem` | `() => T` | — | A new empty item; with it the add button shows under the rows and focus goes to the new row's first field. |
|
|
53
55
|
| `addLabel` | `string` | — | Text of the add button: what is added («Добавить позицию»). |
|
|
54
56
|
| `getLabel` | `(item: T) => string` | — | The row's own text (its title field) for the remove button, the handle and drag announcements; else the row number. |
|
|
@@ -59,6 +61,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
59
61
|
| `label` | `ReactNode` | — | Heading of the list; names the `<ol>`. Omit it inside a `Fieldset` whose legend names the list. |
|
|
60
62
|
| `aria-label` | `string` | — | Name of the `<ol>` when no visible `label` names it (a section heading above the list). |
|
|
61
63
|
| `hint` | `ReactNode` | — | Help text under the list; hidden while `error` shows. |
|
|
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. |
|
|
62
65
|
| `error` | `ReactNode` | — | A problem of the list as a whole («Добавьте хотя бы одну позицию»); a field's own error goes on that field. |
|
|
63
66
|
| `disabled` | `boolean` | `false` | Disables every field, handle and button; a disabled `Fieldset` around it sets it too. |
|
|
64
67
|
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Tier of the headings, buttons and the kit fields that read the host tier. Without it the host tier, else `m`. |
|
|
@@ -72,6 +75,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
72
75
|
|---|---|---|---|
|
|
73
76
|
| `fill` | shares the free width with the other `fill` columns | names, descriptions, any text | yes |
|
|
74
77
|
| `compact` | two control heights wide | an icon-only `Select`, a `ColorPresets` swatch, a checkbox | |
|
|
78
|
+
| `xs` | 96 px | a number of a few digits: a share, a percent, a count | |
|
|
75
79
|
| `s` | 144 px | a quantity, a code, a number of days | |
|
|
76
80
|
| `m` | 192 px | an amount with its unit, a date, a phone | |
|
|
77
81
|
|
|
@@ -82,6 +86,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
82
86
|
| `getLocked` | a lock where the grip is, no remove button; the row holds its place | a base value, system records a person cannot drop | — |
|
|
83
87
|
| `createItem` | the add button under the rows, aligned with the first column | the person grows the list | — |
|
|
84
88
|
| `min` · `max` | remove buttons off at `min`; the add button off with «Не больше {max}» at `max` | the record count is bounded | `0` · — |
|
|
89
|
+
| `renderTotals` | a row of totals under the rows over a faint line, each on its column; «Итого» in the first cell without a total | the rows add up (shares, amounts, hours) | — |
|
|
85
90
|
|
|
86
91
|
**Combinations**
|
|
87
92
|
- Recommended: `label` on the list (or a `Fieldset` legend around it), one heading per column, a `getLabel` from the title field, an `addLabel` that says what is added.
|
|
@@ -98,13 +103,15 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
98
103
|
| pinned row | `getLocked(item)` | `data-locked="true"` on the `<li>`; a lock, no remove button |
|
|
99
104
|
| invalid | `error` (the list), a field's own `error` | the list's message under the add row; a field's under the field in its row |
|
|
100
105
|
| disabled | `disabled` (or a disabled Fieldset) | `data-disabled="true"` on the frame; fields, handles and buttons disabled through the kit's disabled context |
|
|
101
|
-
| stacked | container narrower than 30rem | heading row hidden, each row's fields in one column with their labels shown |
|
|
106
|
+
| stacked | container narrower than 30rem | heading row hidden, each row's fields in one column with their labels shown; each total on its own line after its column name |
|
|
107
|
+
| saving | a promise from `onValueChange` | the rows show the new list, `aria-busy="true"` on the `<ol>`; a reject brings the owner's list back |
|
|
102
108
|
| rows come and go | the add button, a remove button, a new `value` | a row the data adds rises in once (`enterMotion`), a removed one closes up in place and the rows below glide up (`listPresence`); the heading row rises in with the first row and closes up with the last (`partPresence`); the first render stays still |
|
|
103
109
|
|
|
104
110
|
## Layout & spacing
|
|
105
111
|
- Rows 12 apart (`--prime-space-3`); columns 12 apart; the heading row sits at the tier's label gap above the first row.
|
|
106
112
|
- Every row and the heading row share one track list: `[lead] [columns] [end]`; the lead is one `xs` control wide (the grip or the lock), the end fits a remove button of the tier.
|
|
107
113
|
- The handle, the lock and the remove button stand on the middle of the first line of fields, so a field's error under it grows the row downwards only.
|
|
114
|
+
- The totals row stands 12 under the last row over a faint line, 12 more above its totals; a total's text starts where its column's field text does.
|
|
108
115
|
- The add button lines up with the first column of fields, 12 under the last row.
|
|
109
116
|
- Under 30rem of its own width (a container query): rows stack, fields 16 apart inside a row, rows 24 apart.
|
|
110
117
|
|
|
@@ -126,6 +133,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
126
133
|
- The heading row is `aria-hidden`: each field in a cell carries its column heading as its own label, read but not shown on wide rows.
|
|
127
134
|
- The `<ol>` is described by the list's `hint` and `error` (`aria-describedby`), which speak for every row.
|
|
128
135
|
- In a narrow container the heading row goes and each field's label shows above it.
|
|
136
|
+
- The totals row (`renderTotals`) is a `group` named by `labels.totals`; each total is read after its column heading.
|
|
129
137
|
- The remove button is «Удалить {row}», the handle «Переместить {row}»: the row's name from `getLabel`, else its number.
|
|
130
138
|
- A pinned row has a lock in place of the handle and no remove button; without `reorderable` the lock is Dnd's lock (an image named «Закреплена»).
|
|
131
139
|
|
|
@@ -140,6 +148,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
140
148
|
| `namedRow` | `"«{label}»"` | Name of a row with `getLabel` text; `{label}` is that text. |
|
|
141
149
|
| `locked` | `"Закреплена"` | Spoken name of the lock of a pinned row in a list without `reorderable`. |
|
|
142
150
|
| `maxReached` | `"Не больше {max}"` | Text beside the disabled add button once the list holds `max` rows. |
|
|
151
|
+
| `totals` | `"Итого"` | Name of the totals row (`renderTotals`), shown in its first cell when that has no total. |
|
|
143
152
|
|
|
144
153
|
## Examples
|
|
145
154
|
| Example | Shows |
|
|
@@ -151,6 +160,8 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
151
160
|
| [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, each change is a row with its month and its price, and the unit sits in the field, not in a heading — `getLocked`, `width`. |
|
|
152
161
|
| [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`. |
|
|
153
162
|
| [errors.tsx](examples/errors.tsx) | Contacts of a client checked on save: a field's problem shows under that field in its row, a problem of the whole list under the list, and focus goes to the first wrong field; a `*` after a heading marks a required column — `error`, `required`. |
|
|
163
|
+
| [totals.tsx](examples/totals.tsx) | A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts — `renderTotals`, `width`. |
|
|
164
|
+
| [saving.tsx](examples/saving.tsx) | Removing a recipient is saved on the server: the row leaves at once and comes back with the reason if the server refuses — `onValueChange`, `error`. |
|
|
154
165
|
| [states.tsx](examples/states.tsx) | The same list editable and disabled while the request saves it: fields, handles and buttons turn off together — `disabled`. |
|
|
155
166
|
| [controlled.tsx](examples/controlled.tsx) | The parent owns the lines: it sums them under the list and puts the saved lines back on reset — `value`, `onValueChange`. |
|
|
156
167
|
| [narrow.tsx](examples/narrow.tsx) | The invoice lines in a 320 px column: the headings give way, each row stacks its fields with their own labels and rows stand a group apart — `columns`. |
|
|
@@ -165,6 +176,7 @@ Generic `FieldList<T>`; `ref` → `HTMLDivElement` (the field frame). An editabl
|
|
|
165
176
|
- A hint that explains what the lock already shows («Первые три всегда первыми») → no hint; the lock says it.
|
|
166
177
|
- `aria-label` written on every field in a cell → none: the column heading names the field.
|
|
167
178
|
- A grip, a remove button and an add button drawn by hand around a `.map()` of fields → FieldList.
|
|
179
|
+
- A total under the list typed by hand in a line of text, or a DataTable of fields to get a totals row → `renderTotals`.
|
|
168
180
|
|
|
169
181
|
## Related
|
|
170
182
|
- **Built from:** [Button](../button/COMPONENT.md) (add, remove), [Dnd](../dnd/COMPONENT.md) (`reorderable`: `Dnd.Sortable`, `Dnd.Handle`; the lock of a pinned row), [Label](../label/COMPONENT.md) (headings), the field frame (label, [Hint](../hint/COMPONENT.md) for the hint and error), [VisuallyHidden](../visually-hidden/COMPONENT.md)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/** Removing a recipient is saved on the server: the row leaves at once and comes back with the reason if the server refuses — `onValueChange`, `error`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
type Recipient = { id: string; name: string; email: string; billing: boolean };
|
|
6
|
+
|
|
7
|
+
const COLUMNS: FieldListColumn[] = [{ label: "Имя" }, { label: "Почта" }];
|
|
8
|
+
|
|
9
|
+
const RECIPIENTS: Recipient[] = [
|
|
10
|
+
{ id: "accounting", name: "Бухгалтерия", email: "buh@severstroy.ru", billing: true },
|
|
11
|
+
{ id: "director", name: "Ирина Котова", email: "kotova@severstroy.ru", billing: false },
|
|
12
|
+
{ id: "manager", name: "Олег Демин", email: "demin@severstroy.ru", billing: false },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
/** The server's answer to a removal, a beat later: the billing contact stays. */
|
|
16
|
+
function saveRemoval(removed: Recipient[]) {
|
|
17
|
+
return new Promise<void>((resolve, reject) => {
|
|
18
|
+
setTimeout(() => {
|
|
19
|
+
if (removed.some((recipient) => recipient.billing)) {
|
|
20
|
+
reject(new Error("Бухгалтерию нельзя убрать: на неё уходят счета"));
|
|
21
|
+
} else {
|
|
22
|
+
resolve();
|
|
23
|
+
}
|
|
24
|
+
}, 900);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default function FieldListSavingExample() {
|
|
29
|
+
const [recipients, setRecipients] = React.useState(RECIPIENTS);
|
|
30
|
+
const [failure, setFailure] = React.useState<string | null>(null);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<FieldList
|
|
34
|
+
label="Кому отправлять счета"
|
|
35
|
+
error={failure}
|
|
36
|
+
columns={COLUMNS}
|
|
37
|
+
value={recipients}
|
|
38
|
+
onValueChange={(next) => {
|
|
39
|
+
setFailure(null);
|
|
40
|
+
const removed = recipients.filter(
|
|
41
|
+
(recipient) => !next.some((item) => item.id === recipient.id),
|
|
42
|
+
);
|
|
43
|
+
if (removed.length === 0) {
|
|
44
|
+
setRecipients(next);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
return saveRemoval(removed).then(
|
|
48
|
+
() => setRecipients(next),
|
|
49
|
+
(reason: Error) => {
|
|
50
|
+
setFailure(reason.message);
|
|
51
|
+
throw reason;
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
}}
|
|
55
|
+
getId={(recipient) => recipient.id}
|
|
56
|
+
getLabel={(recipient) => recipient.name}
|
|
57
|
+
createItem={() => ({ id: crypto.randomUUID(), name: "", email: "", billing: false })}
|
|
58
|
+
addLabel="Добавить получателя"
|
|
59
|
+
renderItem={(recipient, row) => (
|
|
60
|
+
<>
|
|
61
|
+
<Input.Root>
|
|
62
|
+
<Input.Wrapper>
|
|
63
|
+
<Input.Field
|
|
64
|
+
value={recipient.name}
|
|
65
|
+
onChange={(event) => row.update({ name: event.target.value })}
|
|
66
|
+
/>
|
|
67
|
+
</Input.Wrapper>
|
|
68
|
+
</Input.Root>
|
|
69
|
+
<Input.Root>
|
|
70
|
+
<Input.Wrapper>
|
|
71
|
+
<Input.Field
|
|
72
|
+
type="email"
|
|
73
|
+
value={recipient.email}
|
|
74
|
+
placeholder="name@company.ru"
|
|
75
|
+
onChange={(event) => row.update({ email: event.target.value })}
|
|
76
|
+
/>
|
|
77
|
+
</Input.Wrapper>
|
|
78
|
+
</Input.Root>
|
|
79
|
+
</>
|
|
80
|
+
)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** A deal bonus split between the team: the share takes a narrow column, and the totals row under the lines sums the shares and the amounts — `renderTotals`, `width`. */
|
|
2
|
+
import { FieldList, type FieldListColumn, Input } from "prime-ui-kit";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
type Share = { id: string; member: string; percent: string };
|
|
6
|
+
|
|
7
|
+
const BONUS = 240_000;
|
|
8
|
+
|
|
9
|
+
const COLUMNS: FieldListColumn[] = [
|
|
10
|
+
{ label: "Участник" },
|
|
11
|
+
{ label: "Доля", width: "xs" },
|
|
12
|
+
{ label: "Сумма", width: "m" },
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const SHARES: Share[] = [
|
|
16
|
+
{ id: "sokolova", member: "Анна Соколова", percent: "50" },
|
|
17
|
+
{ id: "demin", member: "Олег Демин", percent: "30" },
|
|
18
|
+
{ id: "kotova", member: "Ирина Котова", percent: "20" },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const RUB = new Intl.NumberFormat("ru-RU", {
|
|
22
|
+
style: "currency",
|
|
23
|
+
currency: "RUB",
|
|
24
|
+
maximumFractionDigits: 0,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const percentOf = (share: Share) => Number(share.percent.replace(",", ".")) || 0;
|
|
28
|
+
|
|
29
|
+
export default function FieldListTotalsExample() {
|
|
30
|
+
const [shares, setShares] = React.useState(SHARES);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<FieldList
|
|
34
|
+
label="Бонус по сделке"
|
|
35
|
+
hint="Доли в сумме дают 100 %."
|
|
36
|
+
columns={COLUMNS}
|
|
37
|
+
value={shares}
|
|
38
|
+
onValueChange={setShares}
|
|
39
|
+
getId={(share) => share.id}
|
|
40
|
+
getLabel={(share) => share.member}
|
|
41
|
+
createItem={() => ({ id: crypto.randomUUID(), member: "", percent: "" })}
|
|
42
|
+
addLabel="Добавить участника"
|
|
43
|
+
renderItem={(share, row) => (
|
|
44
|
+
<>
|
|
45
|
+
<Input.Root>
|
|
46
|
+
<Input.Wrapper>
|
|
47
|
+
<Input.Field
|
|
48
|
+
value={share.member}
|
|
49
|
+
onChange={(event) => row.update({ member: event.target.value })}
|
|
50
|
+
/>
|
|
51
|
+
</Input.Wrapper>
|
|
52
|
+
</Input.Root>
|
|
53
|
+
<Input.Root>
|
|
54
|
+
<Input.Wrapper>
|
|
55
|
+
<Input.Field
|
|
56
|
+
value={share.percent}
|
|
57
|
+
inputMode="decimal"
|
|
58
|
+
onChange={(event) => row.update({ percent: event.target.value })}
|
|
59
|
+
/>
|
|
60
|
+
<Input.InlineAffix side="end">%</Input.InlineAffix>
|
|
61
|
+
</Input.Wrapper>
|
|
62
|
+
</Input.Root>
|
|
63
|
+
<Input.Root>
|
|
64
|
+
<Input.Wrapper>
|
|
65
|
+
<Input.Field value={RUB.format((BONUS * percentOf(share)) / 100)} readOnly />
|
|
66
|
+
</Input.Wrapper>
|
|
67
|
+
</Input.Root>
|
|
68
|
+
</>
|
|
69
|
+
)}
|
|
70
|
+
renderTotals={(items) => {
|
|
71
|
+
const percent = items.reduce((sum, share) => sum + percentOf(share), 0);
|
|
72
|
+
return (
|
|
73
|
+
<>
|
|
74
|
+
{null}
|
|
75
|
+
{`${percent} %`}
|
|
76
|
+
{RUB.format((BONUS * percent) / 100)}
|
|
77
|
+
</>
|
|
78
|
+
);
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -120,6 +120,7 @@ A rejection of `upload` fails the file with its message as the reason; retry que
|
|
|
120
120
|
| `required` | `boolean` | — | Red `*` after the label and native `required` on the input. |
|
|
121
121
|
| `optional` | `boolean` | — | Muted marker right after the label text (`labels.optional`). |
|
|
122
122
|
| `hint` | `ReactNode` | — | Help text under the field. Hidden while `error` is shown. |
|
|
123
|
+
| `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. |
|
|
123
124
|
| `error` | `ReactNode` | — | Error message under the field (a rejected file, too few files); implies `invalid`. |
|
|
124
125
|
| `invalid` | `boolean` | — | Danger line and icon of the zone, `aria-invalid` on the input. A non-empty `error` implies it. |
|
|
125
126
|
| `focusRing` | `boolean` | `true` | `false` hides only the visual focus ring of the drop zone and the add tile (`data-focus-ring="false"`); focus, keyboard and ARIA stay. The picker Button keeps its ring. |
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
## When to use
|
|
9
9
|
- Under a control without `hint` / `error` props (an embedded `Datepicker.Panel`, a custom control), where you wire the ids yourself: format rules, limits, where the value comes from.
|
|
10
10
|
- As the error message of such a control (`invalid`), in the same slot as the hint.
|
|
11
|
+
- A status the field's value has (a deadline that nears, a check that passed): `tone`, with the status in the words.
|
|
11
12
|
|
|
12
13
|
## When not to use
|
|
13
14
|
- Input, Textarea, Select, Checkbox, Slider, SegmentedControl and other fields with `hint` / `error` props → pass the props; they render Hint and link the ids ([Input](../input/COMPONENT.md), [Textarea](../textarea/COMPONENT.md), [Checkbox](../checkbox/COMPONENT.md), [Slider](../slider/COMPONENT.md)).
|
|
@@ -36,6 +37,7 @@ Hint.Root <p> with the text
|
|
|
36
37
|
| Prop | Type | Default | Description |
|
|
37
38
|
|---|---|---|---|
|
|
38
39
|
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | host tier, else `"m"` | Tier of the paired field: 12/16 for xs–m, 13/20 for l and xl. Without it the tier of its host, else `m`. |
|
|
40
|
+
| `tone` | `"neutral" \| "success" \| "warning" \| "danger"` | `"neutral"` | Status color of the text (a deadline that nears, a check that passed). The words carry the status, the color repeats it; `invalid` and `disabled` take over the color. |
|
|
39
41
|
| `invalid` | `boolean` | `false` | Error styling: danger text that drops in (fade + 4 px from above). |
|
|
40
42
|
| `disabled` | `boolean` | `false` | Disabled text color, next to a disabled control. A disabled `Fieldset` around the hint sets it too. |
|
|
41
43
|
| `children` | `ReactNode` | — | Text, optionally with `Hint.Icon` first. |
|
|
@@ -49,7 +51,15 @@ Hint.Root <p> with the text
|
|
|
49
51
|
| `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `children` (the glyph), `className` and the other span attributes. |
|
|
50
52
|
|
|
51
53
|
## Variants
|
|
52
|
-
|
|
54
|
+
### tone
|
|
55
|
+
| Value | Looks like | Use when | Default |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `neutral` | Muted text | Format rules, limits, where the value comes from | yes |
|
|
58
|
+
| `success` | Success text | The value passed a check («ИНН проверен») | |
|
|
59
|
+
| `warning` | Warning text | The value needs attention soon («Осталось 12 дней») | |
|
|
60
|
+
| `danger` | Danger text, no `aria-invalid` | A status of the data, not a validation error («Срок истёк») | |
|
|
61
|
+
|
|
62
|
+
The words carry the status; the color only repeats it, and a `Hint.Icon` with the status glyph adds a shape. A validation error is `invalid` (or a field's `error`), never `tone="danger"`. `invalid` and `disabled` take over the tone's color.
|
|
53
63
|
|
|
54
64
|
### size
|
|
55
65
|
| Value | Looks like | Use when | Default |
|
|
@@ -68,11 +78,12 @@ The hint is always smaller than the field text; use the field's size.
|
|
|
68
78
|
| `invalid` | Danger text, drops in | Validation error | `false` |
|
|
69
79
|
| `disabled` | Disabled text | Under a disabled control | `false` |
|
|
70
80
|
|
|
71
|
-
**Combinations** — one element switches between the hint text and the error text with `invalid` (no layout jump); a standalone hint that changes after an action stays mounted with `aria-live="polite"` (inside a field the frame does it). `invalid` and `disabled` together make no sense.
|
|
81
|
+
**Combinations** — one element switches between the hint text and the error text with `invalid` (no layout jump); a standalone hint that changes after an action stays mounted with `aria-live="polite"` (inside a field the frame does it). `invalid` and `disabled` together make no sense. A result of the whole form goes to a [Banner](../banner/COMPONENT.md), not a toned hint.
|
|
72
82
|
|
|
73
83
|
## States
|
|
74
84
|
| State | Driven by | DOM |
|
|
75
85
|
|---|---|---|
|
|
86
|
+
| status | `tone` | `data-tone` with the shared text tone (none for `neutral`, and none while `invalid` or `disabled`); the color eases between tones |
|
|
76
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 |
|
|
77
88
|
| disabled | `disabled`, or a disabled `Fieldset` around it | `data-disabled="true"` |
|
|
78
89
|
| size | `size`, else the host tier | `data-size` |
|
|
@@ -106,6 +117,7 @@ No `labels`.
|
|
|
106
117
|
| Example | Shows |
|
|
107
118
|
|---|---|
|
|
108
119
|
| [overview.tsx](examples/overview.tsx) | Help text under a calendar that has no hint of its own, linked to it through `id` and `aria-describedby` — `id`. |
|
|
120
|
+
| [variants.tsx](examples/variants.tsx) | Status hints whose words carry the status and the color repeats it — `tone`. |
|
|
109
121
|
| [sizes.tsx](examples/sizes.tsx) | Every size tier, one per field tier: 12/16 for xs–m, 13/20 for l and xl — `size`. |
|
|
110
122
|
| [states.tsx](examples/states.tsx) | Default help text next to an error and a hint under a disabled control — `invalid`, `disabled`. |
|
|
111
123
|
| [with-icon.tsx](examples/with-icon.tsx) | A leading icon centred on the first line, in the default and the error state — `Hint.Icon`. |
|
|
@@ -116,6 +128,8 @@ No `labels`.
|
|
|
116
128
|
- A separate hint and error element at the same time → one element; the error replaces the hint.
|
|
117
129
|
- Hint without `id` + `aria-describedby` → the screen reader does not connect it to the field.
|
|
118
130
|
- Hint size different from the field size → use the same `size`, or none inside a sized host.
|
|
131
|
+
- `tone="danger"` for a validation error → `invalid` (or the field's `error`): only it sets `aria-invalid`.
|
|
132
|
+
- A status by color alone («12 д.» in orange) → the words say it («Осталось 12 дней»), the tone repeats it.
|
|
119
133
|
- `role="alert"` on a field error → none; the field frame announces it, a standalone hint takes `aria-live="polite"`.
|
|
120
134
|
|
|
121
135
|
## Related
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Status hints whose words carry the status and the color repeats it — `tone`. */
|
|
2
|
+
import { Hint, Icon, Typography } from "prime-ui-kit";
|
|
3
|
+
|
|
4
|
+
export default function HintVariantsExample() {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<div>
|
|
8
|
+
<Hint.Root>Действует до 30 ноября.</Hint.Root>
|
|
9
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
10
|
+
neutral
|
|
11
|
+
</Typography>
|
|
12
|
+
</div>
|
|
13
|
+
<div>
|
|
14
|
+
<Hint.Root tone="success">
|
|
15
|
+
<Hint.Icon>
|
|
16
|
+
<Icon name="status.success" />
|
|
17
|
+
</Hint.Icon>
|
|
18
|
+
ИНН проверен в ФНС.
|
|
19
|
+
</Hint.Root>
|
|
20
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
21
|
+
success
|
|
22
|
+
</Typography>
|
|
23
|
+
</div>
|
|
24
|
+
<div>
|
|
25
|
+
<Hint.Root tone="warning">
|
|
26
|
+
<Hint.Icon>
|
|
27
|
+
<Icon name="status.warning" />
|
|
28
|
+
</Hint.Icon>
|
|
29
|
+
Осталось 12 дней.
|
|
30
|
+
</Hint.Root>
|
|
31
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
32
|
+
warning
|
|
33
|
+
</Typography>
|
|
34
|
+
</div>
|
|
35
|
+
<div>
|
|
36
|
+
<Hint.Root tone="danger">
|
|
37
|
+
<Hint.Icon>
|
|
38
|
+
<Icon name="status.danger" />
|
|
39
|
+
</Hint.Icon>
|
|
40
|
+
Срок истёк 3 дня назад.
|
|
41
|
+
</Hint.Root>
|
|
42
|
+
<Typography as="span" variant="caption" tone="muted">
|
|
43
|
+
danger
|
|
44
|
+
</Typography>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|