prime-ui-kit 0.21.2 → 0.21.3
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 -2
- package/THIRD_PARTY_NOTICES +13 -0
- package/dist/chunks/{chunk-VQ3UCVDV.js → chunk-BAJRN3QN.js} +2 -2
- package/dist/chunks/{chunk-T7IEJSTH.js → chunk-HMLQCKRL.js} +36 -0
- package/dist/chunks/{chunk-7JYPFRZ3.js → chunk-JO7E6SZQ.js} +29 -18
- package/dist/chunks/{chunk-ME4VEAQ5.js → chunk-R5LI5BO3.js} +2122 -1614
- package/dist/chunks/{chunk-SQGPQOUO.js → chunk-SUGCIPD7.js} +1 -1
- package/dist/color-picker.js +3 -3
- package/dist/components/chat-list/ChatList.d.ts +4 -3
- package/dist/components/command-menu/CommandMenu.d.ts +1 -4
- package/dist/components/composer/Composer.d.ts +1 -1
- package/dist/components/data-table/DataTable.d.ts +9 -7
- package/dist/components/data-table/Footer.d.ts +7 -3
- package/dist/components/datepicker/DatepickerPanel.d.ts +2 -2
- package/dist/components/dnd/labels.d.ts +2 -0
- package/dist/components/field-list/FieldList.d.ts +2 -4
- package/dist/components/file-upload/FileUpload.d.ts +1 -9
- package/dist/components/index.d.ts +2 -2
- package/dist/components/kanban/Kanban.d.ts +0 -2
- package/dist/components/list/List.d.ts +7 -1
- package/dist/components/message/Message.d.ts +5 -1
- package/dist/components/message/MessageParts.d.ts +7 -2
- package/dist/components/rich-text/RichText.d.ts +12 -4
- package/dist/components/select/selectContext.d.ts +0 -2
- package/dist/components/smart-filter/SmartFilter.d.ts +1 -5
- package/dist/components/sparkline/Sparkline.d.ts +50 -8
- package/dist/components/tag-select/TagSelect.d.ts +0 -2
- package/dist/components/textarea/Textarea.d.ts +1 -1
- package/dist/icon-set.d.ts +5 -1
- package/dist/icon-set.js +8 -2
- package/dist/icons/glyph.d.ts +12 -1
- package/dist/icons/glyphs/telegram.d.ts +2 -0
- package/dist/icons/glyphs/vk.d.ts +2 -0
- package/dist/icons/glyphs/whatsapp.d.ts +2 -0
- package/dist/icons/registry.d.ts +8 -1
- package/dist/index.css +1 -1
- package/dist/index.js +7 -3
- package/dist/internal/ToolRow.d.ts +3 -1
- package/dist/internal/barPlan.d.ts +7 -1
- package/dist/internal/blockStack.d.ts +7 -0
- package/dist/internal/overlay/DialogParts.d.ts +8 -6
- package/dist/internal/richText.d.ts +32 -1
- package/dist/internal/textFormatting.d.ts +2 -2
- package/dist/layout/page/Page.d.ts +2 -1
- package/dist/skill/SKILL.md +3 -2
- package/dist/skill/api-contract.md +2 -1
- package/dist/skill/cheatsheet.md +1 -1
- package/dist/skill/checklist.md +6 -1
- package/dist/skill/choosing.md +76 -5
- package/dist/skill/components.md +2 -2
- package/dist/skill/composition.md +108 -20
- package/dist/skill/foundations.md +1 -1
- package/dist/skill/layouts.md +4 -4
- package/dist/skill/reference/components/card/COMPONENT.md +1 -1
- package/dist/skill/reference/components/card/examples/screen.tsx +9 -11
- package/dist/skill/reference/components/chat-list/COMPONENT.md +6 -5
- package/dist/skill/reference/components/chat-list/examples/structure.tsx +4 -4
- package/dist/skill/reference/components/command-menu/COMPONENT.md +1 -2
- package/dist/skill/reference/components/command-menu/examples/states.tsx +1 -1
- package/dist/skill/reference/components/composer/COMPONENT.md +5 -4
- package/dist/skill/reference/components/composer/examples/formatting.tsx +1 -1
- package/dist/skill/reference/components/data-table/COMPONENT.md +17 -16
- package/dist/skill/reference/components/data-table/examples/server.tsx +2 -1
- package/dist/skill/reference/components/datepicker/COMPONENT.md +2 -2
- package/dist/skill/reference/components/dnd/COMPONENT.md +2 -1
- package/dist/skill/reference/components/drawer/COMPONENT.md +9 -6
- package/dist/skill/reference/components/drawer/examples/tab-switcher.tsx +158 -0
- package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
- package/dist/skill/reference/components/field-list/examples/limits.tsx +2 -2
- package/dist/skill/reference/components/file-upload/COMPONENT.md +8 -12
- package/dist/skill/reference/components/file-upload/examples/gallery.tsx +2 -2
- package/dist/skill/reference/components/file-upload/examples/in-form.tsx +2 -4
- package/dist/skill/reference/components/file-upload/examples/narrow.tsx +1 -0
- package/dist/skill/reference/components/file-upload/examples/overview.tsx +1 -5
- package/dist/skill/reference/components/file-upload/examples/sizes.tsx +1 -1
- package/dist/skill/reference/components/file-upload/examples/states.tsx +4 -5
- package/dist/skill/reference/components/file-upload/examples/validation.tsx +2 -7
- package/dist/skill/reference/components/kanban/COMPONENT.md +1 -2
- package/dist/skill/reference/components/list/COMPONENT.md +11 -6
- package/dist/skill/reference/components/list/examples/reorder.tsx +30 -3
- package/dist/skill/reference/components/message/COMPONENT.md +12 -7
- package/dist/skill/reference/components/message/examples/status.tsx +2 -1
- package/dist/skill/reference/components/message/examples/text-formatting.tsx +13 -3
- package/dist/skill/reference/components/modal/COMPONENT.md +5 -4
- package/dist/skill/reference/components/rich-text/COMPONENT.md +45 -7
- package/dist/skill/reference/components/rich-text/examples/entities.tsx +20 -0
- package/dist/skill/reference/components/select/COMPONENT.md +1 -2
- package/dist/skill/reference/components/sheet/COMPONENT.md +5 -4
- package/dist/skill/reference/components/smart-filter/COMPONENT.md +3 -5
- package/dist/skill/reference/components/sparkline/COMPONENT.md +40 -12
- package/dist/skill/reference/components/sparkline/examples/lower-is-better.tsx +46 -0
- package/dist/skill/reference/components/sparkline/examples/own-value.tsx +41 -0
- package/dist/skill/reference/components/sparkline/examples/reference.tsx +2 -1
- package/dist/skill/reference/components/tabs/COMPONENT.md +1 -1
- package/dist/skill/reference/components/tag-select/COMPONENT.md +0 -2
- package/dist/skill/reference/components/tag-select/examples/overview.tsx +0 -1
- package/dist/skill/reference/components/tag-select/examples/sizes.tsx +0 -1
- package/dist/skill/reference/components/textarea/COMPONENT.md +4 -3
- package/dist/skill/reference/icon-set.ts +5 -1
- package/dist/skill/reference/layout/page/COMPONENT.md +13 -12
- package/dist/skill/reference/layout/page/examples/collapse.tsx +45 -31
- package/dist/skill/reference/layout/page/examples/router.tsx +47 -14
- package/dist/skill/ux.md +79 -2
- package/package.json +1 -1
- package/src/components/card/COMPONENT.md +1 -1
- package/src/components/card/examples/screen.tsx +9 -11
- package/src/components/chat-list/COMPONENT.md +6 -5
- package/src/components/chat-list/examples/structure.tsx +4 -4
- package/src/components/command-menu/COMPONENT.md +1 -2
- package/src/components/command-menu/examples/states.tsx +1 -1
- package/src/components/composer/COMPONENT.md +5 -4
- package/src/components/composer/examples/formatting.tsx +1 -1
- package/src/components/data-table/COMPONENT.md +17 -16
- package/src/components/data-table/examples/server.tsx +2 -1
- package/src/components/datepicker/COMPONENT.md +2 -2
- package/src/components/dnd/COMPONENT.md +2 -1
- package/src/components/drawer/COMPONENT.md +9 -6
- package/src/components/drawer/examples/tab-switcher.tsx +158 -0
- package/src/components/field-list/COMPONENT.md +7 -7
- package/src/components/field-list/examples/limits.tsx +2 -2
- package/src/components/file-upload/COMPONENT.md +8 -12
- package/src/components/file-upload/examples/gallery.tsx +2 -2
- package/src/components/file-upload/examples/in-form.tsx +2 -4
- package/src/components/file-upload/examples/narrow.tsx +1 -0
- package/src/components/file-upload/examples/overview.tsx +1 -5
- package/src/components/file-upload/examples/sizes.tsx +1 -1
- package/src/components/file-upload/examples/states.tsx +4 -5
- package/src/components/file-upload/examples/validation.tsx +2 -7
- package/src/components/kanban/COMPONENT.md +1 -2
- package/src/components/list/COMPONENT.md +11 -6
- package/src/components/list/examples/reorder.tsx +30 -3
- package/src/components/message/COMPONENT.md +12 -7
- package/src/components/message/examples/status.tsx +2 -1
- package/src/components/message/examples/text-formatting.tsx +13 -3
- package/src/components/modal/COMPONENT.md +5 -4
- package/src/components/rich-text/COMPONENT.md +45 -7
- package/src/components/rich-text/examples/entities.tsx +20 -0
- package/src/components/select/COMPONENT.md +1 -2
- package/src/components/sheet/COMPONENT.md +5 -4
- package/src/components/smart-filter/COMPONENT.md +3 -5
- package/src/components/sparkline/COMPONENT.md +40 -12
- package/src/components/sparkline/examples/lower-is-better.tsx +46 -0
- package/src/components/sparkline/examples/own-value.tsx +41 -0
- package/src/components/sparkline/examples/reference.tsx +2 -1
- package/src/components/tabs/COMPONENT.md +1 -1
- package/src/components/tag-select/COMPONENT.md +0 -2
- package/src/components/tag-select/examples/overview.tsx +0 -1
- package/src/components/tag-select/examples/sizes.tsx +0 -1
- package/src/components/textarea/COMPONENT.md +4 -3
- package/src/layout/page/COMPONENT.md +13 -12
- package/src/layout/page/examples/collapse.tsx +45 -31
- package/src/layout/page/examples/router.tsx +47 -14
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `prime-ui-kit/icons` — the full animated glyph set as named components (`<BellIcon />`), each a
|
|
3
|
-
* Lucide drawing
|
|
3
|
+
* Lucide drawing (or a Simple Icons brand mark: `<TelegramIcon />`) with one gesture; import only
|
|
4
|
+
* what you use, the rest is never bundled. The
|
|
4
5
|
* semantic `<Icon name>` set lives in the main entry. Generated by `bun run icons:add`.
|
|
5
6
|
*/
|
|
6
7
|
export type { DomainIconProps as GlyphIconProps, Glyph } from "./icons/glyph";
|
|
@@ -462,6 +463,7 @@ export { SwissFranc as SwissFrancIcon } from "./icons/glyphs/swiss-franc";
|
|
|
462
463
|
export { SwitchCamera as SwitchCameraIcon } from "./icons/glyphs/switch-camera";
|
|
463
464
|
export { Syringe as SyringeIcon } from "./icons/glyphs/syringe";
|
|
464
465
|
export { Tablet as TabletIcon } from "./icons/glyphs/tablet";
|
|
466
|
+
export { Telegram as TelegramIcon } from "./icons/glyphs/telegram";
|
|
465
467
|
export { Telescope as TelescopeIcon } from "./icons/glyphs/telescope";
|
|
466
468
|
export { Terminal as TerminalIcon } from "./icons/glyphs/terminal";
|
|
467
469
|
export { TextAlignCenter as TextAlignCenterIcon } from "./icons/glyphs/text-align-center";
|
|
@@ -503,6 +505,7 @@ export { Users as UsersIcon } from "./icons/glyphs/users";
|
|
|
503
505
|
export { UsersRound as UsersRoundIcon } from "./icons/glyphs/users-round";
|
|
504
506
|
export { Vibrate as VibrateIcon } from "./icons/glyphs/vibrate";
|
|
505
507
|
export { Video as VideoIcon } from "./icons/glyphs/video";
|
|
508
|
+
export { Vk as VkIcon } from "./icons/glyphs/vk";
|
|
506
509
|
export { Volume as VolumeIcon } from "./icons/glyphs/volume";
|
|
507
510
|
export { Volume2 as Volume2Icon } from "./icons/glyphs/volume-2";
|
|
508
511
|
export { VolumeX as VolumeXIcon } from "./icons/glyphs/volume-x";
|
|
@@ -514,6 +517,7 @@ export { WavesArrowUp as WavesArrowUpIcon } from "./icons/glyphs/waves-arrow-up"
|
|
|
514
517
|
export { WavesLadder as WavesLadderIcon } from "./icons/glyphs/waves-ladder";
|
|
515
518
|
export { Waypoints as WaypointsIcon } from "./icons/glyphs/waypoints";
|
|
516
519
|
export { Webhook as WebhookIcon } from "./icons/glyphs/webhook";
|
|
520
|
+
export { Whatsapp as WhatsappIcon } from "./icons/glyphs/whatsapp";
|
|
517
521
|
export { Wifi as WifiIcon } from "./icons/glyphs/wifi";
|
|
518
522
|
export { WifiCog as WifiCogIcon } from "./icons/glyphs/wifi-cog";
|
|
519
523
|
export { WifiLow as WifiLowIcon } from "./icons/glyphs/wifi-low";
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
- Every route rendered inside `AppShell.Main`: the app is navigation and the page, and the page carries its own top (`Page.TopBar`), title (`Page.Title`, the `<h1>`) and body.
|
|
10
10
|
- A work screen (a list, a board, a chat, a schedule): `Page.TopBar` with the title (`Page.Heading`), the page's tools (`Page.Tools`: sections, search and filter, view), all its buttons (`Page.Actions`) and the active filters (`Page.Chips`).
|
|
11
11
|
- A filter stands next to what it filters. `Page.Tools` holds what acts on the whole page: sections, the view, a period that drives several blocks (the KPI cards and the table), and a search and filter only when it narrows several views of one set (a board and its list). A table's search, filter, chips, «Колонки» and selection menu always belong to its own tools row (DataTable `toolbar` + `toolbarChips`), on a page that is only the table too.
|
|
12
|
-
- A section with sub-pages (the «Мессенджеры» item of the Sidebar with
|
|
13
|
-
- A document page (settings, a long form, help)
|
|
12
|
+
- A section with sub-pages (the «Мессенджеры» item of the Sidebar with «Каналы», «Шаблоны» and «Статистика»): each sub-page is a route of its own, `Page.Title` stays the section's name, and the switch is a `SegmentedControl` of links (`SegmentedControl.Item asChild`) in `Page.Tools`, on a line of its own under the title at the start (see Top bar plan), so it stays put while each sub-page brings its own `Page.Actions` or none; the body is the current sub-page. The Sidebar keeps the section's item current on every sub-route — a router `NavLink` to the section's path without `end`, or `current={pathname.startsWith("/messengers")}`.
|
|
13
|
+
- A document page (settings, a long form, help) read top to bottom with no page tools: `Page.Header` in the flow — the large title and its buttons — and `Page.TopBar collapse` with the path in its `Page.Heading`, which repeats the title once it scrolls away. A dashboard or an analytics page titles itself in `Page.TopBar` like a list.
|
|
14
14
|
- A nested record: a `Breadcrumb` path in `Page.Heading`; in the bar the title reads as its last level.
|
|
15
15
|
- A text page held to a reading measure (`maxWidth="readable"`), a wide dashboard capped at the content width (`maxWidth="wide"`).
|
|
16
16
|
- The tiles of a dashboard — KPI cards, charts, panels — as one board (`Page.Grid` + `Page.Cell`).
|
|
@@ -94,7 +94,7 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
94
94
|
| `…rest` | `HTMLAttributes<HTMLElement>` | — | `children` (`Page.Heading`, `Page.Tools`, `Page.Actions`, `Page.Chips` — direct children), `className` and the other attributes. |
|
|
95
95
|
|
|
96
96
|
### Page.Header
|
|
97
|
-
`ref` → `HTMLDivElement`. The document header in the page flow (settings, long forms
|
|
97
|
+
`ref` → `HTMLDivElement`. The document header in the page flow (settings, long forms): a `Page.Heading` and its `Page.Actions` in one row; the actions wrap under it on a narrow column.
|
|
98
98
|
|
|
99
99
|
| Prop | Type | Default | Description |
|
|
100
100
|
|---|---|---|---|
|
|
@@ -123,7 +123,7 @@ Page.Root the page column (<div>); maxWidth, fill, labe
|
|
|
123
123
|
| `…rest` | `HTMLAttributes<HTMLParagraphElement>` | — | `children`, `className` and the other paragraph attributes. |
|
|
124
124
|
|
|
125
125
|
### Page.Tools
|
|
126
|
-
`ref` → `HTMLDivElement`. What acts on the whole page:
|
|
126
|
+
`ref` → `HTMLDivElement`. What acts on the whole page: sub-page links, view, a shared period, a filter over several views (a table's tools go in its `toolbar`). Sub-page links take a line of their own at the start. Named, a `group`.
|
|
127
127
|
|
|
128
128
|
| Prop | Type | Default | Description |
|
|
129
129
|
|---|---|---|---|
|
|
@@ -226,8 +226,8 @@ No DOM, no ref. Holds the page from anywhere inside it: a press on a `Sidebar.It
|
|
|
226
226
|
### Place of Page.Heading
|
|
227
227
|
| Value | Looks like | Use when | Default |
|
|
228
228
|
|---|---|---|---|
|
|
229
|
-
| in `Page.TopBar` | one line as high as the Sidebar brand row: path › title-m, the caption description under the whole line; both truncate |
|
|
230
|
-
| in `Page.Header` in the flow | the large document header: path over a heading-m title, body-m description, the header's `Page.Actions` beside it, wrapping under it | document pages
|
|
229
|
+
| in `Page.TopBar` | one line as high as the Sidebar brand row: path › title-m, the caption description under the whole line; both truncate | screens of data: lists, boards, chats, records, dashboards and analytics | |
|
|
230
|
+
| in `Page.Header` in the flow | the large document header: path over a heading-m title, body-m description, the header's `Page.Actions` beside it, wrapping under it | document pages with no page tools: settings, long forms, help | |
|
|
231
231
|
|
|
232
232
|
The size follows the place; the consumer passes nothing, and there are no title size props. One `Page.Title` (`<h1>`) per page, in one of the two places. A bar with no visible title still holds the `<h1>`: a `Page.Title` wrapped in `VisuallyHidden`, or a flow `Page.Title` (with `collapse` the bar repeats it).
|
|
233
233
|
|
|
@@ -236,7 +236,7 @@ The size follows the place; the consumer passes nothing, and there are no title
|
|
|
236
236
|
|---|---|---|---|
|
|
237
237
|
| `fill` (Root) | the page is as high as main; the body takes what the bar and the header leave; main keeps gutter-high air under it | a full-height table, board, chat or `Page.Split` in an `AppShell.Root fillViewport` | `false` |
|
|
238
238
|
| `sticky` (TopBar) | the bar stays at the top of main while the page scrolls under it | every page; `false` for a bar that should scroll away | `true` |
|
|
239
|
-
| `collapse` (TopBar) | the flow title rises into the bar's title row once it scrolls away | a document page
|
|
239
|
+
| `collapse` (TopBar) | the flow title rises into the bar's title row once it scrolls away | a document page with the path in the bar and the title in the flow | `false` |
|
|
240
240
|
| `dirty` (Guard) | nothing until a person leaves: a press on the navigation or the path asks «leave without saving?» | a form that is the page has unsaved changes | `false` |
|
|
241
241
|
| `busy` (Guard) | nothing until a person leaves: a press on the navigation or the path does nothing | the page's save request runs | `false` |
|
|
242
242
|
|
|
@@ -245,13 +245,13 @@ One plan, chosen from the natural widths of the groups (`data-plan` on the bar's
|
|
|
245
245
|
|
|
246
246
|
| Step | Looks like | Use when | Default |
|
|
247
247
|
|---|---|---|---|
|
|
248
|
-
| `one` | `[Heading 1fr][Tools][Actions]` on one line | everything fits | yes |
|
|
249
|
-
| `below` | the tools on a line of their own under the heading and the actions — the one tool row, as the DataTable `toolbar`: a search (`SmartFilter.Toolbar`, `Input.Root`) takes the free room, switchers, pickers and buttons keep their width at the end; under 36rem the search takes a line of its own and a `SegmentedControl.Root` / `Datepicker.Root` / Select fills the line it stands on | heading + tools + actions do not fit | |
|
|
248
|
+
| `one` | `[Heading 1fr][Tools][Actions]` on one line | everything fits, and the tools hold no navigation | yes |
|
|
249
|
+
| `below` | the tools on a line of their own under the heading and the actions — the one tool row, as the DataTable `toolbar`: a search (`SmartFilter.Toolbar`, `Input.Root`) takes the free room, switchers, pickers and buttons keep their width at the end; under 36rem the search takes a line of its own and a `SegmentedControl.Root` / `Datepicker.Root` / Select fills the line it stands on | heading + tools + actions do not fit; always for a section's sub-pages (the tools hold a navigation — a `SegmentedControl` of links), gathered at the start | |
|
|
250
250
|
| `icons-secondary` | as `below`; secondary Buttons with a `Button.Icon` show only the icon, the label is their name and their tooltip | heading + actions still do not fit | |
|
|
251
251
|
| `icons` | the primary Button gives way to its icon too | still too wide | |
|
|
252
252
|
| `fold` | the secondary Buttons fold into «⋯» (`labels.more`); the primary stays | still too wide, and «⋯» is narrower than their icons (a single secondary keeps its icon); past it a Breadcrumb path folds into «…»; if the path and the title still do not share the line, the heading stacks: the path on its own line over the title, the title and the description wrapping (`data-heading="stack"`); a heading without a path truncates | |
|
|
253
253
|
|
|
254
|
-
The actions stay on the first line beside the heading at every step; `Page.Chips` is always a line under everything; nothing overflows. The primary is the first solid `Button.Root`; a Button without an icon keeps its label at every step; anything that is not a `Button.Root` stays as it is.
|
|
254
|
+
A section's sub-pages never share the first line: there they would stand where each sub-page's own heading and actions leave room, and those differ («Подключить канал» on one, none on another), so the switch would move under the pointer. On a line of their own at the start they stand at one place on every sub-page and at every width. The actions stay on the first line beside the heading at every step; `Page.Chips` is always a line under everything; nothing overflows. The primary is the first solid `Button.Root`; a Button without an icon keeps its label at every step; anything that is not a `Button.Root` stays as it is.
|
|
255
255
|
|
|
256
256
|
### Grid columns
|
|
257
257
|
| Grid width | Cell `span` 1–3 | `span` 4 | `span` 5–6 | `span` 7–12 | `rows` |
|
|
@@ -342,9 +342,9 @@ A wide cell (`span` 7–11) written next to a narrow one (`span` 4–5) is a pai
|
|
|
342
342
|
| [structure.tsx](examples/structure.tsx) | A document page in the flow: the path over the large title, its line of data and the page's buttons beside them, then the body with a section — `Page.Header`, `Page.Heading`, `Page.Description`, `Page.Section`. |
|
|
343
343
|
| [top-bar.tsx](examples/top-bar.tsx) | A nested record in the top bar: the path ending in the title, a line of data, the page's tools and its buttons — `Page.TopBar`, `Page.Heading`, `Page.Tools`, `Page.Actions`. |
|
|
344
344
|
| [loading.tsx](examples/loading.tsx) | A page whose figures load after it opens: the line of data keeps its place under the title while it loads, so nothing in the bar moves — `loading`. |
|
|
345
|
-
| [collapse.tsx](examples/collapse.tsx) |
|
|
345
|
+
| [collapse.tsx](examples/collapse.tsx) | A document page whose large title scrolls away under the top bar: the bar holds the path and repeats the title after it once the title is out of view — `collapse`, `Page.Header`. |
|
|
346
346
|
| [tools.tsx](examples/tools.tsx) | The orders page's tools in its top bar: sections with counts, the search and filter that narrow both views, the view, in the order written; the active filters on a line under everything — `Page.Tools`, `Page.Chips`. |
|
|
347
|
-
| [router.tsx](examples/router.tsx) | A section with sub-pages: the title is the section, each sub-page is a route of its own, the switch in the bar is their links and the body cross-fades between them; render it inside a router — `Page.Tools`, `asChild`. |
|
|
347
|
+
| [router.tsx](examples/router.tsx) | A section with sub-pages: the title is the section, each sub-page is a route of its own with its own actions, the switch in the bar is their links on a line of their own and the body cross-fades between them; render it inside a router — `Page.Tools`, `asChild`. |
|
|
348
348
|
| [actions-collapse.tsx](examples/actions-collapse.tsx) | The buttons of an invoices page on a tablet-wide column: the line runs short, so the secondary buttons show only their icons with the label as the name and the tooltip, the primary keeps its label — `Page.Actions`, `Button.Icon`. |
|
|
349
349
|
| [sections.tsx](examples/sections.tsx) | A page with titled sections: tiles of one screen close together, a new topic set off by air, a link at the end of the heading — `Page.Section`, `label`, `description`, `Page.Actions`. |
|
|
350
350
|
| [grid.tsx](examples/grid.tsx) | A dashboard board: four KPI tiles, a chart two rows high beside two panels, one gap on both axes — `Page.Grid`, `Page.Cell`, `span`, `rows`. |
|
|
@@ -365,6 +365,7 @@ A wide cell (`span` 7–11) written next to a narrow one (`span` 4–5) is a pai
|
|
|
365
365
|
- `SmartFilter.Root` inside the bar, around `Page.Tools` → it holds no DOM; wrap it around `Page.Root` so its Toolbar (in `Page.Tools`) and its Chips (in `Page.Chips`) share it.
|
|
366
366
|
- A table's search, filter, chips or «Колонки» in `Page.Tools` / `Page.Chips` → they read as filters of the whole page and move when the page grows a block; they go in the table's `toolbar` / `toolbarChips` on every page, the one that is only the table too. The bar keeps the title, sections, the view, a period that drives every block and the page's buttons.
|
|
367
367
|
- Sub-pages of a section as a second Sidebar level, a tab strip inside `Page.Body`, or a title that names the sub-page → the title stays the section, the sub-pages are route links in `Page.Tools` (`SegmentedControl.Item asChild`), and the section's Sidebar item stays current on every sub-route.
|
|
368
|
+
- A dashboard or analytics page with a bar of tools only (`collapse`) over a large title in the flow → the title and its line of data go in the bar's `Page.Heading` beside the tools, as on a list; the flow header is for document pages.
|
|
368
369
|
- `Page.Header` inside `Page.TopBar` → the bar takes `Page.Heading` and `Page.Actions` directly; `Page.Header` is the flow's document header.
|
|
369
370
|
- A hand-made menu button for the phone Sidebar → the bar draws it while the Sidebar is off-canvas.
|
|
370
371
|
- Hiding buttons with media queries on a phone → `Page.Actions` in the bar collapses and folds them by measured width.
|
|
@@ -1,60 +1,74 @@
|
|
|
1
|
-
/**
|
|
2
|
-
import { AppShell, Button, Card, Icon, Page,
|
|
3
|
-
import * as React from "react";
|
|
1
|
+
/** A document page whose large title scrolls away under the top bar: the bar holds the path and repeats the title after it once the title is out of view — `collapse`, `Page.Header`. */
|
|
2
|
+
import { AppShell, Breadcrumb, Button, Card, Icon, Page, Typography } from "prime-ui-kit";
|
|
4
3
|
|
|
5
4
|
import styles from "./examples.module.css";
|
|
6
5
|
|
|
7
|
-
const
|
|
6
|
+
const SECTIONS = [
|
|
8
7
|
{
|
|
9
|
-
id: "
|
|
10
|
-
title: "
|
|
11
|
-
text: "
|
|
8
|
+
id: "terms",
|
|
9
|
+
title: "Сроки",
|
|
10
|
+
text: "Клиент может вернуть заказ в течение 14 дней после получения. Срок считается от даты в акте приёмки.",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "condition",
|
|
14
|
+
title: "Состояние товара",
|
|
15
|
+
text: "Товар принимается в заводской упаковке, без следов использования, с ярлыками и полным комплектом.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "refund",
|
|
19
|
+
title: "Возврат денег",
|
|
20
|
+
text: "Деньги возвращаются тем же способом, которым был оплачен заказ, в течение 10 рабочих дней после проверки.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "delivery",
|
|
24
|
+
title: "Доставка при возврате",
|
|
25
|
+
text: "Если товар с браком, доставку оплачивает магазин. В остальных случаях — клиент по тарифу службы доставки.",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "exceptions",
|
|
29
|
+
title: "Исключения",
|
|
30
|
+
text: "Не возвращаются товары, изготовленные по индивидуальному заказу, и цифровые лицензии после активации.",
|
|
12
31
|
},
|
|
13
|
-
{ id: "w40", title: "29 сентября – 5 октября", text: "Выручка 1,1 млн ₽, 298 заказов." },
|
|
14
|
-
{ id: "w39", title: "22–28 сентября", text: "Выручка 0,9 млн ₽, 264 заказа." },
|
|
15
|
-
{ id: "w38", title: "15–21 сентября", text: "Выручка 1,0 млн ₽, 281 заказ." },
|
|
16
|
-
{ id: "w37", title: "8–14 сентября", text: "Выручка 0,8 млн ₽, 240 заказов." },
|
|
17
32
|
];
|
|
18
33
|
|
|
19
34
|
export default function PageCollapseExample() {
|
|
20
|
-
const [period, setPeriod] = React.useState("week");
|
|
21
|
-
|
|
22
35
|
return (
|
|
23
36
|
<div className={`${styles.window} ${styles.tall}`}>
|
|
24
37
|
<AppShell.Root fillViewport className={styles.shell}>
|
|
25
38
|
<AppShell.Main>
|
|
26
|
-
<Page.Root>
|
|
39
|
+
<Page.Root maxWidth="readable">
|
|
27
40
|
<Page.TopBar collapse>
|
|
28
|
-
<Page.
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
<Page.Heading>
|
|
42
|
+
<Breadcrumb.Root>
|
|
43
|
+
<Breadcrumb.Item href="#documents">Документы</Breadcrumb.Item>
|
|
44
|
+
</Breadcrumb.Root>
|
|
45
|
+
</Page.Heading>
|
|
46
|
+
</Page.TopBar>
|
|
47
|
+
<Page.Header>
|
|
48
|
+
<Page.Heading>
|
|
49
|
+
<Page.Title>Политика возвратов</Page.Title>
|
|
50
|
+
<Page.Description>
|
|
51
|
+
Действует с 1 сентября для всех заказов магазина.
|
|
52
|
+
</Page.Description>
|
|
53
|
+
</Page.Heading>
|
|
34
54
|
<Page.Actions>
|
|
35
55
|
<Button.Root variant="soft" tone="neutral">
|
|
36
56
|
<Button.Icon>
|
|
37
57
|
<Icon name="action.download" />
|
|
38
58
|
</Button.Icon>
|
|
39
|
-
|
|
59
|
+
Скачать PDF
|
|
40
60
|
</Button.Root>
|
|
41
61
|
</Page.Actions>
|
|
42
|
-
</Page.TopBar>
|
|
43
|
-
<Page.Header>
|
|
44
|
-
<Page.Heading>
|
|
45
|
-
<Page.Title>Продажи</Page.Title>
|
|
46
|
-
<Page.Description>Интернет-магазин · на 14 октября, 9:40</Page.Description>
|
|
47
|
-
</Page.Heading>
|
|
48
62
|
</Page.Header>
|
|
49
63
|
<Page.Body>
|
|
50
|
-
{
|
|
51
|
-
<Card.Root key={
|
|
64
|
+
{SECTIONS.map((section) => (
|
|
65
|
+
<Card.Root key={section.id} variant="panel">
|
|
52
66
|
<Card.Header>
|
|
53
|
-
<Card.Title as="h2">{
|
|
67
|
+
<Card.Title as="h2">{section.title}</Card.Title>
|
|
54
68
|
</Card.Header>
|
|
55
69
|
<Card.Body>
|
|
56
70
|
<Typography variant="body-m" tone="secondary">
|
|
57
|
-
{
|
|
71
|
+
{section.text}
|
|
58
72
|
</Typography>
|
|
59
73
|
</Card.Body>
|
|
60
74
|
</Card.Root>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A section with sub-pages: the title is the section, each sub-page is a route of its own, the switch in the bar is their links and the body cross-fades between them; render it inside a router — `Page.Tools`, `asChild`. */
|
|
1
|
+
/** A section with sub-pages: the title is the section, each sub-page is a route of its own with its own actions, the switch in the bar is their links on a line of their own and the body cross-fades between them; render it inside a router — `Page.Tools`, `asChild`. */
|
|
2
2
|
import {
|
|
3
3
|
Button,
|
|
4
4
|
Card,
|
|
@@ -12,10 +12,13 @@ import {
|
|
|
12
12
|
import { Link, Route, Routes, useLocation } from "react-router-dom";
|
|
13
13
|
|
|
14
14
|
type Channel = { id: string; name: string; dialogs: number; reply: string };
|
|
15
|
+
type Template = { id: string; name: string; uses: number };
|
|
15
16
|
|
|
17
|
+
// The action of each sub-page: its own label, none on the statistics.
|
|
16
18
|
const SUB_PAGES = [
|
|
17
|
-
{ to: "/messengers/channels", label: "Каналы" },
|
|
18
|
-
{ to: "/messengers/
|
|
19
|
+
{ to: "/messengers/channels", label: "Каналы", action: "Подключить канал" },
|
|
20
|
+
{ to: "/messengers/templates", label: "Шаблоны", action: "Новый шаблон" },
|
|
21
|
+
{ to: "/messengers/stats", label: "Статистика", action: null },
|
|
19
22
|
];
|
|
20
23
|
|
|
21
24
|
const CHANNELS: Channel[] = [
|
|
@@ -25,6 +28,23 @@ const CHANNELS: Channel[] = [
|
|
|
25
28
|
{ id: "avito", name: "Авито · Склад на Южной", dialogs: 59, reply: "25 мин" },
|
|
26
29
|
];
|
|
27
30
|
|
|
31
|
+
const TEMPLATES: Template[] = [
|
|
32
|
+
{ id: "greeting", name: "Приветствие в рабочее время", uses: 812 },
|
|
33
|
+
{ id: "night", name: "Ответ ночью", uses: 264 },
|
|
34
|
+
{ id: "delivery", name: "Сроки доставки", uses: 151 },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const TEMPLATE_COLUMNS: DataTableColumn<Template>[] = [
|
|
38
|
+
{ id: "name", header: "Шаблон", cell: (row) => row.name },
|
|
39
|
+
{
|
|
40
|
+
id: "uses",
|
|
41
|
+
header: "Отправок",
|
|
42
|
+
numeric: true,
|
|
43
|
+
headerAlign: "end",
|
|
44
|
+
cell: (row) => row.uses,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
28
48
|
const COLUMNS: DataTableColumn<Channel>[] = [
|
|
29
49
|
{ id: "name", header: "Канал", cell: (row) => row.name },
|
|
30
50
|
{
|
|
@@ -46,7 +66,7 @@ const COLUMNS: DataTableColumn<Channel>[] = [
|
|
|
46
66
|
export default function PageRouterExample() {
|
|
47
67
|
const location = useLocation();
|
|
48
68
|
// The first sub-page answers every other path of the section (here the preview's «/»).
|
|
49
|
-
const current = SUB_PAGES.find((page) => page.to === location.pathname)
|
|
69
|
+
const current = SUB_PAGES.find((page) => page.to === location.pathname) ?? SUB_PAGES[0];
|
|
50
70
|
|
|
51
71
|
return (
|
|
52
72
|
<Page.Root>
|
|
@@ -56,7 +76,7 @@ export default function PageRouterExample() {
|
|
|
56
76
|
<Page.Description>4 канала · 1 280 диалогов за октябрь</Page.Description>
|
|
57
77
|
</Page.Heading>
|
|
58
78
|
<Page.Tools aria-label="Мессенджеры">
|
|
59
|
-
<SegmentedControl.Root value={current} aria-label="Подразделы">
|
|
79
|
+
<SegmentedControl.Root value={current.to} aria-label="Подразделы">
|
|
60
80
|
{SUB_PAGES.map((page) => (
|
|
61
81
|
<SegmentedControl.Item key={page.to} value={page.to} asChild>
|
|
62
82
|
<Link to={page.to}>{page.label}</Link>
|
|
@@ -64,18 +84,20 @@ export default function PageRouterExample() {
|
|
|
64
84
|
))}
|
|
65
85
|
</SegmentedControl.Root>
|
|
66
86
|
</Page.Tools>
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
<Button.
|
|
70
|
-
<Icon
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
{current.action ? (
|
|
88
|
+
<Page.Actions>
|
|
89
|
+
<Button.Root>
|
|
90
|
+
<Button.Icon>
|
|
91
|
+
<Icon name="action.add" />
|
|
92
|
+
</Button.Icon>
|
|
93
|
+
{current.action}
|
|
94
|
+
</Button.Root>
|
|
95
|
+
</Page.Actions>
|
|
96
|
+
) : null}
|
|
75
97
|
</Page.TopBar>
|
|
76
98
|
<Page.Body>
|
|
77
99
|
{/* `location` keeps the leaving sub-page on its own route while it fades out. */}
|
|
78
|
-
<Crossfade state={current}>
|
|
100
|
+
<Crossfade state={current.to}>
|
|
79
101
|
<Routes location={location}>
|
|
80
102
|
<Route
|
|
81
103
|
path="/messengers/stats"
|
|
@@ -105,6 +127,17 @@ export default function PageRouterExample() {
|
|
|
105
127
|
</Page.Grid>
|
|
106
128
|
}
|
|
107
129
|
/>
|
|
130
|
+
<Route
|
|
131
|
+
path="/messengers/templates"
|
|
132
|
+
element={
|
|
133
|
+
<DataTable
|
|
134
|
+
columns={TEMPLATE_COLUMNS}
|
|
135
|
+
rows={TEMPLATES}
|
|
136
|
+
getRowKey={(row) => row.id}
|
|
137
|
+
paging="none"
|
|
138
|
+
/>
|
|
139
|
+
}
|
|
140
|
+
/>
|
|
108
141
|
<Route
|
|
109
142
|
path="*"
|
|
110
143
|
element={
|
package/dist/skill/ux.md
CHANGED
|
@@ -34,6 +34,7 @@ adds text that explains what the layout failed to show.
|
|
|
34
34
|
| A base value and its overrides (a price and its changes by month) | one list: the base is the first, pinned row | `FieldList` `getLocked` |
|
|
35
35
|
| Small attributes of a record (an icon, a color, a flag) | compact controls in the record's row, not fields of their own | icon-only `Select`, `ColorPresets`, `Switch` |
|
|
36
36
|
| Short texts in an order the person sets | one column, a handle per row | `FieldList` `reorderable` |
|
|
37
|
+
| Records of one kind, some fixed by the system (funnel stages, default roles) | one list in their real order, the fixed rows pinned in place | `List` `locked`, `FieldList` `getLocked` |
|
|
37
38
|
| Settings that most people never touch | behind a disclosure, after the common ones | `Accordion`, a «Дополнительно» section |
|
|
38
39
|
| One of a few options, sent with a form | all visible | `Radio.Group` |
|
|
39
40
|
| One of a few modes of what the screen shows | all visible | `SegmentedControl` |
|
|
@@ -81,6 +82,36 @@ records they cannot see. A set that can be empty shows its empty state, never a
|
|
|
81
82
|
</Drawer.Section>
|
|
82
83
|
```
|
|
83
84
|
|
|
85
|
+
**One kind, one list.** Records of one kind stay one list in the order they really have, whoever made
|
|
86
|
+
them: the stages of a funnel, the team's and the system's alike, read top to bottom as the board shows
|
|
87
|
+
them. What some rows cannot do (move, be removed) is a mark on those rows — the lock in the grip's
|
|
88
|
+
place — never a second section. Two lists split one order in two, and their columns no longer line up.
|
|
89
|
+
The test: does the board, the report or the process read these records as one sequence? Then they are
|
|
90
|
+
one list.
|
|
91
|
+
|
|
92
|
+
```tsx partial
|
|
93
|
+
// bad — the system stages in a section of their own under the team's; one order, two lists
|
|
94
|
+
<Drawer.Section label="Свои этапы">
|
|
95
|
+
<List.Root aria-label="Свои этапы" onReorder={reorder}>…</List.Root>
|
|
96
|
+
</Drawer.Section>
|
|
97
|
+
<Drawer.Section label="Системные этапы">
|
|
98
|
+
<List.Root aria-label="Системные этапы">…</List.Root>
|
|
99
|
+
</Drawer.Section>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
```tsx good
|
|
103
|
+
// good — one list in board order; the system stages hold their places with a lock
|
|
104
|
+
// declare: const stages: { id: string; name: string; system: boolean }[]
|
|
105
|
+
// declare: const reorder: (id: string, beforeId: string | null) => void
|
|
106
|
+
<List.Root aria-label="Этапы воронки" onReorder={reorder}>
|
|
107
|
+
{stages.map((stage) => (
|
|
108
|
+
<List.Item key={stage.id} value={stage.id} locked={stage.system}>
|
|
109
|
+
<List.ItemText>{stage.name}</List.ItemText>
|
|
110
|
+
</List.Item>
|
|
111
|
+
))}
|
|
112
|
+
</List.Root>
|
|
113
|
+
```
|
|
114
|
+
|
|
84
115
|
## 3. Label economy
|
|
85
116
|
|
|
86
117
|
Every word on screen must carry something the person does not see already.
|
|
@@ -91,6 +122,46 @@ Every word on screen must carry something the person does not see already.
|
|
|
91
122
|
list already counts. The list label names the rows.
|
|
92
123
|
- **No label that repeats its heading.** A field «Цена» inside the section «Цена» needs no label of
|
|
93
124
|
its own beyond what the section says; a group of one field is the field.
|
|
125
|
+
- **A heading only where it names what nothing else names.** A block that names itself — a chart or a
|
|
126
|
+
card with its own title, a table with its toolbar — gets no section heading over it, and the first
|
|
127
|
+
block of a tab never repeats the tab («Характеристики» under the tab «Характеристики»). A section
|
|
128
|
+
heading stands where several blocks share a topic that none of them names. The test: delete the
|
|
129
|
+
heading and read the screen; if nothing is lost, it goes. Blocks without a heading stand one under
|
|
130
|
+
another with the section step between them (a `Tabs.Panel` in a dialog body spaces them by itself;
|
|
131
|
+
no wrapper of your own).
|
|
132
|
+
|
|
133
|
+
```tsx partial
|
|
134
|
+
// bad — a section over a chart that has its own title, under a tab of the same name
|
|
135
|
+
<Tabs.Panel value="analytics">
|
|
136
|
+
<Drawer.Section label="Аналитика" description="За 30 дней">
|
|
137
|
+
<ChartFrame.Root>
|
|
138
|
+
<ChartFrame.Header>
|
|
139
|
+
<ChartFrame.Title>Продажи</ChartFrame.Title>
|
|
140
|
+
</ChartFrame.Header>
|
|
141
|
+
…
|
|
142
|
+
</ChartFrame.Root>
|
|
143
|
+
</Drawer.Section>
|
|
144
|
+
</Tabs.Panel>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```tsx good
|
|
148
|
+
// good — the charts name themselves; the tab names the topic, the period is the tab's switcher
|
|
149
|
+
// declare: const sales: Parameters<typeof ChartFrame.Root>[0]["rows"]
|
|
150
|
+
// declare: const refunds: Parameters<typeof ChartFrame.Root>[0]["rows"]
|
|
151
|
+
<Tabs.Panel value="analytics">
|
|
152
|
+
<ChartFrame.Root rows={sales}>
|
|
153
|
+
<ChartFrame.Header>
|
|
154
|
+
<ChartFrame.Title>Продажи</ChartFrame.Title>
|
|
155
|
+
</ChartFrame.Header>
|
|
156
|
+
</ChartFrame.Root>
|
|
157
|
+
<ChartFrame.Root rows={refunds}>
|
|
158
|
+
<ChartFrame.Header>
|
|
159
|
+
<ChartFrame.Title>Возвраты</ChartFrame.Title>
|
|
160
|
+
</ChartFrame.Header>
|
|
161
|
+
</ChartFrame.Root>
|
|
162
|
+
</Tabs.Panel>
|
|
163
|
+
```
|
|
164
|
+
|
|
94
165
|
- **A unit lives in one place.** In the field (`Input.InlineAffix`) or in the heading, never both;
|
|
95
166
|
never in the field's own label («Бюджет», not «Бюджет, ₽»). Prefer the field: the unit stays
|
|
96
167
|
beside the number when the row stacks, and a screen reader hears it with the field (the affix
|
|
@@ -111,6 +182,11 @@ Every word on screen must carry something the person does not see already.
|
|
|
111
182
|
| «Звук» (Switch) — «Применяется сразу, без сохранения» | no hint |
|
|
112
183
|
| «Отключить доступ» — «Сотрудник не сможет войти, история его действий останется» | «Входы прекратятся» |
|
|
113
184
|
| «Почта для входа» — «На эту почту придёт ссылка для входа в систему» | «Для входа и уведомлений» |
|
|
185
|
+
- **Helper text is opt-in.** A hint, a limit, a counter, a range line («Показано 1–20 из 240») or a
|
|
186
|
+
description shows only where it tells this person something they need for this task; the kit
|
|
187
|
+
draws none of its own unless asked — DataTable shows its range with `showRange`, and FieldList and
|
|
188
|
+
FileUpload at `max` close up their add control without a word, so a limit worth knowing goes in
|
|
189
|
+
`hint` («До 10 фото»). Turn one on for a reason you can name, never because the prop exists.
|
|
114
190
|
- **One name per thing.** The same action, entity and status keep one word on the whole screen
|
|
115
191
|
(and across screens): «Счёт», not «Счёт» in the title and «Инвойс» in the button.
|
|
116
192
|
- **Count the labels.** On a form of repeating rows, labels on screen ≤ number of columns + the
|
|
@@ -158,8 +234,9 @@ Which button starts each step and how the confirm behaves:
|
|
|
158
234
|
## 6. Nothing to guess
|
|
159
235
|
|
|
160
236
|
- **A disabled control says why** when the reason is not on screen: a `hint` under the field, a
|
|
161
|
-
line beside the button
|
|
162
|
-
|
|
237
|
+
line beside the button, or the disabled control itself in `Tooltip.Trigger`. A control that
|
|
238
|
+
leaves at a limit (the add button of `FieldList` or `FileUpload` at `max`) says it ahead, in the
|
|
239
|
+
list's `hint`. An action a person may never take (no permission) is hidden, not
|
|
163
240
|
disabled.
|
|
164
241
|
- **The screen answers the latest question.** A request a value starts (a search, a filter, a page,
|
|
165
242
|
a period) is cancelled when the value changes again — abort it (`AbortController`) or drop its
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prime-ui-kit",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.3",
|
|
4
4
|
"packageManager": "bun@1.3.13",
|
|
5
5
|
"description": "React 19 UI kit for product interfaces: 79 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y, Next.js App Router ready. Ships COMPONENT.md docs, copyable examples and an agent skill.",
|
|
6
6
|
"bin": {
|
|
@@ -220,7 +220,7 @@ No `labels`.
|
|
|
220
220
|
| [flat.tsx](examples/flat.tsx) | A flat tile without the raised shadow next to the default one, for dense grids — `flat`. |
|
|
221
221
|
| [states.tsx](examples/states.tsx) | A KPI tile while its report loads next to the loaded one, in the `stat-trend` and `mini` templates — `loading`. |
|
|
222
222
|
| [narrow.tsx](examples/narrow.tsx) | The card is a size container: the split template stacks its cells below 22rem and the trend value shrinks below 20rem — `variant`. |
|
|
223
|
-
| [screen.tsx](examples/screen.tsx) | A dashboard: the title
|
|
223
|
+
| [screen.tsx](examples/screen.tsx) | A dashboard: the title with a line of data, the period switch and the report in the page's top bar; one grid of KPI tiles that cross-fade to the new period, a revenue trend, invoiced against paid beside two panels, and the latest payments with a link to the list — `Page.TopBar`, `Page.Grid`. |
|
|
224
224
|
|
|
225
225
|
## Mistakes
|
|
226
226
|
- A `<div>` with a border styled as a card → use `Card.Root`; depth comes from fill, not lines.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A dashboard: the title
|
|
1
|
+
/** A dashboard: the title with a line of data, the period switch and the report in the page's top bar; one grid of KPI tiles that cross-fade to the new period, a revenue trend, invoiced against paid beside two panels, and the latest payments with a link to the list — `Page.TopBar`, `Page.Grid`. */
|
|
2
2
|
import {
|
|
3
3
|
Badge,
|
|
4
4
|
Button,
|
|
@@ -157,10 +157,14 @@ export default function CardScreenExample() {
|
|
|
157
157
|
|
|
158
158
|
return (
|
|
159
159
|
<Page.Root>
|
|
160
|
-
{/* An overview
|
|
161
|
-
|
|
162
|
-
<Page.TopBar
|
|
163
|
-
<Page.
|
|
160
|
+
{/* An overview titles itself in its bar like any work screen: the title with its line of
|
|
161
|
+
data, the period that drives the tiles, the report. */}
|
|
162
|
+
<Page.TopBar>
|
|
163
|
+
<Page.Heading>
|
|
164
|
+
<Page.Title>Обзор</Page.Title>
|
|
165
|
+
<Page.Description>Продажи и оплаты · на 8 октября, 9:40</Page.Description>
|
|
166
|
+
</Page.Heading>
|
|
167
|
+
<Page.Tools aria-label="Обзор">
|
|
164
168
|
<SegmentedControl.Root
|
|
165
169
|
aria-label="Период"
|
|
166
170
|
value={period}
|
|
@@ -180,12 +184,6 @@ export default function CardScreenExample() {
|
|
|
180
184
|
</Button.Root>
|
|
181
185
|
</Page.Actions>
|
|
182
186
|
</Page.TopBar>
|
|
183
|
-
<Page.Header>
|
|
184
|
-
<Page.Heading>
|
|
185
|
-
<Page.Title>Обзор</Page.Title>
|
|
186
|
-
<Page.Description>Продажи и оплаты · на 8 октября, 9:40</Page.Description>
|
|
187
|
-
</Page.Heading>
|
|
188
|
-
</Page.Header>
|
|
189
187
|
<Page.Body>
|
|
190
188
|
{/* One board: every tile of the screen in one grid, 16 apart on both axes. */}
|
|
191
189
|
<Page.Grid>
|
|
@@ -29,7 +29,7 @@ ChatList.Root <div> column filling its host (container `chat-lis
|
|
|
29
29
|
│ └─ Crossfade loading (placeholder List rows) ⇄ empty (EmptyPage compact) ⇄ the list
|
|
30
30
|
│ └─ List.Root <ul> aria-label «Диалоги»; spacer <li aria-hidden> stand for rows out of view
|
|
31
31
|
│ └─ ChatList.Item List.Item <li> + <button> | <a href> | your element (asChild); current from value; one Tab stop
|
|
32
|
-
│ ├─ ChatList.ItemIcon before the name: avatar slot (aria-hidden); after it: a glyph
|
|
32
|
+
│ ├─ ChatList.ItemIcon before the name: avatar slot (aria-hidden); after it: a glyph (role="img" with aria-label)
|
|
33
33
|
│ ├─ ChatList.ItemText the name; muted glyph after it
|
|
34
34
|
│ ├─ ChatList.ItemDescription author · media word · text — cross-fades with draft and «печатает…»
|
|
35
35
|
│ ├─ (meta) delivery ticks · time
|
|
@@ -89,12 +89,12 @@ ChatList.Root <div> column filling its host (container `chat-lis
|
|
|
89
89
|
| `…rest` | `ButtonHTMLAttributes<HTMLButtonElement>` | — | `className` and the native attributes of the row element. |
|
|
90
90
|
|
|
91
91
|
### ChatList.ItemIcon
|
|
92
|
-
`ref` → `HTMLSpanElement`. Before the name: the avatar slot (`aria-hidden`), an `Avatar.Root` sized by the row. After the name: a glyph beside it (verified, bot,
|
|
92
|
+
`ref` → `HTMLSpanElement`. Before the name: the avatar slot (`aria-hidden`), an `Avatar.Root` sized by the row. After the name: a glyph beside it (verified, bot, channel type), any kit icon sized by the row; with `aria-label` it is `role="img"` of that name.
|
|
93
93
|
|
|
94
94
|
| Prop | Type | Default | Description |
|
|
95
95
|
|---|---|---|---|
|
|
96
|
-
| `children` | `ReactNode` | — (required) | An `Avatar.Root` (with `Avatar.Status`), or after the name an
|
|
97
|
-
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className` and the other `span` attributes. |
|
|
96
|
+
| `children` | `ReactNode` | — (required) | An `Avatar.Root` (with `Avatar.Status`), or after the name an icon: `<Icon name>` or a glyph of `prime-ui-kit/icons` (`<SendIcon />`), as is. |
|
|
97
|
+
| `…rest` | `Omit<HTMLAttributes<HTMLSpanElement>, "children">` | — | `className`, `aria-label` (names a glyph after the name: its channel, «Официальный аккаунт») and the other `span` attributes. |
|
|
98
98
|
|
|
99
99
|
### ChatList.ItemText
|
|
100
100
|
`ref` → `HTMLSpanElement`. The name of the dialog: one line, truncated, the glyphs after it kept whole; the search query is marked.
|
|
@@ -200,7 +200,7 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
|
|
|
200
200
|
|
|
201
201
|
- The list is a `<ul>` named «Диалоги» (`labels.list`); each row an `<li>` with a button, a link with `href`, or your link with `asChild`.
|
|
202
202
|
- The open dialog is `aria-current="true"` on a button, `"page"` on a link. Selection does not follow the focus: arrows only move it (a dialog may load a conversation); Enter, Space and a click open.
|
|
203
|
-
- The row is named by its content: the name, glyphs
|
|
203
|
+
- The row is named by its content: the name, the glyphs (`ChatList.ItemIcon` `aria-label`, «Без звука»), the preview, the delivery mark («Прочитано»), the time, then «упоминание» and «непрочитанных: 3» instead of the bare badges; the avatar is `aria-hidden`.
|
|
204
204
|
- While the other side types, the preview is `aria-hidden` and «печатает…» is read; a draft reads «Черновик: …».
|
|
205
205
|
- A row with a menu has `aria-keyshortcuts="Shift+F10"`; the menu is a `Dropdown` (`menu` / `menuitem`).
|
|
206
206
|
- The search field is `type="search"` named «Поиск по диалогам»; after a search a polite status says «Найдено: 4» or «Ничего не найдено».
|
|
@@ -257,6 +257,7 @@ Root attribute: `data-size`. Every motion honours reduced motion (durations coll
|
|
|
257
257
|
- Filtering inside `renderItem` (returning `null`) → filter `items`: the list counts, announces and glides by `items`.
|
|
258
258
|
- Pinned dialogs mixed into the time order → put them first in `items`; `pinned` only draws the pin.
|
|
259
259
|
- `onValueChange` used to mark a dialog read on focus → arrows only move the focus; mark read when the conversation shows it (`Chat.Messages onRead`) or on open.
|
|
260
|
+
- A glyph after the name cloned with `label` and `size` → put the icon in `ChatList.ItemIcon` as is and name the part with `aria-label`: the row sizes it.
|
|
260
261
|
- An `Avatar.Root size="s"` to fit the row → leave `size` out; the slot sizes the avatar to the tier.
|
|
261
262
|
- A «⋯» button nested inside the row for the menu → `ChatList.ItemMenu`: it opens on right click, a long press and Shift+F10.
|
|
262
263
|
- Deleting from the menu without a question → open a `ConfirmDialog` from the menu item.
|
|
@@ -111,13 +111,13 @@ export default function ChatListStructureExample() {
|
|
|
111
111
|
</ChatList.ItemIcon>
|
|
112
112
|
<ChatList.ItemText>{item.name}</ChatList.ItemText>
|
|
113
113
|
{item.verified ? (
|
|
114
|
-
<ChatList.ItemIcon>
|
|
115
|
-
<Icon name="status.success" tone="accent"
|
|
114
|
+
<ChatList.ItemIcon aria-label="Официальный аккаунт">
|
|
115
|
+
<Icon name="status.success" tone="accent" />
|
|
116
116
|
</ChatList.ItemIcon>
|
|
117
117
|
) : null}
|
|
118
118
|
{item.group ? (
|
|
119
|
-
<ChatList.ItemIcon>
|
|
120
|
-
<Icon name="object.users"
|
|
119
|
+
<ChatList.ItemIcon aria-label="Группа">
|
|
120
|
+
<Icon name="object.users" />
|
|
121
121
|
</ChatList.ItemIcon>
|
|
122
122
|
) : null}
|
|
123
123
|
<ChatList.ItemDescription
|
|
@@ -136,7 +136,7 @@ CommandMenu.Root Modal + top-aligned palette panel, query and lo
|
|
|
136
136
|
| `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `children` (the title), `className` and the other span attributes. |
|
|
137
137
|
|
|
138
138
|
### CommandMenu.Empty
|
|
139
|
-
`ref` → `HTMLDivElement`. A compact EmptyPage (`role="status"`) shown while nothing matches and nothing loads: `labels.empty
|
|
139
|
+
`ref` → `HTMLDivElement`. A compact EmptyPage (`role="status"`) shown while nothing matches and nothing loads: `labels.empty`, text only. An action for the query is a `persistent` `CommandMenu.Item` at the end of the list.
|
|
140
140
|
|
|
141
141
|
| Prop | Type | Default | Description |
|
|
142
142
|
|---|---|---|---|
|
|
@@ -236,7 +236,6 @@ One look: the floating layer of the ladder (it is a Modal), `--prime-modal-radiu
|
|
|
236
236
|
|---|---|---|
|
|
237
237
|
| `search` | `"Поиск"` | Default placeholder and accessible name of `CommandMenu.Input`. |
|
|
238
238
|
| `empty` | `"Ничего не найдено"` | `CommandMenu.Empty`: nothing matches the query. |
|
|
239
|
-
| `emptyHint` | `"Попробуйте изменить запрос"` | Second line of `CommandMenu.Empty`; `""` hides it. |
|
|
240
239
|
| `loading` | `"Загрузка…"` | Announced while `loading` or `loadingMore`. |
|
|
241
240
|
| `retry` | `"Повторить"` | The retry button under `loadError` (`onRetry`). |
|
|
242
241
|
|
|
@@ -46,7 +46,7 @@ export default function CommandMenuStatesExample() {
|
|
|
46
46
|
onSearchChange={setSearch}
|
|
47
47
|
loading={state === "loading"}
|
|
48
48
|
aria-label="Выбор склада"
|
|
49
|
-
labels={{ empty: "
|
|
49
|
+
labels={{ empty: "В этом городе склада пока нет" }}
|
|
50
50
|
>
|
|
51
51
|
<CommandMenu.Input placeholder="Город склада" />
|
|
52
52
|
<CommandMenu.List>
|