prime-ui-kit 0.17.0 → 0.19.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-2ODXLPW3.js → chunk-QFHE5GKW.js} +1 -0
- package/dist/chunks/{chunk-7WWZYGRC.js → chunk-RZNGLELR.js} +4497 -3896
- package/dist/chunks/{chunk-A4WE3UNC.js → chunk-WYCTXD63.js} +18399 -17624
- package/dist/chunks/{chunk-SVFWPLNW.js → chunk-ZTZ2ML2B.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/ColumnsMenu.d.ts +16 -20
- package/dist/components/data-table/DataTable.d.ts +16 -11
- package/dist/components/data-table/Head.d.ts +10 -1
- package/dist/components/data-table/Row.d.ts +7 -1
- package/dist/components/data-table/StateRows.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 +24 -12
- package/dist/components/hint/Hint.d.ts +8 -2
- package/dist/components/index.d.ts +7 -4
- 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 +8 -9
- package/dist/components/list/List.d.ts +25 -6
- 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 +7 -1
- package/dist/internal/formatClock.d.ts +23 -2
- package/dist/internal/leaveGuard.d.ts +27 -0
- package/dist/internal/messageList.d.ts +2 -0
- package/dist/internal/overlay/DialogGuard.d.ts +3 -2
- package/dist/internal/overlay/DialogParts.d.ts +40 -6
- 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 +48 -15
- package/dist/skill/anti-slop.md +28 -0
- package/dist/skill/api-contract.md +14 -7
- package/dist/skill/cheatsheet.md +28 -9
- package/dist/skill/checklist.md +9 -0
- package/dist/skill/choosing.md +25 -12
- package/dist/skill/components.md +1 -0
- package/dist/skill/composition.md +88 -16
- package/dist/skill/layouts.md +6 -4
- 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/confirm-dialog/COMPONENT.md +1 -1
- package/dist/skill/reference/components/crossfade/COMPONENT.md +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +46 -41
- package/dist/skill/reference/components/data-table/examples/actions-column.tsx +154 -0
- package/dist/skill/reference/components/data-table/examples/columns-visibility.tsx +3 -12
- package/dist/skill/reference/components/data-table/examples/controlled.tsx +1 -1
- package/dist/skill/reference/components/data-table/examples/narrow.tsx +4 -11
- package/dist/skill/reference/components/data-table/examples/numeric.tsx +9 -2
- package/dist/skill/reference/components/data-table/examples/row-link.tsx +18 -22
- package/dist/skill/reference/components/data-table/examples/screen.tsx +12 -1
- package/dist/skill/reference/components/data-table/examples/toolbar.tsx +26 -32
- 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/dnd/COMPONENT.md +4 -3
- package/dist/skill/reference/components/dnd/examples/examples.module.css +4 -3
- package/dist/skill/reference/components/dnd/examples/handle.tsx +5 -5
- package/dist/skill/reference/components/dnd/examples/locked.tsx +1 -1
- package/dist/skill/reference/components/dnd/examples/table.tsx +6 -6
- package/dist/skill/reference/components/drawer/COMPONENT.md +38 -9
- package/dist/skill/reference/components/drawer/examples/client-card.tsx +32 -8
- package/dist/skill/reference/components/drawer/examples/header-actions.tsx +1 -1
- package/dist/skill/reference/components/drawer/examples/loading.tsx +58 -0
- package/dist/skill/reference/components/field-list/COMPONENT.md +40 -24
- package/dist/skill/reference/components/field-list/examples/base-row.tsx +2 -1
- package/dist/skill/reference/components/field-list/examples/locked.tsx +11 -11
- package/dist/skill/reference/components/field-list/examples/reorder.tsx +1 -1
- package/dist/skill/reference/components/field-list/examples/saving.tsx +83 -0
- package/dist/skill/reference/components/field-list/examples/screen.module.css +1 -1
- package/dist/skill/reference/components/field-list/examples/screen.tsx +168 -162
- package/dist/skill/reference/components/field-list/examples/totals.tsx +83 -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 +12 -8
- package/dist/skill/reference/components/kanban/examples/columns.tsx +4 -2
- package/dist/skill/reference/components/kanban/examples/states.tsx +7 -5
- package/dist/skill/reference/components/list/COMPONENT.md +39 -13
- package/dist/skill/reference/components/list/examples/reorder.tsx +42 -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 +35 -10
- 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 +4 -2
- 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/sheet/COMPONENT.md +18 -7
- 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 +42 -20
- 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/page/examples/screen.module.css +1 -1
- package/dist/skill/reference/layout/page/examples/screen.tsx +93 -104
- package/dist/skill/reference/layout/page/examples/sections.tsx +2 -15
- package/dist/skill/reference/layout/sidebar/COMPONENT.md +1 -0
- package/dist/skill/responsive.md +19 -12
- package/dist/skill/ux.md +5 -3
- package/dist/tokens/semantic.d.ts +3 -1
- package/dist/tokens/themes/dark.d.ts +1 -1
- 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/confirm-dialog/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/data-table/COMPONENT.md +46 -41
- package/src/components/data-table/examples/actions-column.tsx +154 -0
- package/src/components/data-table/examples/columns-visibility.tsx +3 -12
- package/src/components/data-table/examples/controlled.tsx +1 -1
- package/src/components/data-table/examples/narrow.tsx +4 -11
- package/src/components/data-table/examples/numeric.tsx +9 -2
- package/src/components/data-table/examples/row-link.tsx +18 -22
- package/src/components/data-table/examples/screen.tsx +12 -1
- package/src/components/data-table/examples/toolbar.tsx +26 -32
- 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/dnd/COMPONENT.md +4 -3
- package/src/components/dnd/examples/examples.module.css +4 -3
- package/src/components/dnd/examples/handle.tsx +5 -5
- package/src/components/dnd/examples/locked.tsx +1 -1
- package/src/components/dnd/examples/table.tsx +6 -6
- package/src/components/drawer/COMPONENT.md +38 -9
- package/src/components/drawer/examples/client-card.tsx +32 -8
- package/src/components/drawer/examples/header-actions.tsx +1 -1
- package/src/components/drawer/examples/loading.tsx +58 -0
- package/src/components/field-list/COMPONENT.md +40 -24
- package/src/components/field-list/examples/base-row.tsx +2 -1
- package/src/components/field-list/examples/locked.tsx +11 -11
- package/src/components/field-list/examples/reorder.tsx +1 -1
- package/src/components/field-list/examples/saving.tsx +83 -0
- package/src/components/field-list/examples/screen.module.css +1 -1
- package/src/components/field-list/examples/screen.tsx +168 -162
- package/src/components/field-list/examples/totals.tsx +83 -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 +12 -8
- package/src/components/kanban/examples/columns.tsx +4 -2
- package/src/components/kanban/examples/states.tsx +7 -5
- package/src/components/list/COMPONENT.md +39 -13
- package/src/components/list/examples/reorder.tsx +42 -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 +35 -10
- 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 +4 -2
- package/src/components/segmented-control/examples/in-form.tsx +1 -1
- package/src/components/select/COMPONENT.md +7 -3
- package/src/components/sheet/COMPONENT.md +18 -7
- 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 +42 -20
- 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/page/examples/screen.module.css +1 -1
- package/src/layout/page/examples/screen.tsx +93 -104
- package/src/layout/page/examples/sections.tsx +2 -15
- package/src/layout/sidebar/COMPONENT.md +1 -0
- package/src/styles/theme-dark.css +1 -1
- package/src/styles/theme-light.css +2 -1
- package/dist/components/data-table/context.d.ts +0 -14
- package/dist/components/scroll-container/useWheelToInline.d.ts +0 -10
package/dist/skill/cheatsheet.md
CHANGED
|
@@ -37,11 +37,15 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
37
37
|
- After-upload work (attach the file to a record) runs inside `upload` after the request resolves — `context.id` is the item; a file the user can open is `FileUploadFile.href`, not your own download button beside the row.
|
|
38
38
|
- A choice in a form (SegmentedControl, ButtonGroup) is a field too: `label`, `hint`, `error` on its root — not a `Label` wired by hand.
|
|
39
39
|
- A record card that saves each field: `<Input.Root onCommit={save}>` (save returns a Promise) — not hand-written blur / Enter / Escape handlers or a field disabled while saving.
|
|
40
|
-
- Markdown text outside a chat (an announcement, a task description) is `<Textarea.Root formatting>`: the Composer's toolbar, hotkeys and subset (`**`, `_`, `~~`, `` ` ``, `||spoiler||`, links, `> `) — not a hand-made toolbar over a textarea. Turn it on only where the text is shown with its Markdown.
|
|
40
|
+
- Markdown text outside a chat (an announcement, a task description) is written in `<Textarea.Root formatting>`: the Composer's toolbar, hotkeys and subset (`**`, `_`, `~~`, `` ` ``, `||spoiler||`, links, `> `) — not a hand-made toolbar over a textarea. Turn it on only where the text is shown with its Markdown, and show it with `<RichText>{text}</RichText>` — not `dangerouslySetInnerHTML` or a Markdown library.
|
|
41
|
+
- Free text the team often repeats (a city, a place): `<Input.Root suggestions={CITIES}>` — any text stays allowed; not a `<datalist>` or a Select. A time of day: `<Input.Field type="time" />` — not a Datepicker or two Selects.
|
|
42
|
+
- A status of the value under a field («Осталось 12 дней»): `hint` with `hintTone="warning"`, the words carrying the status — not a coloured text of your own, and never `hintTone="danger"` for a validation error (that is `error`).
|
|
43
|
+
- A unit or a prefix stands in the field's affix (`Input.InlineAffix`, `Input.Affix`) — not in its `label` too: the affix is read with the field.
|
|
41
44
|
- Copy or call next to a value: `Input.CopyButton`, `Input.Action` inside `Input.Wrapper` — not a Button beside the field; a password: `Input.RevealButton`.
|
|
42
45
|
- Records of one shape a person adds and removes (invoice lines, contacts, specs, prices by period): `<FieldList columns={[{ label: "Услуга" }, { label: "Цена", width: "m" }]} value onValueChange getId createItem addLabel renderItem={(item, row) => <>…fields without label…</>} />` — the headings stand once, every field takes its column heading as its label; not a `.map()` of fields with a `label` in every row, not «Позиция 1», «Позиция 2».
|
|
43
46
|
- Small attributes in a row of fields: an icon-only `Select` (`<Select.Trigger><Select.TriggerIcon>…</Select.TriggerIcon></Select.Trigger>`, no `Select.Value`) and a `ColorPresets` swatch in `width: "compact"` columns — not a full-width Select and a ColorField on a line of their own.
|
|
44
47
|
- A base value with overrides (a price and its changes by month): one `FieldList`, the base as the first row pinned by `getLocked` — not a separate field above a list in another geometry.
|
|
48
|
+
- Rows that add up: `FieldList renderTotals={(items) => <>…one node per column…</>}` draws «Итого» under the rows on the same tracks — not a line of text typed under the list, not a DataTable of fields.
|
|
45
49
|
- One value in several units (a deposit in ₽, $, ฿): the fields side by side in one row under one heading, each unit in `Input.InlineAffix` — not a label per field, not the unit in the label and in the field.
|
|
46
50
|
|
|
47
51
|
## State
|
|
@@ -81,7 +85,9 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
81
85
|
- The app is navigation and the page: everything at the top belongs to the page's `<Page.Root>` — not an app-wide header, a global search or notifications above every page.
|
|
82
86
|
- A work screen (a list, a board, a chat, a record) titles itself in `<Page.TopBar>`: `<Page.Heading>` with `<Page.Title>` + `<Page.Description>` carrying a line of data («128 в работе · 6 ждут оплаты») — not a sentence about the page.
|
|
83
87
|
- The bar holds only its four groups: `<Page.Heading>`, `<Page.Tools aria-label>` (sections, `<SmartFilter.Toolbar />`, the view — in reading order), `<Page.Actions>`, `<Page.Chips>` (`<SmartFilter.Chips />`) — not a hand-made flex row of title, tabs, search and buttons ([the top bar](composition.md#the-top-bar)).
|
|
84
|
-
- A nested record: `<Breadcrumb.Root>` first in `<Page.Heading>`, then `<Page.Title>` with the record's name — the path ends in the title on one line; not a title row under the path, not a breadcrumb in the body.
|
|
88
|
+
- A nested record: `<Breadcrumb.Root>` first in `<Page.Heading>`, then `<Page.Title>` with the record's name — the path ends in the title on one line; not a title row under the path, not a breadcrumb in the body. With a router each crumb is `<Breadcrumb.Item asChild>` around its `Link`.
|
|
89
|
+
- A line of data that loads after the page opens: `<Page.Description loading={!summary}>` from the first render (`Drawer.Description` / `Modal.Description` alike) — not a Description mounted when the data arrives, which moves the title, nor a `Skeleton` placed in it by hand.
|
|
90
|
+
- A page that edits a record: `<Page.Guard dirty={changed} busy={saving} />` inside `Page.Root` — the Sidebar, BottomNav and path links ask before the changes are lost; not your own `beforeunload` or a confirm wired to each link.
|
|
85
91
|
- Every button of the page goes into one `<Page.Actions>`, secondary first, the primary last, each with a `<Button.Icon>` beside its label — not buttons split between two places, not a button with only text.
|
|
86
92
|
- A filtered page: `<SmartFilter.Root>` wraps `<Page.Root>` — it draws nothing; not `SmartFilter.Root` inside the bar.
|
|
87
93
|
- No size knob on the title: the bar sizes the heading and gives way by width itself ([the five steps](responsive.md#the-pages-top-bar--pagetopbar)).
|
|
@@ -91,15 +97,19 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
91
97
|
|
|
92
98
|
- `Card.Root` only for a bounded block with its own title: a KPI tile (`variant="stat-trend"`), a settings panel (`variant="panel"`), side facts — not around a whole section, a form field or a page.
|
|
93
99
|
- Never a `DataTable` inside a Card — the table is already a filled block; a card in a card only sparingly ([the rule](foundations.md#card-in-a-card)).
|
|
100
|
+
- A titled panel (a settings section, a group of a long form) is `<Card.Root>` with `<Card.Header>` (`<Card.Title as="h2">`, `<Card.Description>`) stacked in the flow — never a heading column beside its panel ([a titled block](composition.md#4-surfaces)).
|
|
94
101
|
- Section without a card: `<Page.Section label="…">` + the content (a link or button beside the heading in `Page.Actions`) — not a hand-made `<section>` + `Typography as="h2"` + own gap.
|
|
95
|
-
- The tiles of a screen (KPI cards, charts, panels): one `<Page.Grid>` with a `<Page.Cell span>` per tile — not a grid per row (the rows would stand 24 apart instead of 16).
|
|
102
|
+
- The tiles of a screen (KPI cards, charts, panels): one `<Page.Grid>` with a `<Page.Cell span>` per tile — not a grid per row (the rows would stand 24 apart instead of 16). A chart beside a panel is an 8 and a 4 written next to each other — the grid pairs them at mid widths; not 6 + 6 to dodge a hole.
|
|
103
|
+
- A KPI tile whose figure loads: `<Card.Root variant="stat-trend" loading>` with its parts mounted — not Skeletons placed in `Card.Value` by hand.
|
|
104
|
+
- Several tags or labels as one value (a client's tags): `<Badge.Group>` of `Badge.Root` — not a flex row with its own gap.
|
|
96
105
|
- Depth from fill, not lines: no borders around blocks; `<Divider />` only inside a block.
|
|
97
|
-
- Every scroll region is a `ScrollContainer` — never `overflow: auto` on a `div`: its hidden edges fade by themselves (`edges="shadow"` on a filled surface, `"none"` only for a page column), `scrollbar="hidden"` on strips, `snap` on strips of cards, `lock` to hold a list still, `anchor="end"` for a log. Kit components already scroll through it; do not wrap them again.
|
|
106
|
+
- Every scroll region is a `ScrollContainer` — never `overflow: auto` on a `div`: its hidden edges fade by themselves (`edges="shadow"` on a filled surface, `"none"` only for a page column), `scrollbar="hidden"` on strips, `snap` on strips of cards (it settles once the scroll ends; never `scroll-snap-type`), `lock` to hold a list still, `anchor="end"` for a log. Kit components already scroll through it; do not wrap them again.
|
|
98
107
|
- Tabs are a bar, not a container: `Tabs.Root` stands in the host's padding and the panel content goes straight into `Tabs.Panel` — never a Card or a padded `div` around the tabs or inside a panel. In a Modal / Drawer, `Tabs.Root` wraps `X.Header` + `X.Body` and `Tabs.List` goes last into the header — not a tab strip at the top of the body.
|
|
99
108
|
|
|
100
109
|
## Tables
|
|
101
110
|
|
|
102
111
|
- A list page is a `<DataTable columns rows getRowKey>` under the page's `<SmartFilter.Root fields value onValueChange>` with `<SmartFilter.Toolbar />` in its `Page.Tools` and `<SmartFilter.Chips />` in its `Page.Chips` ([where the filters go](composition.md#where-the-filters-go)); filter rows with `matchesSmartFilter` — not a search Input and a row of Selects above the table.
|
|
112
|
+
- The column chooser is `<DataTable columnsMenu>` in the header row; `toolbar` only for several controls of the table itself (bulk actions, a section table's own search and filters) ([controls of a table](composition.md#controls-of-a-table)) — never a toolbar for one button.
|
|
103
113
|
- Search over a table or a list page is always SmartFilter, even with no filter fields yet (`fields={[]}` is a plain search that grows into filters). A search `Input` only outside a filtered list: inside a picker, a settings section, a panel.
|
|
104
114
|
- Paging is built in: `paging="pages"` (default) with `pageSize`, `paging="infinite"` for feeds, `paging="none"` for short lists — not a separate `<Pagination>` under a DataTable.
|
|
105
115
|
- Long lists are virtualized inside the kit: pass the whole array to `DataTable` (with `scrollHeight` or `paging="infinite"`; over 100 rows — grouped, `onReorder` and `renderExpanded` bodies render every row, keep those short or paged), `TagSelect options`, `Chat.Messages items`, `Gantt rows` — never slice it by hand, never wrap a kit list in react-window / react-virtual or your own windowing. Big remote data is still paged or loaded on scroll by the server (`source="server"`, `onLoadMore`).
|
|
@@ -108,18 +118,25 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
108
118
|
- A chart with axes or several series: `<ChartFrame.Root series rows state>` around the library (or your SVG) with `ChartFrame.Plot renderPlot` and `Legend`; `rows` is the data table screen readers get — not a hand-made Card with a grey box while loading and a legend of colored dots.
|
|
109
119
|
- A tooltip on your own SVG chart: `<ChartFrame.Tooltip point={{ x, y }} items>` with the pointer in plot coordinates — not a `foreignObject` or a div placed and clamped by hand; it flips only when out of room.
|
|
110
120
|
- Amounts, counts and dates are `numeric` columns; statuses are `Badge.Root tone` from one status → tone map.
|
|
121
|
+
- A column header that needs a word more («Маржа»): the column's `description` (an info glyph with a tooltip) — not the explanation squeezed into the header, not your own icon and Tooltip in `header`.
|
|
122
|
+
- A list row with controls of its own (a Switch, a ⋯ menu): `List.ItemActions` inside `List.Item`, a stage colour as `Badge.Dot color` in `List.ItemIcon` — not a control inside the row's button, not a row rebuilt as a `div`.
|
|
111
123
|
- Rows people order by hand: `onReorder(id, beforeId)` (apply it with `moveBefore`, return the save's Promise) — not a Dnd list rebuilt next to the table.
|
|
112
124
|
- A short list of records people order by hand (stages, priorities) is `<List.Root onReorder>` with `List.Item value` — not up / down buttons or a hand-made `Dnd.Sortable`.
|
|
125
|
+
- A toggle in a reorderable row leads it (`List.ItemActions side="start"`) and a plain settings row ends with it ([list rows](composition.md#list-rows)) — never a Switch between the label and the grip.
|
|
126
|
+
- The grip ends its row: a `Dnd.Handle` is the last child of its item, after the row's own controls, and a locked row's lock takes the same place (List, FieldList and Kanban place theirs there) — not before the row's text.
|
|
113
127
|
- Groups that fold: pass `defaultCollapsedGroups={[]}` (or the controlled `collapsedGroups` + `onCollapsedGroupsChange`) — not your own button in `renderGroupHeader` or filtering `rows`. One heatmap over several columns: the same `scale: { min, max }` on each.
|
|
114
128
|
- A trend in a table row is `<Sparkline layout="compact">` in the column's `cell` (a column `width`, series built once, not inline, the same `min` / `max` on every row when rows compare by size); the number itself goes in its own column. A missing day is `value: null`, never `0`.
|
|
115
129
|
- Properties of one record: `<DescriptionList.Root>` of `DescriptionList.Item` — not a hand-made grid of label / value text, not a one-row DataTable.
|
|
116
130
|
- Who is busy when, a plan with dates: `Gantt` with `onBarChange` / `onBarCreate` — not bars drawn by hand or a DataTable of start and end dates. Statuses moved by hand: `Kanban` — `plain` (default, a header plate) or `line` (a header over a line) for a board that is the page's main region, `variant="well"` only when columns must read as bounded queues (WIP limits, sparse columns); never a Card or a filled box around a column.
|
|
131
|
+
- A stage's figures go in `renderColumnMeta` (an array is a line per entry: «12 сделок · 4,8 млн ₽», «Средний чек 400 000 ₽») — not in the column `title`.
|
|
117
132
|
- Open a board card with `Kanban.Root onItemOpen` (click, Enter, Space, never after a drag) or `getItemHref`; a Gantt row with `onRowClick` / `getRowHref` (the DataTable contract), a bar with `onItemOpen`; more facts in a bar tooltip with `renderBarTooltip` (it adds lines under the dates). A legend of hues is a row of `Badge.Root color` + `Badge.Dot` in the host's `toolbar`, the category also in text. Hour slots (bookings, appointments) are not Gantt — its unit is a calendar day.
|
|
118
133
|
|
|
119
134
|
## Chat
|
|
120
135
|
|
|
121
136
|
- A conversation is `Chat`: `<Chat.Root typing selected search>` with `Chat.Header` (avatar, `Chat.Title`, `Chat.Description`, `Chat.Actions` + `Chat.SearchButton`), `Chat.Search`, `Chat.Pinned`, `<Chat.Messages items getKey getAuthor getDate renderMessage>` and `<Chat.Footer><Composer.Root /></Chat.Footer>` — not a header, a ScrollContainer and a list of your own, not Cards or a Timeline styled as bubbles, not a Textarea with a send button. A list without the rest is `Chat.Root` with `Chat.Messages` alone; one message on its own is `Message.Root`.
|
|
122
|
-
- The list of conversations is `ChatList` — not a `List` or Cards with your own preview, unread counts and «печатает…». Back button and avatar in the chat header are plain children of `Chat.Header`: it lays them out side by side.
|
|
137
|
+
- The list of conversations is `ChatList` — not a `List` or Cards with your own preview, unread counts and «печатает…». Back button and avatar in the chat header are plain children of `Chat.Header`: it lays them out side by side. A dialog with its own route is `<ChatList.Item asChild>` around the router `Link`.
|
|
138
|
+
- Times in the operator's zone, not the browser's: `timeZone="Europe/Moscow"` on `Chat.Messages`, `ChatList.Root` and a lone `Message.Root` — not dates shifted by hand.
|
|
139
|
+
- A sticker is `Message.Sticker` (your Lottie or video player inside it); a message shown outside a chat (a bot's welcome message in its settings) is `<Message.Root preview>` — not a screenshot or a bubble drawn again.
|
|
123
140
|
- A dialog loading is `<Chat.Messages loading>` (bubble skeletons that cross-fade into the list) — not a Spinner or grey boxes. A bot's buttons under a message are `Message.Actions` rows of `Button.Root variant="soft" tone="neutral"` (`asChild` for links), never buttons inside `Message.Text`.
|
|
124
141
|
- `renderMessage` returns a `Message.Root` (its `value` and `position` are set for you) or a `Chat.Service`; an inline function is fine: an item renders again only when it or its place changes, so a row shows what its item holds, and another message (a reply's original) comes from `entry.find(key)`. Dates, runs and the unread band (`unread={key}`) come from the list.
|
|
125
142
|
- A group chat is `<Chat.Messages group>`: put `Message.Author` (`color={hueOf(user.id)}`, `tag`, `Message.AuthorIcon` for a crown) and `Message.Avatar` on every message — the list shows the header on the first of a run and the avatar on the last; a direct chat shows neither.
|
|
@@ -140,13 +157,14 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
|
|
|
140
157
|
|
|
141
158
|
- Blocking decision or ≤ 4 fields → `Modal` (`<Modal.Content size="s">` for a confirm); long form, filters, details → `Drawer`; a short task that ends by itself (a date, an action, a note) → `Sheet`; small panel at a control → `Popover`; list of actions → `Dropdown`; a field value → `Select`; a name for an icon button → `Tooltip`; ⌘K → `CommandMenu`; a photo, a scan or a video full screen from its thumbnail → `MediaViewer` (an album in a grid is `Gallery`, which opens it for you; a standalone viewer keeps `open` and `index` in two states) — not a Modal with an `<img>`.
|
|
142
159
|
- A record's drawer or modal shows its person or object as `Drawer.Icon` / `Modal.Icon` around `Avatar.Root` / `Thumbnail.Root`. A confirm that asks for input (a reason) is a `Modal size="s"` with the field, `Modal.Confirm` and `Modal.Guard busy` — `ConfirmDialog` is a question only.
|
|
160
|
+
- A titled group in a dialog's body (contacts, deals, history): `<Drawer.Section label="Контакты">` (`Modal.Section`), its button in `Drawer.Actions` inside it — not a `Typography as="h3"` row with buttons of your own.
|
|
143
161
|
- Never a hand-made `div` backdrop, dialog, menu or tooltip.
|
|
144
162
|
- Every Modal and Drawer has its `Header` (title, ×); `Body` (content, scrolls) and `Footer` (actions) are there when it has content and actions. Actions always go into the `Footer`: details `<Drawer.Footer><Drawer.Close><Button.Root variant="outline" tone="neutral">Закрыть</Button.Root></Drawer.Close></Drawer.Footer>`, plus the main action last; a form: «Отмена» + submit — not Save at the bottom of the body. A `Sheet` (from the bottom edge) is the opposite: no footer by default — a pick or a swipe ends it — and its header is optional: keep it when the content needs a title, drop it (× and hairline go too) when the content speaks for itself, with `aria-label` on `Sheet.Content`. Never `Drawer.Content side="bottom"`.
|
|
145
163
|
- Content inside a Drawer, a Sheet or a Card is the host's own: a calendar is `Datepicker.Panel` straight in the body, across its width — no card, no padded box around it.
|
|
146
164
|
- A destructive confirm is `<ConfirmDialog.Root title description confirmLabel onConfirm={remove}>` with a `ConfirmDialog.Trigger` — not a Modal assembled as a confirm with your own pending state; a reversible delete offers «Отменить» in the notification instead.
|
|
147
165
|
- Overlays close smoothly only if they stay mounted: render the `Root` always and drive visibility with `open` alone — never `{open && <Drawer.Root>}`, never a changing `key` on the Root. The kit keeps the last content on screen while the exit plays.
|
|
148
|
-
- Placement of a menu, popover, select or picker: leave the defaults — `side="bottom"`, `align="auto"`: it opens below the trigger, lines up with the trigger edge that has the room (a «⋯» at the end of a row grows toward the start) and flips to the top by itself. Never write `side` or `align` to restate that; set `align` only when the panel must line up with one edge of a block whatever room there is.
|
|
149
|
-
- While a request runs: `<Modal.Guard busy={saving} />` inside the content (`Drawer.Guard`, `Sheet.Guard` alike) — Escape, a press outside, × and `Close` are held, × and `Close` disabled; add `dirty` to ask before unsaved changes are lost. Not `closeOnEscape={false}` or `showClose={false}` by hand
|
|
166
|
+
- Placement of a menu, popover, select or picker: leave the defaults — `side="bottom"`, `align="auto"`: it opens below the trigger, lines up with the trigger edge that has the room (a «⋯» at the end of a row grows toward the start) and flips to the top by itself. Never write `side` or `align` to restate that; set `align` only when the panel must line up with one edge of a block whatever room there is. A Popover under a wide anchor that must never be narrower than it: `<Popover.Content width="min-trigger">`.
|
|
167
|
+
- While a request runs: `<Modal.Guard busy={saving} />` inside the content (`Drawer.Guard`, `Sheet.Guard` alike) — Escape, a press outside, × and `Close` are held, × and `Close` disabled; add `dirty` to ask before unsaved changes are lost; both also hold the browser's tab close. Not `closeOnEscape={false}` or `showClose={false}` by hand, not your own `beforeunload`.
|
|
150
168
|
|
|
151
169
|
## Motion — it comes with the components
|
|
152
170
|
|
|
@@ -171,7 +189,8 @@ State change is continuous in the whole kit: every region that changes what it s
|
|
|
171
189
|
- Refreshing data already on screen: keep it, set `aria-busy` — not back to a skeleton on every refetch.
|
|
172
190
|
- Table states in place: `loading` (+ `loadingRows`), `empty`, `loadError` on `DataTable` — the head and toolbar stay.
|
|
173
191
|
- First run of a page or region: `EmptyPage.Root` + `EmptyPage.Icon` + `EmptyPage.Title` + `EmptyPage.Description` + `EmptyPage.Actions` with one action — not «Здесь пока ничего нет».
|
|
174
|
-
- Page-level persistent problem: `<Banner.Root tone="danger">` first in `Page.Body`; the result of an action: `useNotifications().notify({ tone, title })
|
|
192
|
+
- Page-level persistent problem: `<Banner.Root tone="danger">` first in `Page.Body`; the result of an action: `useNotifications().notify({ tone, title })` — a toast never closes the Drawer or Modal it was raised from.
|
|
193
|
+
- A refused sign-in: `invalid` on the fields it concerns and the reason in a `<VisuallyHidden live="assertive">` mounted from the start — not a Banner in a `LoginForm`.
|
|
175
194
|
|
|
176
195
|
## Sidebar
|
|
177
196
|
|
|
@@ -189,7 +208,7 @@ Full rules: [responsive.md](responsive.md).
|
|
|
189
208
|
|
|
190
209
|
- Rearrange, never hide: every desktop function is on the phone, in another place or a menu — not `display: none` below a breakpoint.
|
|
191
210
|
- Breakpoints only in [their scope](responsive.md#breakpoints); inside a page — [intrinsic grids](responsive.md#intrinsic-grids) and container queries.
|
|
192
|
-
- A wide table scrolls inside itself; offer
|
|
211
|
+
- A wide table scrolls inside itself; offer the column chooser with `columnsMenu` — never drop columns by breakpoint, never hand-build the chooser.
|
|
193
212
|
- A row that opens a record page is `getRowHref` (a real link: ⌘-click, new tab), a row that opens a drawer is `onRowClick` — never `navigate()` in `onRowClick`, never your own `event.target` checks for buttons in the row.
|
|
194
213
|
- A list + detail screen is `Page.Split` (`Page.List`, `Page.Detail`) in a `Page.Root fill` page, its records a `List` (`current` on the open one) — never ghost Buttons with a soft current; a full-height table is `scrollHeight="fill"` — never a height computed from `100vh`.
|
|
195
214
|
- App sections on phones: `BottomNav` in `AppShell.Footer` — not in Main, not `position: fixed`, no own media query: the shell hides the footer while the Sidebar rail is on screen; a short task from the bottom edge: `Sheet` — Select, Dropdown, Popover and Modal become sheets below 640 by themselves.
|
package/dist/skill/checklist.md
CHANGED
|
@@ -11,6 +11,8 @@ checks.
|
|
|
11
11
|
- [ ] Every button of the page is in one `Page.Actions`, the primary last, each with a `Button.Icon` ([the top bar](composition.md#the-top-bar)).
|
|
12
12
|
- [ ] `SmartFilter.Root` wraps `Page.Root`; its toolbar is in `Page.Tools`, its chips in `Page.Chips` ([the top bar](composition.md#the-top-bar)).
|
|
13
13
|
- [ ] A Card only for a standalone titled block — never around a table, a single field or the page ([surfaces](composition.md#4-surfaces)).
|
|
14
|
+
- [ ] A titled panel carries its heading in its `Card.Header` and stacks in the flow; no heading column beside a panel ([surfaces](composition.md#4-surfaces)).
|
|
15
|
+
- [ ] The column chooser is `columnsMenu` in the table's header row; a DataTable `toolbar` holds two or more table controls or does not exist ([controls of a table](composition.md#controls-of-a-table)).
|
|
14
16
|
|
|
15
17
|
## UX ([ux.md](ux.md))
|
|
16
18
|
- [ ] I wrote the five-line brief before the JSX ([brief](ux.md#1-brief-before-code)).
|
|
@@ -20,6 +22,7 @@ checks.
|
|
|
20
22
|
- [ ] Buttons say the verb and the object, errors say how to fix it, empty states offer the next step ([words](ux.md#4-words)).
|
|
21
23
|
- [ ] Money, numbers and dates go through `Intl` with `ru-RU`, in Russian typography ([words](ux.md#4-words)).
|
|
22
24
|
- [ ] The friction of every action matches the cost of a mistake ([friction](ux.md#5-friction-matches-the-cost-of-a-mistake)).
|
|
25
|
+
- [ ] A form in a dialog holds its dismiss with `Modal.Guard` / `Drawer.Guard`, a form that is the page with `Page.Guard` ([friction](ux.md#5-friction-matches-the-cost-of-a-mistake)).
|
|
23
26
|
- [ ] A disabled control says why when the reason is off screen; an action without permission is hidden ([nothing to guess](ux.md#6-nothing-to-guess)).
|
|
24
27
|
- [ ] A request a value starts — a simulated one too — is cancelled when the value changes again ([nothing to guess](ux.md#6-nothing-to-guess)).
|
|
25
28
|
- [ ] Tabs, filters, search, page and the open record are in the URL ([nothing to guess](ux.md#6-nothing-to-guess)).
|
|
@@ -34,6 +37,7 @@ checks.
|
|
|
34
37
|
## Sizes and alignment
|
|
35
38
|
- [ ] Default size `m`; a tier changes for a whole region, never for one control ([the host tier](api-contract.md#size-and-the-host-tier)).
|
|
36
39
|
- [ ] Every row of controls uses one size and its heights line up ([density](composition.md#5-density-and-size)).
|
|
40
|
+
- [ ] A reorderable row's toggle leads it and the grip ends it; a plain settings row ends with its toggle ([list rows](composition.md#list-rows)).
|
|
37
41
|
- [ ] Controls in table cells are one tier below the table; no CSS sets row or cell heights ([size axis](foundations.md#3-size-axis)).
|
|
38
42
|
- [ ] Icon-only buttons are square (round only for media and transfers) and named ([icons](api-contract.md#icons)).
|
|
39
43
|
|
|
@@ -58,6 +62,7 @@ checks.
|
|
|
58
62
|
|
|
59
63
|
## Hierarchy
|
|
60
64
|
- [ ] One primary action per area, last in its row; the rest `soft` / `ghost` / `outline` neutral ([actions](composition.md#6-actions)).
|
|
65
|
+
- [ ] A dialog footer holds Cancel and the primary only; deleting the edited record is the last item of the header ⋯ menu ([actions](composition.md#6-actions)).
|
|
61
66
|
- [ ] The same action has the same label and variant everywhere on the screen ([secondary actions](api-contract.md#secondary-actions)).
|
|
62
67
|
|
|
63
68
|
## States
|
|
@@ -66,6 +71,8 @@ checks.
|
|
|
66
71
|
- [ ] Fields show `hint` and `error`; only the minority is marked `required` or `optional` ([forms](api-contract.md#forms)).
|
|
67
72
|
- [ ] Every data block has its loading, empty and error states ([feedback](composition.md#9-feedback)).
|
|
68
73
|
- [ ] Every region swaps its states through `Crossfade`, loading is a `Skeleton` of the data ([continuity](foundations.md#11-state-changes-are-continuous)).
|
|
74
|
+
- [ ] A `Page.Description` that waits for data stays mounted with `loading`, so the title in the bar never jumps ([screen states](composition.md#screen-states)).
|
|
75
|
+
- [ ] A KPI tile that waits for its figure is `Card.Root loading`, its parts mounted ([inside a category](choosing.md#2-inside-a-category)).
|
|
69
76
|
- [ ] While a form saves, its submit is `loading` and its fields sit in a `Fieldset disabled` ([forms](api-contract.md#forms)).
|
|
70
77
|
- [ ] A save that stays says «Сохранено» on its button, one that goes away notifies, a failure always shows ([feedback](composition.md#9-feedback)).
|
|
71
78
|
- [ ] Overlays stay mounted and are driven by `open` only ([motion mistakes](motion.md#6-mistakes)).
|
|
@@ -82,4 +89,6 @@ checks.
|
|
|
82
89
|
- [ ] Every action is reachable by Tab in reading order and works with Enter / Space ([interaction](responsive.md#interaction-and-accessibility)).
|
|
83
90
|
- [ ] Every field has a label (visible, or `aria-label` for a toolbar search) ([forms](api-contract.md#forms)).
|
|
84
91
|
- [ ] Meaning is never colour alone ([tone and color](foundations.md#6-tone-and-color)).
|
|
92
|
+
- [ ] A toned hint says its status in words; a validation problem is `error`, never a `danger` hint ([forms](api-contract.md#forms)).
|
|
93
|
+
- [ ] A refused sign-in marks its fields `invalid` and says why in a hidden live region, with no Banner ([feedback](composition.md#9-feedback)).
|
|
85
94
|
- [ ] Custom `labels` replace the default Russian strings where they do not fit ([system strings](api-contract.md#shared-vocabulary-exported-types)).
|
package/dist/skill/choosing.md
CHANGED
|
@@ -10,7 +10,7 @@ confuse. Links: [components.md](components.md). Whole screens: [composition.md](
|
|
|
10
10
|
| trigger an action (save, delete, go on) | Actions — Button, ButtonGroup, LinkButton |
|
|
11
11
|
| type a value (text, number, code, file, photos), group the fields of a form, edit a list of records of one shape | Inputs — Input, Textarea, DigitInput, FileUpload, Label, Hint, Fieldset, FieldList |
|
|
12
12
|
| pick from options, toggle, set a range, filter, a date or a color | Selection — Checkbox, Radio, Switch, SegmentedControl, Slider, Select, NativeSelect, TagSelect, SmartFilter, Datepicker, ColorField, ColorPicker, ColorSwatches |
|
|
13
|
-
| see data: status labels, people, objects, numbers and their trend, properties of a record, rows, a board, a schedule, events, code, disclosed sections | Data display — Badge, Avatar, Thumbnail, Kbd, Card, DescriptionList, DataTable, Kanban, Gantt, Timeline, CodeBlock, Accordion, Sparkline, ChartFrame |
|
|
13
|
+
| see data: status labels, people, objects, numbers and their trend, properties of a record, rows, a board, a schedule, events, code, disclosed sections | Data display — Badge, Avatar, Thumbnail, Kbd, Card, DescriptionList, DataTable, Kanban, Gantt, Timeline, CodeBlock, RichText, Accordion, Sparkline, ChartFrame |
|
|
14
14
|
| hold a conversation: pick a dialog, read and send chat messages of every kind | Messages — Chat, ChatList, Message, Gallery, VoicePlayer, VideoNote, Composer |
|
|
15
15
|
| learn what happened, how far along it is, or watch a region load and change state; say it to a screen reader | Status and loading — Banner, Notification, ProgressBar, ProgressCircle, Spinner, Skeleton, Crossfade, EmptyPage, VisuallyHidden |
|
|
16
16
|
| move between places: app sections, panels of a screen, the records of a list, the path, pages, steps | Navigation — Tabs, Breadcrumb, Pagination, List, Stepper, Sidebar, BottomNav |
|
|
@@ -33,7 +33,8 @@ Input with `Input.InlineAffix`. Search and filters above a list or table → Sma
|
|
|
33
33
|
field outside a list (a picker, a settings section, a panel) → Input with `Input.Icon` + `type="search"`
|
|
34
34
|
and `aria-label`. Several fields of a form under one heading → Fieldset (`label`, `disabled` while the
|
|
35
35
|
form saves). Several records of the same fields that a person adds, edits and removes (invoice lines,
|
|
36
|
-
contacts, specs, prices by period) → FieldList: one row per record, the headings once
|
|
36
|
+
contacts, specs, prices by period) → FieldList: one row per record, the headings once; their sums
|
|
37
|
+
under them → its `renderTotals`.
|
|
37
38
|
|
|
38
39
|
**Selection.**
|
|
39
40
|
|
|
@@ -46,21 +47,27 @@ contacts, specs, prices by period) → FieldList: one row per record, the headin
|
|
|
46
47
|
| one of many (> 5) or a long list; icons, descriptions, search, groups | Select (`Select.Content searchable`) |
|
|
47
48
|
| one record out of thousands for a form field (a client, a contract), searched on the server | Select with `search` / `onSearchChange`, `filter={false}`, `loading`, `hasMore` + `onLoadMore` — never a read-only field that opens a CommandMenu |
|
|
48
49
|
| one value from a plain text list on a mobile-first form, posted with the form (`name`) | NativeSelect — the OS picker opens on phones |
|
|
50
|
+
| free text the team often repeats (a place, a city), any other text allowed | Input with `suggestions` — never a `<datalist>`, never a Select |
|
|
51
|
+
| a pick that belongs to a text (an icon before a name) | Select with only `Select.TriggerIcon` inside `Input.Wrapper`, named by `aria-label` |
|
|
49
52
|
| several of many, shown as tags, with search / creation | TagSelect (`Select.Root multiple` when tags are not needed) |
|
|
50
53
|
| narrowing a list or table by several fields, with search and show / hide | SmartFilter |
|
|
51
54
|
| an approximate number in a range | Slider (exact number → Input) |
|
|
52
55
|
| a date or a period | Datepicker |
|
|
56
|
+
| a time of day without a date | Input with `Input.Field type="time"` (a date with a time → Datepicker `withTime`) |
|
|
53
57
|
| a color from a fixed palette in a form or dialog | ColorSwatches (inline grid, default choice) |
|
|
54
|
-
| a compact color next to a field or in a list row | ColorPresets (trigger + popover palette) |
|
|
58
|
+
| a compact color next to a field or in a list row | ColorPresets (trigger + popover palette; `allowCustom` when any hex is valid too) |
|
|
55
59
|
| any color as a field of a form (swatch + hex, picker in a popover) | ColorField |
|
|
56
60
|
| any color in a panel of its own (hex, eyedropper, channels) | ColorPicker |
|
|
57
61
|
|
|
58
62
|
**Data.** Status, count, removable value or applied filter → Badge (`tone` for a status, `color` for a
|
|
59
|
-
category, `onRemove` to remove, `pressed` / `onPressedChange` to toggle)
|
|
63
|
+
category, `onRemove` to remove, `pressed` / `onPressedChange` to toggle); several badges as one value
|
|
64
|
+
(a client's tags) → `Badge.Group`. Person or organization → Avatar. Object (product, vehicle, file, cover) → Thumbnail with a
|
|
60
65
|
`ratio` — never a round Avatar for things. Keyboard shortcut → Kbd. One block of related
|
|
61
66
|
numbers/content → Card (pick the template; a whole tile that opens its details → `Card.Root asChild`
|
|
62
67
|
with a link). Read-only properties of one record («label — value») → DescriptionList, never a
|
|
63
|
-
hand-made grid of text pairs. Rows with columns, sorting, selection → DataTable
|
|
68
|
+
hand-made grid of text pairs. Rows with columns, sorting, selection → DataTable (a header that
|
|
69
|
+
needs explaining → the column's `description`). A KPI tile waiting for its figure →
|
|
70
|
+
`Card.Root loading`, never Skeletons inside its parts.
|
|
64
71
|
Items that flow through statuses and are moved by hand → Kanban (a single list to reorder, or items
|
|
65
72
|
dropped onto folders or assignees → Dnd directly). Its look: `plain` (default; `line` for a quieter header over a line) when the board is
|
|
66
73
|
the page's main region or sits in a Card / Drawer — headers and cards stand on the host, one level of
|
|
@@ -69,7 +76,7 @@ columns, a dense board of narrow columns). Never your own box around a column.
|
|
|
69
76
|
Records with a start and an end compared across time — who is busy when, a project plan with
|
|
70
77
|
phases and milestones → Gantt (never a DataTable of dates or hand-drawn bars); moved and resized by
|
|
71
78
|
hand through `onBarChange`.
|
|
72
|
-
Chronological events → Timeline; a conversation between people (support, a team chat) → Message —
|
|
79
|
+
Chronological events → Timeline (two facts under an event → two `Timeline.ItemMeta`, they stack); a conversation between people (support, a team chat) → Message —
|
|
73
80
|
never a Timeline or a list of Cards styled as bubbles. Code → CodeBlock. A number with its trend over days or weeks (revenue, orders, sign-ups) → Sparkline: the latest value, its change and a line you can scrub; values with a sign by period → Sparkline `variant="bars"`; against a plan → `reference`; missing days → `value: null`; a chart with axes or several series → ChartFrame around a chart library or your own SVG (the panel, legend, tooltip, states and the screen-reader table come from the kit, the colors from `series` / `chartColors` — never status tones); values to read, sort or compare row by row → DataTable, not a chart.
|
|
74
81
|
|
|
75
82
|
**Messages.** A whole conversation → `Chat`: `Chat.Root` with `Chat.Header` (avatar, `Chat.Title`,
|
|
@@ -92,7 +99,11 @@ comment, a report) → `Gallery`; one picture of an object → `Thumbnail`. A vi
|
|
|
92
99
|
`Message.Media` or `Thumbnail.Video`, not VideoNote. «Печатает…» → `Chat.Root typing` (or
|
|
93
100
|
`Message.Typing`), never a text row; a jump to a message → `Message.Reply target`, `Chat.Pinned` or
|
|
94
101
|
`Chat.Root jumpTo`, never `scrollIntoView`; search inside a chat → `Chat.Search`. Files, photos,
|
|
95
|
-
places and contacts sent in a chat → the `Message` parts, not FileUpload rows or Cards
|
|
102
|
+
places and contacts sent in a chat → the `Message` parts, not FileUpload rows or Cards; a sticker →
|
|
103
|
+
`Message.Sticker` (a Lottie or video sticker is your player inside it). A message shown outside a
|
|
104
|
+
chat (a bot's welcome message in its settings) → `Message.Root preview`; its formatted text alone →
|
|
105
|
+
`RichText`, never `dangerouslySetInnerHTML`. Times and date separators in a profile's zone →
|
|
106
|
+
`timeZone` on `Chat.Messages`, `ChatList.Root` and a lone `Message.Root`.
|
|
96
107
|
|
|
97
108
|
**Status and loading.** See the pair below. Progress of a single task → ProgressBar `value`; parts of a whole →
|
|
98
109
|
ProgressBar `segments` (categories wear `color`, meanings wear `tone`; `legend` for color · name ·
|
|
@@ -111,7 +122,8 @@ loading, data, empty and error, or showing one record at a time → its content
|
|
|
111
122
|
**Overlays.** See the pairs below. Global search over commands and pages (⌘K) → CommandMenu. Menus,
|
|
112
123
|
selects, pickers and Modal turn into bottom sheets below 640px by themselves — never build your own.
|
|
113
124
|
A photo, a scan or a video opened full screen from its thumbnail (a Gallery tile, a Thumbnail button)
|
|
114
|
-
→ MediaViewer, never a Modal with an `<img>`.
|
|
125
|
+
→ MediaViewer, never a Modal with an `<img>`. A titled block inside a Drawer or a Modal (a client's
|
|
126
|
+
contacts, its deals) → `Drawer.Section` / `Modal.Section`.
|
|
115
127
|
|
|
116
128
|
**Phone navigation.** App navigation is Sidebar on a wide screen and `BottomNav` in `AppShell.Footer`
|
|
117
129
|
on a phone for 2–6 main sections (the shell hides it by itself while the Sidebar rail is on screen;
|
|
@@ -129,8 +141,9 @@ active filters (`Page.Chips`) — [the top bar](composition.md#the-top-bar). A d
|
|
|
129
141
|
page (settings, a long form, help) or an overview has its large title in the flow (`Page.Header`
|
|
130
142
|
outside the bar), with an optional `Page.TopBar collapse` that repeats it once it scrolls away. A record opened from a table: its own page → DataTable `getRowHref`; in place →
|
|
131
143
|
`onRowClick` + Drawer; a list worked through record by record → `Page.Split` with a `List` of
|
|
132
|
-
the records (tickets, letters, invoices; `current` on the open one
|
|
133
|
-
|
|
144
|
+
the records (tickets, letters, invoices; `current` on the open one; a row's own Switch or menu in
|
|
145
|
+
`List.ItemActions`) or a `ChatList` of the conversations. A record edited on a page of its own →
|
|
146
|
+
`Page.Guard` holds leaving it with unsaved changes. Sections of a long rail → `Sidebar.Group collapsible`; a section with sub-pages →
|
|
134
147
|
`Sidebar.Sub`; the signed-in user → `Sidebar.Account` in a `Dropdown.Trigger` at the bottom of
|
|
135
148
|
`Sidebar.Footer`.
|
|
136
149
|
|
|
@@ -146,7 +159,7 @@ conversations. Sections of a long rail → `Sidebar.Group collapsible`; a sectio
|
|
|
146
159
|
| Select / Dropdown / CommandMenu / TagSelect | Select picks a **value** for a field (form data). Dropdown runs **actions** (Edit, Duplicate, Delete). CommandMenu searches commands and pages across the app (locally or on the server) — it is not a field. TagSelect picks **several values** as tags. |
|
|
147
160
|
| SmartFilter / SegmentedControl / Select | Search and filters over a list → SmartFilter (`fields={[]}` for a search alone), even with one field. A switch of what the same data shows (sections with counts, a period, a view) → SegmentedControl, or a Select when the options are many. |
|
|
148
161
|
| Badge / Button | A badge labels or holds a value (status, filter, keyword): `onRemove` drops it, `pressed` toggles it. A command (save, open, go) is always a Button. |
|
|
149
|
-
| Banner / Notification / Hint | Banner: persistent message in the page flow about a page/section state (sync failed, trial ends). Notification: transient toast about the result of an action (saved, failed). Hint / `error`: help or validation text under one field. |
|
|
162
|
+
| Banner / Notification / Hint | Banner: persistent message in the page flow about a page/section state (sync failed, trial ends). Notification: transient toast about the result of an action (saved, failed). Hint / `error`: help or validation text under one field; a status of its value (a deadline that nears) → `hintTone` on the field, `tone` on a lone Hint. |
|
|
150
163
|
| ProgressBar / ProgressCircle / Spinner | ProgressBar: a share with width to spare and words beside it (an upload row, an import, a quota line). ProgressCircle: a share on the data scale (24–80) where width is short or a ring reads better (a KPI goal, a ring breakdown, a named import card); `value={null}` while the share is unknown, `spin` while a share grows, an icon in the centre. Spinner: an anonymous «busy» on the icon scale inside a control or a status line, no share, no name of its own. A ring you can press (cancel, retry, download a transfer) → Button `shape="circle"` with `progress`, not any of the three. |
|
|
151
164
|
| Button / LinkButton | Button does something (submit, open, delete). LinkButton goes somewhere (a URL, «Подробнее», «Все заказы»). |
|
|
152
165
|
| Checkbox / Switch / Radio | Checkbox: choice submitted later with a button, can be multiple. Switch: on/off that applies at once (no Save button). Radio: one of several, submitted later. A privacy toggle inside a create form that is submitted → Checkbox. |
|
|
@@ -163,7 +176,7 @@ conversations. Sections of a long rail → `Sidebar.Group collapsible`; a sectio
|
|
|
163
176
|
| Gantt / Timeline / Kanban / DataTable | Gantt: records with a start and an end compared across time (who is busy when, a plan with phases and milestones), moved, stretched and drawn by hand (`onBarChange`, `onBarCreate`). Timeline: a history of events at points in time, read top to bottom. Kanban: work items moved between statuses, time does not matter. DataTable: many records compared by many attributes, sorted, filtered and selected — dates are only columns. |
|
|
164
177
|
| DescriptionList / DataTable / Card | DescriptionList: the properties of **one** record as «label — value» pairs (requisites, deal terms, a summary before a confirm). DataTable: **many** records with the same properties. Card: the bounded block they sit in, or one headline number (`mini`, `stat-trend`) — a DescriptionList inside a `Card.Body`, never a Card per property. |
|
|
165
178
|
| ConfirmDialog / Modal / `holdToConfirm` / undo | Which of them an action deserves is its cost: [ux.md → friction](ux.md#5-friction-matches-the-cost-of-a-mistake). ConfirmDialog only asks a question (`onConfirm` returns the request; pending, failure and dismiss rules are built in); a form, a choice or more than a sentence of content → Modal; never a Modal hand-built as a confirm. |
|
|
166
|
-
| Composer / Textarea | Composer: the message input of a conversation — Enter sends, attachments, a reply or edit bar, voice and round video. Textarea: long text saved with a form (a description, a comment field) by its submit button. |
|
|
179
|
+
| Composer / Textarea / RichText | Composer: the message input of a conversation — Enter sends, attachments, a reply or edit bar, voice and round video. Textarea: long text saved with a form (a description, a comment field) by its submit button; `formatting` when the text keeps the chat's Markdown. RichText: that Markdown shown, read-only, outside a message (a template, a bot's welcome text). |
|
|
167
180
|
| Chat / Message / Gallery | Chat: a conversation — the header, search, pinned, the one message list (dates, runs, selection, jumps, read receipts, typing) and the Composer in its footer; a list without the rest is `Chat.Root` with `Chat.Messages`. Message: one message and its content parts — a row of that list, or a bubble on its own (a quoted reply in a ticket). Gallery: an album of photos and videos laid out by their ratios — `Message.Media` in a message, `Gallery` anywhere else (a post, a comment, a report). |
|
|
168
181
|
| Message / Timeline / Notification | Message: what people say to each other in a conversation, in bubbles. Timeline: what happened to a record (status changes, an audit log). Notification: the result of the user's own action, for a few seconds. A chat event («Анна добавила Олега») is `Chat.Service`, not a Notification. |
|
|
169
182
|
| FileUpload list / grid / gallery / strip | One component, four shapes. **List** (`layout="list"`, the default, with the drop zone): documents of a form, where the name matters; rows do not reorder. **Cards** (`trigger="tile"` `layout="grid"`): mixed photos and documents whose order matters (an inspection report). **Gallery** (`trigger="tile"` `layout="grid"` `cover` `accept="image/*"`): the photos of one object (a listing, a product); the first is the cover and dragging a photo first makes it the cover — no «make cover» button. **Strip** (`trigger="button"` + `FileUpload.Queue layout="strip"`): a paperclip and one scrolling row of tiles above a message field; the Composer already is this. Files on a server are `value` + `onValueChange` (return a promise to save); uploads are `queue` from `useUploadQueue`. One picture of a person is the avatar zone, not a gallery. |
|
package/dist/skill/components.md
CHANGED
|
@@ -78,6 +78,7 @@ Show data: labels, people, objects, cards, tables, boards, feeds, code, disclose
|
|
|
78
78
|
| Gantt | A resource or project schedule: rows of people, rooms, vehicles or tasks with bars on a day / week / month axis, edited by pointer and keyboard. | [COMPONENT.md](reference/components/gantt/COMPONENT.md) · [examples](reference/components/gantt/examples/) |
|
|
79
79
|
| Timeline | An event feed: dots on a thin line, event title and date, an optional amount on the right, grouped under labels. | [COMPONENT.md](reference/components/timeline/COMPONENT.md) · [examples](reference/components/timeline/examples/) |
|
|
80
80
|
| CodeBlock | A static TypeScript / TSX snippet with syntax highlighting, on a filled panel or bare inside a host. | [COMPONENT.md](reference/components/code-block/COMPONENT.md) · [examples](reference/components/code-block/examples/) |
|
|
81
|
+
| RichText | Text in the chat's Markdown subset outside a message — a bot's welcome text, a template, a comment — rendered as elements, never as HTML. | [COMPONENT.md](reference/components/rich-text/COMPONENT.md) · [examples](reference/components/rich-text/examples/) |
|
|
81
82
|
| Accordion | Collapsible sections: FAQ, settings groups, checkout steps. | [COMPONENT.md](reference/components/accordion/COMPONENT.md) · [examples](reference/components/accordion/examples/) |
|
|
82
83
|
| Sparkline | A small chart — a smooth line over a fading area, a bare line or bars — with its headline: the value at rest, its change and its date; any point can be scrubbed. | [COMPONENT.md](reference/components/sparkline/COMPONENT.md) · [examples](reference/components/sparkline/examples/) |
|
|
83
84
|
| ChartFrame | A dashboard panel around any chart — Recharts, ECharts, visx, Chart.js or a hand-drawn SVG — with the title, a headline value, a measured plot area, a series legend, a tooltip body and a data table for screen readers. | [COMPONENT.md](reference/components/chart-frame/COMPONENT.md) · [examples](reference/components/chart-frame/examples/) |
|
|
@@ -15,9 +15,9 @@ playground page. What each screen teaches is in [Screen rules](#screen-rules) at
|
|
|
15
15
|
|---|---|---|---|
|
|
16
16
|
| [List page](#list-page) | data-table | [screen.tsx](reference/components/data-table/examples/screen.tsx) | the title, a line of figures, SmartFilter search and filters, export and one primary action in the page's top bar; statuses, row menu, pages, filtered empty state |
|
|
17
17
|
| [Detail page](#detail-page) | description-list | [screen.tsx](reference/components/description-list/examples/screen.tsx) | a nested record: the path ending in the title, status and due date and the record's actions by weight in the top bar; main column + side column of Cards, Timeline |
|
|
18
|
-
| [Settings page](#settings-page) | page | [screen.tsx](reference/layout/page/examples/screen.tsx) | the path in a collapsing top bar, the large title in the flow,
|
|
18
|
+
| [Settings page](#settings-page) | page | [screen.tsx](reference/layout/page/examples/screen.tsx) | the path in a collapsing top bar, the large title in the flow, a card with its own header per section, form with its own save, Switches that apply at once, danger zone with a confirm |
|
|
19
19
|
| [Form in a Drawer](#form-in-a-drawer) | drawer | [screen.tsx](reference/components/drawer/examples/screen.tsx) | the title, counts, search and the Drawer trigger in the top bar; Drawer form: groups, errors on submit, focus on the first invalid field, loading, toast |
|
|
20
|
-
| [Record editor](#record-editor) | field-list | [screen.tsx](reference/components/field-list/examples/screen.tsx) | a document page (a long form): the title and a line of description in `Page.Heading`, «Сохранить тариф» in `Page.Actions`, both in a `Page.Header` in the flow; a plan card edited in
|
|
20
|
+
| [Record editor](#record-editor) | field-list | [screen.tsx](reference/components/field-list/examples/screen.tsx) | a document page (a long form): the title and a line of description in `Page.Heading`, «Сохранить тариф» in `Page.Actions`, both in a `Page.Header` in the flow; a plan card edited in card sections: name and description, specs in a `FieldList` (icon-only `Select` and `ColorPresets` in compact columns, pinned rows, reorder), the price and its changes by month in one list with the base row pinned, a setup fee in three currencies in one row under one heading, save with «Сохранено» on the button |
|
|
21
21
|
| [Dashboard](#dashboard) | card | [screen.tsx](reference/components/card/examples/screen.tsx) | the large title + a line of data in the flow, period switch and report in a collapsing `Page.TopBar`; KPI row that cross-fades to the new period, a Sparkline revenue trend, invoiced vs paid in a ChartFrame, panels with progress and a split, short table + link to the list |
|
|
22
22
|
| [Screen states](#screen-states) | crossfade | [screen.tsx](reference/components/crossfade/examples/screen.tsx) | title + the statement date in `Page.TopBar`, page error in a Banner with retry, region loading with a Skeleton, Crossfade between states, table error in place, first-run EmptyPage |
|
|
23
23
|
| [Messenger](#messenger) | chat | [screen.tsx](reference/components/chat/examples/screen.tsx) | title + the unread count in `Page.TopBar`; a full-height Page.Split of a `ChatList` with its search (live last message, ticks, unread counts, «печатает…») and a `Chat`: `Chat.Header` with the back button, avatar, title, the status that turns into «печатает…» (`typing`) and the search button, `Chat.Search`, `Chat.Messages` from `items` + `renderMessage` (dates, runs and the unread band from the data, history loaded above with `hasOlder` / `onLoadOlder`, a first load in skeletons), text, a photo, a file and sent voice, reply and edit from the menu through `Composer.Context messageId` (`replyTo` / `editOf`), the jump to the quoted message (`Message.Reply target`), optimistic sends whose messages rise from the composer while the client's answer slides in, «печатает…» both ways (`onActivityChange` → `Chat.Root typing`), unread counts from `onNewMessage` and `onRead`, the Composer with attachments in `Chat.Footer` |
|
|
@@ -57,9 +57,10 @@ Never two titles; never no `<h1>`. A bar whose page shows no title still holds a
|
|
|
57
57
|
```
|
|
58
58
|
AppShell.Root once per app (layouts.md#app-frame-once-per-app)
|
|
59
59
|
├─ AppShell.Nav → Sidebar navigation rail, one layer above the page
|
|
60
|
-
└─ AppShell.Main gutters
|
|
60
|
+
└─ AppShell.Main gutters, top air (24, 32 from 1024px), bottom air 64; the page renders here
|
|
61
61
|
└─ SmartFilter.Root a filtered page only: the filter's state, no DOM, around the page
|
|
62
62
|
└─ Page.Root the page you deliver
|
|
63
|
+
├─ Page.Guard a page that edits a record: holds leaving it (busy, dirty); no DOM
|
|
63
64
|
├─ Page.TopBar sticky; led by the menu button while the Sidebar is off-canvas
|
|
64
65
|
│ ├─ Page.Heading the path › the title on one line, the line of data under it
|
|
65
66
|
│ │ ├─ Breadcrumb.Root only on a nested record: the levels above this page
|
|
@@ -68,7 +69,7 @@ AppShell.Root once per app (layouts.md#app-frame-once-per-a
|
|
|
68
69
|
│ ├─ Page.Tools sections, search and filter, view — kit controls in reading order
|
|
69
70
|
│ ├─ Page.Actions every button of the page, each with an icon; the primary last
|
|
70
71
|
│ └─ Page.Chips SmartFilter.Chips: a line under everything, gone while empty
|
|
71
|
-
└─ Page.Body blocks 24 apart,
|
|
72
|
+
└─ Page.Body blocks 24 apart, the first 24 under the bar's controls
|
|
72
73
|
├─ Page.Grid every tile of the screen, 16 on both axes
|
|
73
74
|
│ └─ Page.Cell span (of 12), rows; the tile fills it
|
|
74
75
|
└─ Page.Section a new topic: h2 label (+ Page.Actions), 40 from its neighbours
|
|
@@ -84,7 +85,7 @@ Document page or overview: the title in the flow
|
|
|
84
85
|
`NotificationProvider` and `AppShell` live at the app root.
|
|
85
86
|
- `Page.Root` for app pages (full panel width); `Page.Root maxWidth="wide"` for settings,
|
|
86
87
|
`maxWidth="readable"` for one narrow column of text or fields.
|
|
87
|
-
- No outer padding, no margins: `AppShell.Main` hands the page its gutters and top air, `Page` has
|
|
88
|
+
- No outer padding, no margins: `AppShell.Main` hands the page its gutters and its top and bottom air, `Page` has
|
|
88
89
|
block → block 24, section → section 40, tile → tile 16 (and header → body 24). Your CSS lays out
|
|
89
90
|
only the inside of a block.
|
|
90
91
|
|
|
@@ -190,11 +191,12 @@ Every gap is a `--prime-space-N` token (N × 4px) set as `gap` on the parent. Cl
|
|
|
190
191
|
| tile → tile, both axes (KPI, charts, panels) | 16 | `Page.Grid` |
|
|
191
192
|
| field → field, switch → switch | 20 | `--prime-space-5` |
|
|
192
193
|
| main column → side column | 24 | `--prime-space-6` |
|
|
193
|
-
| top bar → the page's first block |
|
|
194
|
+
| top bar → the page's first block | 24 under the bar's controls | the rhythm's header → body; the bar's own bottom padding is part of it |
|
|
194
195
|
| flow header → body (a document page) | 24 | `Page.Header` |
|
|
195
196
|
| block → block on a page | 24 | `Page.Body` |
|
|
196
197
|
| group → group in a form | 32 | `--prime-space-8` |
|
|
197
198
|
| section → section (a new topic with an `h2`) | 40 | `Page.Section` |
|
|
199
|
+
| the page's last block → the panel's bottom edge | 64; a `Page.Root fill` page the side gutter | `AppShell.Main` (beside the Sidebar rail too) |
|
|
198
200
|
|
|
199
201
|
A screen with one gap everywhere reads as a list of unrelated things. Use at least three levels: inside
|
|
200
202
|
an item, between items, between blocks. The tiles of one screen are one board: put them all in one
|
|
@@ -211,6 +213,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
211
213
|
| section heading without a card | `Page.Section label` (an `h2` in `title-m`) |
|
|
212
214
|
| card / panel title | `Card.Title as="h2"` (under the page title) |
|
|
213
215
|
| heading of a group of fields | `Fieldset label` (`title-s` in the `<legend>`) |
|
|
216
|
+
| section heading inside a Drawer or a Modal | `Drawer.Section label` / `Modal.Section label` (an `h3` in `title-s`, `Drawer.Actions` at its end) |
|
|
214
217
|
| other group heading inside a card | `Typography as="h3" variant="title-s"` |
|
|
215
218
|
| body text, cells | `body-m` |
|
|
216
219
|
| lead under a heading | `body-m` + `tone="secondary"` |
|
|
@@ -231,6 +234,39 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
231
234
|
- **Card when** the block stands alone and has its own title: a KPI tile, a settings panel, facts in a
|
|
232
235
|
side column, a chart. **No card when** the block is the only thing in its region: a section heading
|
|
233
236
|
and the content are enough.
|
|
237
|
+
- **A titled block stands in the flow, its heading inside it.** A panel with a title and a line of
|
|
238
|
+
description (a settings section, a group of a long form) is a `Card.Root` whose `Card.Header`
|
|
239
|
+
holds `Card.Title as="h2"` and `Card.Description`; the cards stack 24 apart. Never a heading column
|
|
240
|
+
beside its panel: it takes a third of the width from the fields and breaks the reading line.
|
|
241
|
+
|
|
242
|
+
```tsx partial
|
|
243
|
+
// bad — the heading in a column on the left, the panel on the right
|
|
244
|
+
<div className={styles.headingColumnAndPanel}>
|
|
245
|
+
<div>
|
|
246
|
+
<Typography as="h2" variant="title-m">Компания</Typography>
|
|
247
|
+
<Typography tone="secondary">Попадают в счета и акты.</Typography>
|
|
248
|
+
</div>
|
|
249
|
+
<Card.Root variant="panel">…</Card.Root>
|
|
250
|
+
</div>
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```tsx good
|
|
254
|
+
// good — the card carries its own heading, stacked in the flow
|
|
255
|
+
<Card.Root variant="panel">
|
|
256
|
+
<Card.Header>
|
|
257
|
+
<Card.Title as="h2">Компания</Card.Title>
|
|
258
|
+
<Card.Description>Попадают в счета и акты.</Card.Description>
|
|
259
|
+
</Card.Header>
|
|
260
|
+
<Card.Body>
|
|
261
|
+
<Input.Root label="Название">
|
|
262
|
+
<Input.Wrapper>
|
|
263
|
+
<Input.Field name="name" />
|
|
264
|
+
</Input.Wrapper>
|
|
265
|
+
</Input.Root>
|
|
266
|
+
</Card.Body>
|
|
267
|
+
</Card.Root>
|
|
268
|
+
```
|
|
269
|
+
|
|
234
270
|
- `DataTable` is already a filled block — never inside a Card; a card in a card only as
|
|
235
271
|
[the next layer, sparingly](foundations.md#card-in-a-card). No borders around blocks; `Divider`
|
|
236
272
|
only inside a block (list rows, a form and its footer).
|
|
@@ -254,6 +290,9 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
254
290
|
table cell.
|
|
255
291
|
- Placement: every button of the page in one `Page.Actions` — of `Page.TopBar` on a work screen, of the flow `Page.Header` on a document page — the primary last; card actions in `Card.Footer`; dialog actions in
|
|
256
292
|
`Modal.Footer` / `Drawer.Footer`; row actions in the last column; bulk actions in the table toolbar.
|
|
293
|
+
- A dialog footer is the dialog's answer: Cancel and the primary. Deleting the record the dialog
|
|
294
|
+
edits is not a third footer button: it is the last `tone="danger"` item of a ⋯ `Dropdown` in
|
|
295
|
+
`Modal.Actions` / `Drawer.Actions`, confirmed by a `ConfirmDialog`.
|
|
257
296
|
- An empty state's action replaces the header's primary, it does not duplicate it.
|
|
258
297
|
- Destructive: `tone="danger"`; a standalone trigger is `outline`, in a menu it is the last item, the
|
|
259
298
|
confirm is a `ConfirmDialog` (its `onConfirm` returns the request's Promise).
|
|
@@ -265,11 +304,18 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
265
304
|
- Labels above every field (`label`), placeholder is an example value, help in `hint`, problems in
|
|
266
305
|
`error` — after blur or on submit. Mark the minority (`required` or `optional`).
|
|
267
306
|
- Records of one shape that a person adds, edits and removes are a `FieldList`: one row per record,
|
|
268
|
-
the column headings once above the rows, small attributes (an icon,
|
|
307
|
+
the column headings once above the rows, small attributes (an icon, then its color) in `compact`
|
|
269
308
|
columns. Never a label in every row, never numbered labels, never one record split over lines.
|
|
309
|
+
FieldList draws every row's actions itself — the remove inside the field of a one-column list, a
|
|
310
|
+
pinned row's remove off with its reason (`labels.lockedReason`) — so never add your own.
|
|
270
311
|
- One value in several units is one row of fields under one heading, each unit in its field's
|
|
271
312
|
`Input.InlineAffix`; a base value with overrides is one `FieldList` with the base pinned first.
|
|
272
313
|
The shape of the data decides the layout: [ux.md](ux.md#2-the-shape-of-the-data-decides-the-layout).
|
|
314
|
+
- Rows that add up (shares of a bonus, hours, amounts) carry their sums under them in the same list:
|
|
315
|
+
`FieldList renderTotals`, a short number in a `width: "xs"` column. A number with its unit
|
|
316
|
+
(`inputMode="decimal"` and an end `Input.InlineAffix`) ends at the field's end, and its column's
|
|
317
|
+
total follows it there by itself. Return the save's promise from
|
|
318
|
+
its `onValueChange` when each edit saves at once.
|
|
273
319
|
- Fields 20 apart; related short fields side by side in an
|
|
274
320
|
[intrinsic grid](responsive.md#intrinsic-grids) with `align-items: start`
|
|
275
321
|
(`reserveSupportRow` on Input / Textarea keeps neighbours aligned); groups 32 apart, each a
|
|
@@ -294,9 +340,18 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
294
340
|
`SmartFilter.Toolbar` in the bar's `Page.Tools`, `SmartFilter.Chips` in its `Page.Chips`
|
|
295
341
|
([the list page](#list-page)).
|
|
296
342
|
A search alone is the same SmartFilter with `fields={[]}`.
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
343
|
+
|
|
344
|
+
### Controls of a table
|
|
345
|
+
|
|
346
|
+
- The column chooser is part of the table: `columnsMenu` puts it at the end of the header row — in
|
|
347
|
+
the head cell of a trailing row-actions column (`hideHeader`), else in a narrow cell of its own —
|
|
348
|
+
and it sticks with `stickyLastColumn`. Give the key column and the actions column `hideable: false`.
|
|
349
|
+
- The DataTable `toolbar` holds several controls of the table itself: a bulk bar of the selected
|
|
350
|
+
rows, or the search and filters of a table that is not the page's main region (a table in a
|
|
351
|
+
section of a record, in a Drawer).
|
|
352
|
+
- One control never gets a bar of its own. A lone «Экспорт» or «Создать» is the page's
|
|
353
|
+
(`Page.Actions`); the period, the view switch, the search and filters of the page go to
|
|
354
|
+
`Page.Tools`; a count or a total of the list is data in `Page.Description`.
|
|
300
355
|
|
|
301
356
|
### Tables
|
|
302
357
|
|
|
@@ -315,6 +370,15 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
315
370
|
- Phones: the table scrolls inside itself, `stickyFirstColumn` keeps the key column; never let the page
|
|
316
371
|
scroll sideways. Short lists of objects can be `Card variant="list"` instead of a table.
|
|
317
372
|
|
|
373
|
+
### List rows
|
|
374
|
+
|
|
375
|
+
- A row's own controls stand beside its element in `List.ItemActions`, one tier below the row.
|
|
376
|
+
- A plain settings row reads label first and ends with its toggle (the platform convention).
|
|
377
|
+
- A row people reorder leads with its toggle — `<List.ItemActions side="start">`, or the Switch as the
|
|
378
|
+
first child of a `Dnd.SortableItem` — so the on / off state reads first, like a checkbox, and the
|
|
379
|
+
grip keeps the end edge ([reorder.tsx](reference/components/list/examples/reorder.tsx)).
|
|
380
|
+
- Never a toggle between the label and the grip: «Почта … [Switch] [grip]» reads as two handles.
|
|
381
|
+
|
|
318
382
|
## 9. Feedback
|
|
319
383
|
|
|
320
384
|
| Situation | Component |
|
|
@@ -323,6 +387,7 @@ Hierarchy comes from space and a handful of type roles, not from size jumps, col
|
|
|
323
387
|
| a form saved and still on screen | the submit button's text: «Сохранено» |
|
|
324
388
|
| a state of the page or a section that persists (sync failed, trial ends, read-only) | `Banner` as the first block of `Page.Body` (or inside the Card it concerns) |
|
|
325
389
|
| a problem with one field | the field's `error` |
|
|
390
|
+
| a refused sign-in | `invalid` on the fields it concerns and the reason in an always-mounted `VisuallyHidden live="assertive"` — no Banner on a sign-in or sign-up form ([LoginForm](reference/components/login-form/COMPONENT.md)) |
|
|
326
391
|
| a region failed to load | the region shows it in place: DataTable `loadError`, or `EmptyPage tone="danger"` with `EmptyPage.RetryButton` |
|
|
327
392
|
| a region is loading | DataTable `loading`; otherwise a `Skeleton` in the geometry of the content + `aria-busy` on the region; `Spinner` only when there is no shape to hold |
|
|
328
393
|
| a region changes state (loading → data → empty → error, another record) | its content in `Crossfade state={status}` — always, so the change flows instead of flipping |
|
|
@@ -353,6 +418,12 @@ Every overlay closes on Escape and on a press outside unless you turn it off
|
|
|
353
418
|
closing with `Modal.Guard` / `Drawer.Guard` (`busy` while a request runs, `dirty` for unsaved
|
|
354
419
|
changes) instead.
|
|
355
420
|
|
|
421
|
+
A toast is no layer of that stack: `notify()` from inside a Drawer leaves it open, and a press on
|
|
422
|
+
the toast closes nothing below it. Titled groups of a dialog's body (a client's contacts, its deals)
|
|
423
|
+
are `Drawer.Section` / `Modal.Section` with their own `Drawer.Actions` / `Modal.Actions`, not a
|
|
424
|
+
hand-made heading row; a footer's `Drawer.Meta` / `Modal.Meta` goes above the buttons by itself when
|
|
425
|
+
the footer runs short.
|
|
426
|
+
|
|
356
427
|
## 11. Narrow screens
|
|
357
428
|
|
|
358
429
|
- Layouts are [intrinsic](responsive.md#intrinsic-grids) — no breakpoints needed, and they work in
|
|
@@ -445,7 +516,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
445
516
|
- `SmartFilter.Root` wraps the page: `SmartFilter.Toolbar` in `Page.Tools`, `SmartFilter.Chips` in `Page.Chips`; export (`soft` + `neutral`, an icon beside its label) and the one primary action (`solid`, last) in `Page.Actions`. The table itself has no `toolbar`.
|
|
446
517
|
- A status is a `Badge` with one `tone` per meaning across the product (`STATUS` is one map); the text carries the meaning, the tone repeats it.
|
|
447
518
|
- Numbers and dates are `numeric` columns with `headerAlign="end"` (tabular figures, values and heading on the end edge). On a phone the table scrolls inside itself and `stickyFirstColumn` keeps the number in place — the page never scrolls sideways.
|
|
448
|
-
- Row actions are a `Dropdown` behind an `action.more` icon of size `s
|
|
519
|
+
- Row actions are a `Dropdown` behind an `action.more` icon of size `s` in the last column (`hideHeader`), pinned to the end edge with `stickyLastColumn` so they stay in reach while the columns scroll: controls in cells are one tier below the table. Delete is `tone="danger"` at the end of the menu.
|
|
449
520
|
- Empty after a filter is the table's `empty` with `EmptyPage size="s"` and a «Сбросить фильтры» action; the header and the filters stay in place.
|
|
450
521
|
|
|
451
522
|
### Detail page
|
|
@@ -465,7 +536,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
465
536
|
[page/examples/screen.tsx](reference/layout/page/examples/screen.tsx)
|
|
466
537
|
|
|
467
538
|
- A document page: `Page.Root maxWidth="wide"`, the large `Page.Title` with a sentence of `Page.Description` in the `Page.Heading` of a `Page.Header` in the flow, and a `Page.TopBar collapse` whose `Page.Heading` holds only the path and repeats the title once it scrolls away.
|
|
468
|
-
- A section is `
|
|
539
|
+
- A section is a `Card variant="panel"` with its own `Card.Header` (`Card.Title as="h2"` + a line of `Card.Description`), the cards stacked in the flow ([a titled block](#4-surfaces)).
|
|
469
540
|
- The form wraps the whole `Card.Root` in `<form noValidate>`; the buttons are in `Card.Footer`: «Отменить изменения» `ghost` + `neutral`, then the primary «Сохранить». While the request runs — `loading` on the button, and one `Fieldset disabled` turns the fields off; the form stays on screen, so success is the button text «Сохранено» for two seconds, without a notification.
|
|
470
541
|
- Fields are 20 px apart; two short related fields side by side while there is width, with `align-items: start` and `reserveSupportRow`, so an error does not shift the neighbour.
|
|
471
542
|
- An error comes through the field's `error` after submit, not while typing; a format hint goes in `hint`.
|
|
@@ -489,10 +560,10 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
489
560
|
|
|
490
561
|
[field-list/examples/screen.tsx](reference/components/field-list/examples/screen.tsx)
|
|
491
562
|
|
|
492
|
-
- A record editor is a document page — a long form read top to bottom in
|
|
563
|
+
- A record editor is a document page — a long form read top to bottom in card sections, like Settings — so its title stands in the flow: `Page.Header` with the title and one line of description in `Page.Heading` and the primary action in `Page.Actions`; no second title anywhere.
|
|
493
564
|
- The shape of the data decides the layout: records a person adds and removes are a `FieldList`, one row per record; the column headings stand once above the rows, and the fields in the cells take them as their labels.
|
|
494
565
|
- No label in every row and no numbered one («Преимущество 1»): a one-column list has no headings at all — its section names it ([reorder.tsx](reference/components/field-list/examples/reorder.tsx)).
|
|
495
|
-
- Small attributes of a record go in compact columns of its row: the icon is a `Select` with only `Select.TriggerIcon`, the color `ColorPresets`; the first specs are pinned (`getLocked`) — the lock
|
|
566
|
+
- Small attributes of a record go in compact columns of its row, the color right after the icon it paints: the icon is a `Select` with only `Select.TriggerIcon`, the color `ColorPresets`; the first specs are pinned (`getLocked`) — the lock and the remove that is off say so themselves, without explanatory text.
|
|
496
567
|
- The base price and its changes are one list: the base row is pinned first and shows when it applies, the changes are rows with a month and a price; the ruble sign is in the field (`Input.InlineAffix`), not in every heading.
|
|
497
568
|
- One value in three currencies is three fields in one row under one group heading, each with its own name for screen readers and the currency inside the field.
|
|
498
569
|
- The lists live in the page state, the name and description fields in the `<form>` of the first section; the primary «Сохранить тариф» in `Page.Actions` is tied to it through `form={formId}` and saves everything. While the request runs the fields and the lists are off (`Fieldset disabled`, `disabled`), afterwards «Сохранено» on the button.
|
|
@@ -504,7 +575,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
504
575
|
- An overview: the large `Page.Title` «Обзор» with a `Page.Description` line of data (what and as of when) in a `Page.Header` in the flow; a `Page.TopBar collapse` above it keeps the page's tools and buttons in reach and repeats the title once it scrolls away; the body starts with the tiles.
|
|
505
576
|
- The period is a `SegmentedControl` in the bar's `Page.Tools` and the report a `soft` + `neutral` button with an icon in its `Page.Actions`: the period changes the values on this same screen. Tabs (`Tabs`) are for different content, not for a period.
|
|
506
577
|
- Every tile of the screen is one `Page.Grid`: KPI `Card variant="stat-trend"` in `Page.Cell span={3}`, 16 px on both axes; `Card.Delta` with a `tone` by the meaning of the change: overdue growing is `danger`, its decline `success`.
|
|
507
|
-
- Another period — other values: each KPI tile is wrapped in `Crossfade state={period}` and flows instead of blinking.
|
|
578
|
+
- Another period — other values: each KPI tile is wrapped in `Crossfade state={period}` and flows instead of blinking; a Crossfade right in a `Page.Cell` fills it, so the tiles of a row keep one height.
|
|
508
579
|
- A trend over days is a `Sparkline` in a `Card` in a full-width cell: the last value, the change and a line you can scrub with a finger; a new series appears by itself.
|
|
509
580
|
- A chart with axes and several series is a `ChartFrame` in `Page.Cell span={8} rows={2}` beside two panels; `ChartFrame.Plot height="fill"` takes their height. A sized plot area (`renderPlot`), a legend that turns series off and a table for screen readers; series colors come from `series`, never your own.
|
|
510
581
|
- Panels are `Card variant="panel"` with `Card.Title as="h2"` in `Card.Header` and quiet context on the right after the title (`caption` + `muted`).
|
|
@@ -519,6 +590,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
519
590
|
- The top bar carries the page: `Page.Title` with a `Page.Description` line of data — the accounts and the date of the statement the screen stands on; there is no second header in the flow.
|
|
520
591
|
- An error that concerns the whole page is a `Banner tone="danger"` as the first block of `Page.Body` with a «Повторить» action; the action's result is a `Notification`.
|
|
521
592
|
- A region's error is in the region's place: `loadError` on the `DataTable` (the table head stays), not a pop-up.
|
|
593
|
+
- The page's line of data that waits for a load (`Page.Description` with counts or sums) stays mounted with `loading` — mounting it after the load moves the title in the bar.
|
|
522
594
|
- Table loading is `loading` + `loadingRows` (a skeleton of rows); card loading is a `Skeleton` in the shape of its rows and `aria-busy` on the card, and the block's height does not change. The state change is smooth: the card's region is in `Crossfade state={status}`, the table body changes by itself.
|
|
523
595
|
- Stale data beats emptiness: the balances are shown as of the previous date, and that is said on the right in `Card.Header`.
|
|
524
596
|
- Empty on the first run is `EmptyPage size="s"` inside the panel: an icon with `tone="accent"`, what it is and why, one action. An empty filter result is the table's `empty`.
|
|
@@ -552,7 +624,7 @@ What each screen in [the index](#screens) demonstrates — keep these when you s
|
|
|
552
624
|
- The placement of deals is `value` as `{ [stage]: id[] }`; `onValueChange` returns the request's promise and applies `value` only after the answer; a server refusal returns the card to its place.
|
|
553
625
|
- Stage totals are in `renderColumnMeta` («2 сделки · 3 575 000 ₽»), the stage color is the column's `color`.
|
|
554
626
|
- A deal opens through `onItemOpen` of `Kanban.Root` (click, Enter, Space; never after a drag) in a `Drawer` with a `DescriptionList`; no `onClick` / `onKeyDown` of your own on the card; a deal with its own page — `getItemHref`. The stage change is available in the panel too — dragging is not the only way; `Drawer.Guard busy` holds the panel while saving.
|
|
555
|
-
- The first load is `loading` on `Kanban` and `DataTable`: skeletons in the geometry of cards and rows.
|
|
627
|
+
- The first load is `loading` on `Kanban` and `DataTable`: skeletons in the geometry of cards and rows. Stages that come from the server too: `loading` with empty `columns` draws `loadingColumns` placeholder columns — never a board skeleton of your own beside the `Kanban`.
|
|
556
628
|
|
|
557
629
|
### App frame
|
|
558
630
|
|