prime-ui-kit 0.13.0 → 0.14.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.
Files changed (215) hide show
  1. package/README.md +7 -6
  2. package/dist/chunks/{chunk-ZSJMNA46.js → chunk-P6ZO37JM.js} +1 -1
  3. package/dist/chunks/{chunk-Z3WKKAS6.js → chunk-SYEX7OVT.js} +3572 -2865
  4. package/dist/chunks/{chunk-E6SQBSDF.js → chunk-WXKRUVSE.js} +199 -162
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/banner/Banner.d.ts +5 -2
  7. package/dist/components/chart-frame/ChartFrame.d.ts +19 -6
  8. package/dist/components/chat/Chat.d.ts +2 -2
  9. package/dist/components/chat/ChatPinned.d.ts +2 -2
  10. package/dist/components/chat/MessageList.d.ts +1 -1
  11. package/dist/components/chat/chatContext.d.ts +1 -1
  12. package/dist/components/field-list/FieldList.d.ts +90 -0
  13. package/dist/components/index.d.ts +3 -1
  14. package/dist/components/input/Input.d.ts +2 -2
  15. package/dist/components/kanban/Kanban.d.ts +6 -3
  16. package/dist/components/page-content/PageContent.d.ts +55 -4
  17. package/dist/components/sparkline/Sparkline.d.ts +64 -15
  18. package/dist/components/sparkline/geometry.d.ts +26 -0
  19. package/dist/components/tooltip/Tooltip.d.ts +5 -2
  20. package/dist/hooks/usePosition.d.ts +15 -4
  21. package/dist/index.css +1 -1
  22. package/dist/index.js +4 -2
  23. package/dist/internal/ChartTooltip.d.ts +26 -0
  24. package/dist/internal/FieldAction.d.ts +6 -7
  25. package/dist/internal/fieldCell.d.ts +21 -0
  26. package/dist/internal/messageList.d.ts +2 -2
  27. package/dist/internal/palette.d.ts +7 -0
  28. package/dist/skill/SKILL.md +24 -14
  29. package/dist/skill/anti-slop.md +77 -4
  30. package/dist/skill/cheatsheet.md +24 -5
  31. package/dist/skill/checklist.md +19 -1
  32. package/dist/skill/choosing.md +10 -5
  33. package/dist/skill/components.md +4 -3
  34. package/dist/skill/composition.md +28 -15
  35. package/dist/skill/foundations.md +8 -6
  36. package/dist/skill/layouts.md +6 -6
  37. package/dist/skill/patterns/app-shell.module.css +1 -8
  38. package/dist/skill/patterns/app-shell.tsx +11 -9
  39. package/dist/skill/patterns/chat.tsx +23 -5
  40. package/dist/skill/patterns/dashboard.module.css +1 -31
  41. package/dist/skill/patterns/dashboard.tsx +150 -136
  42. package/dist/skill/patterns/detail-page.module.css +0 -3
  43. package/dist/skill/patterns/detail-page.tsx +4 -7
  44. package/dist/skill/patterns/form-drawer.tsx +2 -2
  45. package/dist/skill/patterns/kanban-board.tsx +4 -2
  46. package/dist/skill/patterns/list-page.tsx +2 -2
  47. package/dist/skill/patterns/media-library.module.css +0 -22
  48. package/dist/skill/patterns/media-library.tsx +17 -18
  49. package/dist/skill/patterns/record-editor.module.css +18 -0
  50. package/dist/skill/patterns/record-editor.tsx +356 -0
  51. package/dist/skill/patterns/screen-states.module.css +3 -17
  52. package/dist/skill/patterns/screen-states.tsx +70 -69
  53. package/dist/skill/patterns/settings-page.module.css +1 -21
  54. package/dist/skill/patterns/settings-page.tsx +58 -79
  55. package/dist/skill/reference/components/avatar/COMPONENT.md +4 -4
  56. package/dist/skill/reference/components/avatar/examples/presence.tsx +1 -1
  57. package/dist/skill/reference/components/avatar/examples/states.tsx +1 -1
  58. package/dist/skill/reference/components/banner/COMPONENT.md +6 -6
  59. package/dist/skill/reference/components/banner/examples/dismissible.tsx +22 -11
  60. package/dist/skill/reference/components/banner/examples/examples.module.css +3 -3
  61. package/dist/skill/reference/components/banner/examples/narrow.tsx +1 -1
  62. package/dist/skill/reference/components/breadcrumb/COMPONENT.md +1 -1
  63. package/dist/skill/reference/components/breadcrumb/examples/overflow.tsx +1 -1
  64. package/dist/skill/reference/components/card/COMPONENT.md +5 -4
  65. package/dist/skill/reference/components/card/examples/examples.module.css +0 -28
  66. package/dist/skill/reference/components/card/examples/kpi-media.tsx +13 -11
  67. package/dist/skill/reference/components/card/examples/narrow.tsx +1 -1
  68. package/dist/skill/reference/components/card/examples/panel-chart.tsx +45 -22
  69. package/dist/skill/reference/components/chart-frame/COMPONENT.md +10 -7
  70. package/dist/skill/reference/components/chart-frame/examples/tooltip.tsx +20 -24
  71. package/dist/skill/reference/components/chat/COMPONENT.md +3 -3
  72. package/dist/skill/reference/components/chat/examples/search.tsx +1 -1
  73. package/dist/skill/reference/components/chat-list/COMPONENT.md +1 -1
  74. package/dist/skill/reference/components/checkbox/examples/indicator.tsx +24 -2
  75. package/dist/skill/reference/components/color-picker/COMPONENT.md +2 -2
  76. package/dist/skill/reference/components/color-swatches/COMPONENT.md +1 -0
  77. package/dist/skill/reference/components/datepicker/COMPONENT.md +1 -0
  78. package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
  79. package/dist/skill/reference/components/field-list/COMPONENT.md +164 -0
  80. package/dist/skill/reference/components/field-list/examples/base-row.tsx +76 -0
  81. package/dist/skill/reference/components/field-list/examples/controlled.tsx +72 -0
  82. package/dist/skill/reference/components/field-list/examples/errors.tsx +85 -0
  83. package/dist/skill/reference/components/field-list/examples/examples.module.css +34 -0
  84. package/dist/skill/reference/components/field-list/examples/limits.tsx +55 -0
  85. package/dist/skill/reference/components/field-list/examples/locked.tsx +139 -0
  86. package/dist/skill/reference/components/field-list/examples/narrow.tsx +68 -0
  87. package/dist/skill/reference/components/field-list/examples/overview.tsx +66 -0
  88. package/dist/skill/reference/components/field-list/examples/reorder.tsx +42 -0
  89. package/dist/skill/reference/components/field-list/examples/sizes.tsx +42 -0
  90. package/dist/skill/reference/components/field-list/examples/states.tsx +51 -0
  91. package/dist/skill/reference/components/input/examples/examples.module.css +2 -7
  92. package/dist/skill/reference/components/kanban/COMPONENT.md +13 -9
  93. package/dist/skill/reference/components/kanban/examples/limits.tsx +2 -1
  94. package/dist/skill/reference/components/kanban/examples/variants.tsx +16 -8
  95. package/dist/skill/reference/components/list/COMPONENT.md +1 -1
  96. package/dist/skill/reference/components/media-viewer/COMPONENT.md +2 -2
  97. package/dist/skill/reference/components/message/COMPONENT.md +2 -2
  98. package/dist/skill/reference/components/notification/COMPONENT.md +1 -0
  99. package/dist/skill/reference/components/page-content/COMPONENT.md +70 -14
  100. package/dist/skill/reference/components/page-content/examples/grid.tsx +107 -0
  101. package/dist/skill/reference/components/page-content/examples/overview.tsx +2 -2
  102. package/dist/skill/reference/components/page-content/examples/sections.tsx +81 -0
  103. package/dist/skill/reference/components/page-toolbar/COMPONENT.md +1 -1
  104. package/dist/skill/reference/components/segmented-control/COMPONENT.md +1 -0
  105. package/dist/skill/reference/components/select/COMPONENT.md +4 -2
  106. package/dist/skill/reference/components/select/examples/icon-only.tsx +38 -0
  107. package/dist/skill/reference/components/sparkline/COMPONENT.md +83 -30
  108. package/dist/skill/reference/components/sparkline/examples/compact.tsx +1 -1
  109. package/dist/skill/reference/components/sparkline/examples/curve.tsx +28 -0
  110. package/dist/skill/reference/components/sparkline/examples/gaps.tsx +33 -0
  111. package/dist/skill/reference/components/sparkline/examples/overview.tsx +11 -2
  112. package/dist/skill/reference/components/sparkline/examples/period.tsx +7 -2
  113. package/dist/skill/reference/components/sparkline/examples/reference.tsx +37 -0
  114. package/dist/skill/reference/components/sparkline/examples/variants.tsx +2 -2
  115. package/dist/skill/reference/components/tabs/examples/examples.module.css +0 -6
  116. package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +11 -13
  117. package/dist/skill/reference/components/timeline/examples/examples.module.css +0 -8
  118. package/dist/skill/reference/components/timeline/examples/narrow.tsx +2 -2
  119. package/dist/skill/reference/components/timeline/examples/sizes.tsx +3 -3
  120. package/dist/skill/reference/components/timeline/examples/variants.tsx +4 -4
  121. package/dist/skill/reference/components/tooltip/COMPONENT.md +6 -4
  122. package/dist/skill/reference/components/tooltip/examples/placement.tsx +2 -1
  123. package/dist/skill/reference/components/video-note/COMPONENT.md +1 -1
  124. package/dist/skill/reference/components/voice-player/COMPONENT.md +1 -1
  125. package/dist/skill/reference/layout/app-header/examples/in-app-shell.tsx +2 -2
  126. package/dist/skill/reference/layout/app-shell/COMPONENT.md +1 -1
  127. package/dist/skill/reference/layout/app-shell/examples/footer.tsx +2 -2
  128. package/dist/skill/reference/layout/app-shell/examples/overview.tsx +2 -2
  129. package/dist/skill/reference/layout/app-shell/examples/template.tsx +2 -2
  130. package/dist/skill/reference/layout/bottom-nav/examples/floating-action.tsx +2 -2
  131. package/dist/skill/reference/layout/bottom-nav/examples/floating.tsx +2 -2
  132. package/dist/skill/reference/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  133. package/dist/skill/responsive.md +5 -3
  134. package/dist/skill/ux.md +145 -0
  135. package/dist/tokens/semantic.d.ts +2 -0
  136. package/package.json +1 -1
  137. package/src/components/avatar/COMPONENT.md +4 -4
  138. package/src/components/avatar/examples/presence.tsx +1 -1
  139. package/src/components/avatar/examples/states.tsx +1 -1
  140. package/src/components/banner/COMPONENT.md +6 -6
  141. package/src/components/banner/examples/dismissible.tsx +22 -11
  142. package/src/components/banner/examples/examples.module.css +3 -3
  143. package/src/components/banner/examples/narrow.tsx +1 -1
  144. package/src/components/breadcrumb/COMPONENT.md +1 -1
  145. package/src/components/breadcrumb/examples/overflow.tsx +1 -1
  146. package/src/components/card/COMPONENT.md +5 -4
  147. package/src/components/card/examples/examples.module.css +0 -28
  148. package/src/components/card/examples/kpi-media.tsx +13 -11
  149. package/src/components/card/examples/narrow.tsx +1 -1
  150. package/src/components/card/examples/panel-chart.tsx +45 -22
  151. package/src/components/chart-frame/COMPONENT.md +10 -7
  152. package/src/components/chart-frame/examples/tooltip.tsx +20 -24
  153. package/src/components/chat/COMPONENT.md +3 -3
  154. package/src/components/chat/examples/search.tsx +1 -1
  155. package/src/components/chat-list/COMPONENT.md +1 -1
  156. package/src/components/checkbox/examples/indicator.tsx +24 -2
  157. package/src/components/color-picker/COMPONENT.md +2 -2
  158. package/src/components/color-swatches/COMPONENT.md +1 -0
  159. package/src/components/datepicker/COMPONENT.md +1 -0
  160. package/src/components/dnd/COMPONENT.md +3 -3
  161. package/src/components/field-list/COMPONENT.md +164 -0
  162. package/src/components/field-list/examples/base-row.tsx +76 -0
  163. package/src/components/field-list/examples/controlled.tsx +72 -0
  164. package/src/components/field-list/examples/errors.tsx +85 -0
  165. package/src/components/field-list/examples/examples.module.css +34 -0
  166. package/src/components/field-list/examples/limits.tsx +55 -0
  167. package/src/components/field-list/examples/locked.tsx +139 -0
  168. package/src/components/field-list/examples/narrow.tsx +68 -0
  169. package/src/components/field-list/examples/overview.tsx +66 -0
  170. package/src/components/field-list/examples/reorder.tsx +42 -0
  171. package/src/components/field-list/examples/sizes.tsx +42 -0
  172. package/src/components/field-list/examples/states.tsx +51 -0
  173. package/src/components/input/examples/examples.module.css +2 -7
  174. package/src/components/kanban/COMPONENT.md +13 -9
  175. package/src/components/kanban/examples/limits.tsx +2 -1
  176. package/src/components/kanban/examples/variants.tsx +16 -8
  177. package/src/components/list/COMPONENT.md +1 -1
  178. package/src/components/media-viewer/COMPONENT.md +2 -2
  179. package/src/components/message/COMPONENT.md +2 -2
  180. package/src/components/notification/COMPONENT.md +1 -0
  181. package/src/components/page-content/COMPONENT.md +70 -14
  182. package/src/components/page-content/examples/grid.tsx +107 -0
  183. package/src/components/page-content/examples/overview.tsx +2 -2
  184. package/src/components/page-content/examples/sections.tsx +81 -0
  185. package/src/components/page-toolbar/COMPONENT.md +1 -1
  186. package/src/components/segmented-control/COMPONENT.md +1 -0
  187. package/src/components/select/COMPONENT.md +4 -2
  188. package/src/components/select/examples/icon-only.tsx +38 -0
  189. package/src/components/sparkline/COMPONENT.md +83 -30
  190. package/src/components/sparkline/examples/compact.tsx +1 -1
  191. package/src/components/sparkline/examples/curve.tsx +28 -0
  192. package/src/components/sparkline/examples/gaps.tsx +33 -0
  193. package/src/components/sparkline/examples/overview.tsx +11 -2
  194. package/src/components/sparkline/examples/period.tsx +7 -2
  195. package/src/components/sparkline/examples/reference.tsx +37 -0
  196. package/src/components/sparkline/examples/variants.tsx +2 -2
  197. package/src/components/tabs/examples/examples.module.css +0 -6
  198. package/src/components/tabs/examples/in-drawer.tsx +11 -13
  199. package/src/components/timeline/examples/examples.module.css +0 -8
  200. package/src/components/timeline/examples/narrow.tsx +2 -2
  201. package/src/components/timeline/examples/sizes.tsx +3 -3
  202. package/src/components/timeline/examples/variants.tsx +4 -4
  203. package/src/components/tooltip/COMPONENT.md +6 -4
  204. package/src/components/tooltip/examples/placement.tsx +2 -1
  205. package/src/components/video-note/COMPONENT.md +1 -1
  206. package/src/components/voice-player/COMPONENT.md +1 -1
  207. package/src/layout/app-header/examples/in-app-shell.tsx +2 -2
  208. package/src/layout/app-shell/COMPONENT.md +1 -1
  209. package/src/layout/app-shell/examples/footer.tsx +2 -2
  210. package/src/layout/app-shell/examples/overview.tsx +2 -2
  211. package/src/layout/app-shell/examples/template.tsx +2 -2
  212. package/src/layout/bottom-nav/examples/floating-action.tsx +2 -2
  213. package/src/layout/bottom-nav/examples/floating.tsx +2 -2
  214. package/src/layout/bottom-nav/examples/in-app-shell.tsx +2 -2
  215. package/src/styles/theme-light.css +1 -0
package/dist/index.js CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  Drawer,
28
28
  Dropdown,
29
29
  ExampleFrame,
30
+ FieldList,
30
31
  Fieldset,
31
32
  FileUpload,
32
33
  Gallery,
@@ -85,7 +86,7 @@ import {
85
86
  useSidebar,
86
87
  useTokenValue,
87
88
  useUploadQueue
88
- } from "./chunks/chunk-Z3WKKAS6.js";
89
+ } from "./chunks/chunk-SYEX7OVT.js";
89
90
  import {
90
91
  Avatar,
91
92
  Badge,
@@ -117,7 +118,7 @@ import {
117
118
  textTones,
118
119
  tones,
119
120
  variants
120
- } from "./chunks/chunk-E6SQBSDF.js";
121
+ } from "./chunks/chunk-WXKRUVSE.js";
121
122
  import {
122
123
  ControlSizeProvider,
123
124
  useOptionalControlSize
@@ -158,6 +159,7 @@ export {
158
159
  Dropdown,
159
160
  EmptyPage,
160
161
  ExampleFrame,
162
+ FieldList,
161
163
  Fieldset,
162
164
  FileUpload,
163
165
  Gallery,
@@ -0,0 +1,26 @@
1
+ import * as React from "react";
2
+ import type { AnchorPoint } from "../hooks/usePosition";
3
+ export type { AnchorPoint };
4
+ export type ChartTooltipProps = React.HTMLAttributes<HTMLDivElement> & {
5
+ /** The point the content belongs to («8 окт», «Неделя 41»), over the content. */
6
+ label?: React.ReactNode;
7
+ /**
8
+ * Floats the tooltip beside a point of `anchorRef` (px from its top-left corner) — the pointer
9
+ * over the plot, or the point the arrow keys reached; `null` hides it. Without `anchorRef` the
10
+ * body renders in place (a chart library positions it).
11
+ */
12
+ point?: AnchorPoint | null;
13
+ /** The plot the point lies in: its scroll and size changes move the tooltip with it. */
14
+ anchorRef?: React.RefObject<HTMLElement | null>;
15
+ /** Escape hid the floating tooltip (it stays hidden until the point moves). */
16
+ onDismiss?: () => void;
17
+ ref?: React.Ref<HTMLDivElement>;
18
+ };
19
+ /**
20
+ * The one chart tooltip of the kit (ChartFrame.Tooltip, Sparkline): its look, and — with
21
+ * `anchorRef` — its place. A floating tooltip is an anchored layer like every other
22
+ * (`useFloatingLayer` + `usePosition` with `anchorPoint`): right of the point, vertically centred,
23
+ * flipped to the left only where the right has no room, kept inside the viewport, passive (no
24
+ * focus, no presses) and aria-hidden — the chart speaks its points itself.
25
+ */
26
+ export declare function ChartTooltip({ anchorRef, ...props }: ChartTooltipProps): import("react/jsx-runtime").JSX.Element;
@@ -1,10 +1,4 @@
1
1
  import * as React from "react";
2
- /**
3
- * A button segment at a field's edge (Input, Textarea): full field height, the icon centred with the
4
- * field padding on both sides, a hover wash over the whole segment, the touch hit area. The field
5
- * hosts style it through `data-field-action`.
6
- */
7
- export declare const fieldActionClass: string;
8
2
  export type FieldActionButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "aria-label" | "children"> & {
9
3
  /** Accessible name and tooltip («Позвонить», «Открыть профиль»). */
10
4
  label: string;
@@ -14,8 +8,13 @@ export type FieldActionButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonE
14
8
  children: React.ReactNode;
15
9
  ref?: React.Ref<HTMLButtonElement>;
16
10
  };
11
+ /**
12
+ * A segment without a tooltip, for a kit control whose glyph says it all (Input's clear and
13
+ * reveal): the name stays accessible, nothing floats over the field.
14
+ */
15
+ export declare function FieldActionSegment(props: FieldActionButtonProps): import("react/jsx-runtime").JSX.Element;
17
16
  /** A named icon action at the field's edge with a tooltip. */
18
- export declare function FieldActionButton({ label, asChild, className, disabled, type, children, ref, ...rest }: FieldActionButtonProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function FieldActionButton(props: FieldActionButtonProps): import("react/jsx-runtime").JSX.Element;
19
18
  export type CopyState = "idle" | "copied" | "error";
20
19
  export type FieldCopyLabels = {
21
20
  /** Name and tooltip of the copy button. */
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ /**
3
+ * The contract between `FieldList` and the fields in its cells. A cell provides its column heading;
4
+ * a framed field without a `label` of its own takes it as its label (and keeps its own `label`, if
5
+ * any), so every field in a row has a name without a visible label per row. The column heading
6
+ * shows the name once above the rows; the field's label is read, not shown, until the list stacks
7
+ * its rows into blocks in a narrow container (`FIELD_LIST_CONTAINER`), where each field shows it.
8
+ */
9
+ export type FieldCell = {
10
+ /** The column heading: the name of the field in this cell. */
11
+ label: string;
12
+ /**
13
+ * `wide` — hidden while the column heading is on screen, shown when the rows stack; `always` —
14
+ * a one-column list, named by the list's own label, shows none in either layout.
15
+ */
16
+ hidden: "wide" | "always";
17
+ };
18
+ export declare const FieldCellContext: React.Context<FieldCell | null>;
19
+ /** Name of the size container `FieldList` sets; FieldFrame shows a cell label under its query. */
20
+ export declare const FIELD_LIST_CONTAINER = "prime-field-list";
21
+ export declare function useFieldCell(): FieldCell | null;
@@ -43,8 +43,8 @@ export type MessageListContextValue = {
43
43
  /** A message was new at the end in this commit. */
44
44
  arrivedNow: () => boolean;
45
45
  /**
46
- * Brings a message into view and points at it (unless `flash` is `false`: the others blink
47
- * around it); `focus` moves the focus to it too.
46
+ * Brings a message into view and blinks it (unless `flash` is `false`); `focus`
47
+ * moves the focus to it too.
48
48
  */
49
49
  reveal: (value: string, focus?: boolean, flash?: boolean) => boolean;
50
50
  /** The selection; read it through `useMessageSelection` / `useListSelection`. */
@@ -1,4 +1,11 @@
1
1
  import { type PaletteColor } from "./states";
2
+ /**
3
+ * A palette hue as a data mark: its step of the categorical chart palette, so a category has one
4
+ * color in charts and in shares. The one source of `--hue-chart` in `palette.module.css` (kept equal
5
+ * by `palette.test.ts`); as strings here, because a chart library takes its colors in JS. Gray is
6
+ * `chart-other`; sky has no chart step (it sits too close to blue) and keeps its solid.
7
+ */
8
+ export declare const HUE_CHART: Record<PaletteColor, string>;
2
9
  /**
3
10
  * A palette hue for a person, stable per `id`: the same id always gets the same hue, on every
4
11
  * render and every device (a string hash, no state). For names in a group chat and initials of
@@ -6,10 +6,12 @@ description: >
6
6
  navigation on prime-ui-kit; when
7
7
  choosing which kit component fits a task; when a needed component is missing and must be composed from
8
8
  the kit; when adding loading, empty and error states, feedback, motion or micro-animation to a screen;
9
- and when reviewing a screen for design-system compliance (spacing rhythm, sizes, tokens, states,
10
- motion, a11y, narrow screens). Triggers: build a screen, page, form, table, board, schedule, chat,
11
- uploads, settings, dashboard, navigation; which component to use; animate, transition, loading state; prime-ui-kit; review a screen
12
- against the design system — in any language.
9
+ when changing or redesigning a screen that exists; and when reviewing a screen for design-system and
10
+ UX compliance (data shape and layout, repeated or redundant labels, copy, friction, spacing rhythm,
11
+ sizes, tokens, states, motion, a11y, narrow screens). Triggers: build a screen, page, form, table,
12
+ board, schedule, chat, uploads, settings, dashboard, navigation; repeating fields, add another row;
13
+ which component to use; animate, transition, loading state; prime-ui-kit; improve, fix or review a
14
+ screen against the design system — in any language.
13
15
  ---
14
16
 
15
17
  # prime-ui-kit
@@ -24,25 +26,31 @@ region, `Skeleton` while it loads).
24
26
 
25
27
  0. **First** — [cheatsheet.md](cheatsheet.md): one page of the decisions agents most often get wrong,
26
28
  each as «do this, not that» with the exact API. Then follow the steps in order; each names its file.
27
- 1. **The app frame, once per app** — [layouts.md](layouts.md): styles, theme, providers, `AppShell`
29
+ Changing a screen that exists: audit it first ([ux.md](ux.md#8-changing-a-screen-someone-built)),
30
+ then make the change.
31
+ 1. **Brief** — [ux.md](ux.md): five lines before any JSX (who and what for, the data and its shape,
32
+ the main action, the states, the narrow screen); the shape of the data decides the layout, every
33
+ label must carry something new, friction matches the cost of a mistake.
34
+ 2. **The app frame, once per app** — [layouts.md](layouts.md): styles, theme, providers, `AppShell`
28
35
  with `Sidebar` and `AppHeader`, page wrappers, auth screens. A screen you deliver renders only its page.
29
- 2. **Choose** — [choosing.md](choosing.md): the category by task, then the component, then the pairs
36
+ 3. **Choose** — [choosing.md](choosing.md): the category by task, then the component, then the pairs
30
37
  that are easy to confuse.
31
- 3. **Read the component** — [components.md](components.md) links each `COMPONENT.md` (API, variants,
38
+ 4. **Read the component** — [components.md](components.md) links each `COMPONENT.md` (API, variants,
32
39
  states, a11y, mistakes) and its `examples/`. Never guess a prop; copy structure from an example.
33
- 4. **Compose the screen** — [composition.md](composition.md): start from the closest working screen in
40
+ 5. **Compose the screen** — [composition.md](composition.md): start from the closest working screen in
34
41
  [patterns/](patterns/) and keep its skeleton; rhythm, hierarchy, surfaces and type in
35
42
  [foundations.md](foundations.md).
36
- 5. **Write the JSX** — [api-contract.md](api-contract.md): prop names, controlled state, events (async
43
+ 6. **Write the JSX** — [api-contract.md](api-contract.md): prop names, controlled state, events (async
37
44
  events return the request's Promise), forms and their submit flow (`Fieldset disabled`), uploads
38
45
  (`useUploadQueue`), destructive actions (`ConfirmDialog`), helpers (`groupMessages` for chat rows you build yourself),
39
46
  icon names.
40
- 6. **States and motion** — [motion.md](motion.md): every region loads, empties and fails without
47
+ 7. **States and motion** — [motion.md](motion.md): every region loads, empties and fails without
41
48
  flipping; what the kit animates by itself and when to move your own elements.
42
- 7. **Narrow screens** — [responsive.md](responsive.md): every screen works from 320px, the page panel is
49
+ 8. **Narrow screens** — [responsive.md](responsive.md): every screen works from 320px, the page panel is
43
50
  `PageToolbar`, nothing is hidden on phones, touch follows `(hover)` / `(pointer)`.
44
- 8. **Check** — [checklist.md](checklist.md) and [anti-slop.md](anti-slop.md). Fix every «no», then hand
45
- over.
51
+ 9. **Check** — [checklist.md](checklist.md) and [anti-slop.md](anti-slop.md), then look at the screen
52
+ in a browser: both themes, 1280 and 375 px ([ux.md](ux.md#9-look-at-it-before-handing-it-over)). Fix
53
+ every «no», then hand over.
46
54
 
47
55
  ## Where things are
48
56
 
@@ -63,6 +71,7 @@ project under `node_modules/prime-ui-kit/src/`.
63
71
  | File | Read when |
64
72
  |---|---|
65
73
  | [cheatsheet.md](cheatsheet.md) | first — the most frequent decisions and mistakes in one page |
74
+ | [ux.md](ux.md) | before the layout of every screen and before changing one — the brief, data shape → layout, label economy, copy, friction, defaults, the visual check |
66
75
  | [layouts.md](layouts.md) | once per app — the app frame, page wrappers, auth screens |
67
76
  | [choosing.md](choosing.md) | unsure which component fits (Gantt / Timeline / Kanban / DataTable, Message / Composer, FileUpload list / grid / gallery / strip, ColorField / ColorPicker…); how to confirm a destructive action |
68
77
  | [components.md](components.md) | finding a component and its `COMPONENT.md` / `examples/` |
@@ -110,7 +119,8 @@ Take a semantic `<Icon name="…" />` first, else a glyph from the full animated
110
119
  5. Air is hierarchy: tighter inside a group, wider between groups, widest between sections — spacing via
111
120
  `gap` on the parent, never margins.
112
121
  6. One primary action per area. The rest are `soft`/`ghost`/`outline` with `tone="neutral"`.
113
- 7. Labels above fields, always. Placeholder is an example value, never the label.
122
+ 7. Labels above fields, always — once per column for records in rows (`FieldList`), never numbered
123
+ («Пункт 1»), never repeating their heading. Placeholder is an example value, never the label.
114
124
  8. Text through `Typography` roles (`title-m` section, `title-s` group, `body-m` text, `caption` meta;
115
125
  the page title is `PageContent.Title`). No custom font sizes.
116
126
  9. Overlays (Modal, ConfirmDialog, Drawer, Sheet, Popover, Dropdown, Tooltip, CommandMenu, Select,
@@ -48,9 +48,10 @@ Text color goes through `Typography tone="secondary"`, not CSS.
48
48
  // bad — a card around a card around a table
49
49
  <Card.Root><Card.Root><DataTable columns={columns} rows={rows} /></Card.Root></Card.Root>
50
50
 
51
- // good — DataTable already is a filled block; a section heading is enough
52
- <Typography as="h2" variant="title-m">Последние оплаты</Typography>
53
- <DataTable columns={columns} rows={rows} />
51
+ // good — DataTable already is a filled block; a titled section is enough
52
+ <PageContent.Section label="Последние оплаты">
53
+ <DataTable columns={columns} rows={rows} />
54
+ </PageContent.Section>
54
55
  ```
55
56
 
56
57
  ## 3a. Hover that merges with the neighbour
@@ -106,6 +107,78 @@ and inside a card, in both themes: rest, hover and the neighbour must be three d
106
107
 
107
108
  A search field in a toolbar may go without a visible label, but then it needs `aria-label`.
108
109
 
110
+ ## 5a. A label in every row, a record split over lines
111
+
112
+ The most common shape bug of generated forms: rules for one field applied to a list of records.
113
+
114
+ ```tsx partial
115
+ // bad — «Иконка · Параметр · Значение · Цвет» repeated in every record, a record on two lines,
116
+ // numbered labels, a hint that explains the lock
117
+ {specs.map((spec, index) => (
118
+ <div key={spec.id} className={styles.grid}>
119
+ <Select.Root label="Иконка">…</Select.Root>
120
+ <Input.Root label={`Параметр ${index + 1}`}>…</Input.Root>
121
+ <Input.Root label="Значение">…</Input.Root>
122
+ <ColorField label="Цвет иконки" />
123
+ </div>
124
+ ))}
125
+ <Typography variant="body-s">Первые три всегда первыми. Остальные можно перетаскивать.</Typography>
126
+
127
+ // good — one row per record, the headings once, compact columns for the small attributes
128
+ <FieldList
129
+ label="Характеристики"
130
+ columns={[
131
+ { label: "Иконка", width: "compact" },
132
+ { label: "Параметр" },
133
+ { label: "Значение" },
134
+ { label: "Цвет", width: "compact" },
135
+ ]}
136
+ reorderable
137
+ getLocked={(spec) => spec.pinned}
138
+ value={specs}
139
+ onValueChange={setSpecs}
140
+ getId={(spec) => spec.id}
141
+ getLabel={(spec) => spec.name}
142
+ createItem={newSpec}
143
+ addLabel="Добавить характеристику"
144
+ renderItem={(spec, row) => (
145
+ <>
146
+ <Select.Root value={spec.icon} onValueChange={(icon) => row.update({ icon })}>
147
+ <Select.Trigger>
148
+ <Select.TriggerIcon><Icon name={spec.icon} /></Select.TriggerIcon>
149
+ </Select.Trigger>
150
+ <Select.Content>…</Select.Content>
151
+ </Select.Root>
152
+ <Input.Root>…</Input.Root>
153
+ <Input.Root>…</Input.Root>
154
+ <ColorPresets.Root value={spec.color} onValueChange={…}>…</ColorPresets.Root>
155
+ </>
156
+ )}
157
+ />
158
+ ```
159
+
160
+ The same for one value in several units — one heading, the fields in a row, the unit inside:
161
+
162
+ ```tsx partial
163
+ // bad — three labels, the currency in the label and again in the field, one field on a line of its own
164
+ <Input.Root label="Депозит, $" optional>…<Input.InlineAffix side="end">$</Input.InlineAffix>…</Input.Root>
165
+ <Input.Root label="Депозит, ฿" optional>…</Input.Root>
166
+ <Input.Root label="Депозит, ₽" optional>…</Input.Root>
167
+
168
+ // good — a group heading, three fields in one row, each named for assistive tech
169
+ <Fieldset label="Депозит">
170
+ <div className={styles.row}>
171
+ <Input.Root>
172
+ <Input.Wrapper>
173
+ <Input.Field aria-label="Депозит в долларах" inputMode="decimal" />
174
+ <Input.InlineAffix side="end">$</Input.InlineAffix>
175
+ </Input.Wrapper>
176
+ </Input.Root>
177
+ {/* ฿ and ₽ the same */}
178
+ </div>
179
+ </Fieldset>
180
+ ```
181
+
109
182
  ## 6. Everything on one level of air
110
183
 
111
184
  ```css
@@ -115,7 +188,7 @@ A search field in a toolbar may go without a visible label, but then it needs `a
115
188
  /* good — proximity scale */
116
189
  .form { display: grid; gap: var(--prime-space-8); } /* group → group 32 */
117
190
  .group { display: grid; gap: var(--prime-space-5); } /* field → field 20 */
118
- /* section → section 40 comes from PageContent.Body */
191
+ /* block → block 24 and section → section 40 come from PageContent.Body / PageContent.Section */
119
192
  ```
120
193
 
121
194
  ## 7. `div` instead of a ready component
@@ -39,6 +39,10 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
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
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.
41
41
  - 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
+ - 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
+ - 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
+ - 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.
45
+ - 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.
42
46
 
43
47
  ## State
44
48
 
@@ -76,7 +80,8 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
76
80
 
77
81
  - `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.
78
82
  - Never a card in a card, and never a `DataTable` inside a Card — the table is already a filled block.
79
- - Section without a card: `<Typography as="h2" variant="title-m">` + the content.
83
+ - Section without a card: `<PageContent.Section label="…">` + the content (a link or button beside the heading in `PageContent.Actions`) — not a hand-made `<section>` + `Typography as="h2"` + own gap.
84
+ - The tiles of a screen (KPI cards, charts, panels): one `<PageContent.Grid>` with a `<PageContent.Cell span>` per tile — not an `auto-fit` grid per row (the rows would stand 24 apart instead of 16).
80
85
  - Depth from fill, not lines: no borders around blocks; `<Divider />` only inside a block.
81
86
  - 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.
82
87
  - 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.
@@ -90,13 +95,14 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
90
95
  - Data the server sorts and pages (thousands of records): `source="server"` + `rowCount`, controlled `sort` / `page` as the request, `page` back to 1 on a new search or filter, old rows kept with `loading` while the next page loads — not a server page passed to the default client mode, and not `rows={[]}` between pages.
91
96
  - Categories (sources, platforms, regions) get `color` and the same order of `chartColors` in charts; statuses and meanings get `tone` — never paint categories with tones.
92
97
  - A chart with axes or several series: `<ChartFrame.Root series state>` around the library (or your SVG) with `ChartFrame.Plot renderPlot`, `Legend`, `Table` — not a hand-made Card with a grey box while loading and a legend of colored dots.
98
+ - 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.
93
99
  - Amounts, counts and dates are `numeric` columns; statuses are `Badge.Root color` from one status → color map.
94
100
  - 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.
95
101
  - 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`.
96
102
  - 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.
97
- - A trend in a table row is `<Sparkline layout="compact">` in the column's `cell` (a column `width`, series built once, not inline); the number itself goes in its own column.
103
+ - 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`.
98
104
  - Properties of one record: `<DescriptionList.Root>` of `DescriptionList.Item` — not a hand-made grid of label / value text, not a one-row DataTable.
99
- - 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`.
105
+ - 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.
100
106
  - Open a board card with `Kanban.Root onItemClick` (click, Enter, Space, never after a drag) or `getItemHref`; a Gantt row with `onRowClick` / `getRowHref` (the DataTable contract), a bar with `onSelect`; 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.
101
107
 
102
108
  ## Chat
@@ -113,7 +119,7 @@ reference is each component's `COMPONENT.md` ([components.md](components.md)), t
113
119
  - «печатает…» on the other side: `Composer onActivityChange` (`typing` / `voice` / `video` / `null`, on a change only) → your socket; on the receiving side `<Chat.Root typing={activity === "typing"}>` (the header's status and the list's typing bubble together). Never send it from `onValueChange`.
114
120
  - Appearance: messages that are new at the end enter by themselves — mine and theirs float up a little from below and fade in; the first render, history loaded above and rows scrolled into view never animate. Do not add your own enter animation or `scrollTop = scrollHeight`.
115
121
  - `Chat.Messages` scrolls to my message, stays at the end for theirs while the reader is there, and otherwise shows the «N новых» button; `onNewMessage(value, { direction })` is for a sound or a counter, `onRead(values)` for read receipts (only what was really seen). The first unread is `unread={key}`; older history is `hasOlder` + `onLoadOlder`.
116
- - Jump to a message: `Message.Reply target={id}`, a press on `Chat.Pinned`, or `Chat.Root current={id}` (the chat scrolls to it and the other messages blink back around it); search: `Chat.SearchButton` + `Chat.Search` mark, count and step through the matches in texts and transcripts, keeping the others stepped back. Never an outline or a wash of your own on the target or on selected messages. Never `scrollIntoView` or your own transforms on rows.
122
+ - Jump to a message: `Message.Reply target={id}`, a press on `Chat.Pinned`, or `Chat.Root current={id}` (the chat scrolls to it and the message blinks); search: `Chat.SearchButton` + `Chat.Search` mark, count and step through the matches in texts and transcripts, keeping the others stepped back. Never an outline or a wash of your own on the target or on selected messages. Never `scrollIntoView` or your own transforms on rows.
117
123
  - A long history (thousands of messages): nothing to switch on — `Chat.Messages` renders only the rows around the view and keeps the reader's place. With children, every top-level child is one row (a `Message.Root`, or a fragment / your own row component keyed by the message `value`); give pictures their `width` / `height` so a row's size is final before load; jumps, search, selection and the keyboard still reach every message. Not a hand-rolled window, `content-visibility` on rows or a measured bubble.
118
124
  - A message's layout is CSS: the bubble fits its content and the time sits at the end of the last line (or of the reactions row). Every size change of a message (reactions, a transcript, an edit, an upload) plays as one built-in stroke, width and height together; a reaction grows the message upward with the time still, the rows above gliding as one block. Never size, measure or animate a bubble yourself; an own content part declares itself with the kit's part contract instead of a wrapper.
119
125
  - One sound at a time is built in: a voice message, a video message and a chat video pause each other; `playing` / `onPlayingChange` on `VoicePlayer` or `VideoNote` when the app needs to know. A round video never plays or loops before a press. A recorded voice message without levels gets them from `getWaveform(blob)` once, stored with the message.
@@ -183,10 +189,23 @@ Full rules: [responsive.md](responsive.md).
183
189
 
184
190
  ## Spacing and values
185
191
 
186
- - Gaps are `gap` on the parent with `--prime-space-N` (N × 4px): 8 in a row of buttons, 20 field → field, 32 group → group; `PageContent.Body` already spaces blocks 40 — not margins, not outer padding.
192
+ - Gaps are `gap` on the parent with `--prime-space-N` (N × 4px): 8 in a row of buttons, 16 tile → tile, 20 field → field, 32 group → group; `PageContent` already spaces blocks 24 and sections 40 — not margins, not outer padding.
187
193
  - Tokens only: `var(--prime-space-4)`, `var(--prime-color-text-secondary)`, `var(--prime-radius-m)` — never raw `px` / `rem` / hex, never `--prime-ref-*`, never inline `style`.
188
194
  - Text through `Typography` roles (`title-m`, `title-s`, `body-m`, `caption`) and `tone="secondary" | "muted"` — not `font-size` or `color` in CSS.
189
195
 
196
+ ## UX — what goes on the screen
197
+
198
+ - Five lines of brief before the JSX: who and what for, the data and its shape, the main action, the states, the narrow screen ([ux.md](ux.md)) — not a layout picked by habit.
199
+ - The shape of the data picks the layout: one record → a form or a `DescriptionList`; records a person edits → `FieldList`; records a person reads → `DataTable` ([the table](ux.md#2-the-shape-of-the-data-decides-the-layout)).
200
+ - Every label carries something new: once per column, never numbered, never repeating its heading; no hint that explains what the layout shows (a lock already says «закреплено»).
201
+ - Buttons say the verb and the object («Выставить счёт»), errors say how to fix it («Формат: +7 912 404-18-20»), empty states offer the next step — not «ОК», «Ошибка», «Нет данных».
202
+ - Money, numbers and dates through `Intl` (`ru-RU`); «ёлочки», `…`, a non-breaking space before a unit — not `toFixed` and string glue.
203
+ - Friction matches the cost: a reversible action at once with «Отменить» in a `Notification`; `ConfirmDialog` only for irreversible or weighty ones, naming the thing and the loss — not «Вы уверены?» on every delete.
204
+ - A disabled control says why when the reason is not on screen; an action without permission is hidden.
205
+ - Tabs, filters, search, page and the open record live in the URL.
206
+ - Changing a screen that exists: audit it against [ux.md](ux.md), [checklist.md](checklist.md) and [anti-slop.md](anti-slop.md) first, fix the shape, then make the change.
207
+ - Before handing over, look at it in a browser: both themes, 1280 and 375 px, every text read aloud.
208
+
190
209
  ## Anti-slop
191
210
 
192
211
  The whole list, bad → good: [anti-slop.md](anti-slop.md). Before handing over: [checklist.md](checklist.md).
@@ -8,10 +8,28 @@ Answer every item yes or no. Any «no» is fixed before handing over.
8
8
  - [ ] One `PageContent.Header` with title, one-line description and actions; blocks in `PageContent.Body`.
9
9
  - [ ] Cards only for standalone blocks; no card around a table, a single field or the whole page.
10
10
 
11
+ ## UX ([ux.md](ux.md))
12
+ - [ ] I wrote the brief first: who and what for, the data and its shape, the main action, the states, the narrow screen.
13
+ - [ ] Every entity is laid out by its shape: one record is one row or one block, never split over lines;
14
+ records a person edits are a `FieldList`, records a person reads a `DataTable`.
15
+ - [ ] Labels on a form of rows ≤ columns + the list label: no label repeated per row, none numbered
16
+ («Пункт 1»), none repeating its heading; a unit in one place (the field's affix).
17
+ - [ ] No text that explains the layout; every hint says what the person cannot see.
18
+ - [ ] Buttons say the verb and the object; errors say how to fix it; empty states offer the next step.
19
+ - [ ] Money, numbers and dates through `Intl` with `ru-RU`; «ёлочки», `…`, a non-breaking space before units.
20
+ - [ ] Friction matches the cost: reversible → do it and offer «Отменить»; irreversible → `ConfirmDialog`
21
+ naming the thing and the loss; bulk → the count.
22
+ - [ ] A disabled control says why when the reason is off screen; an action without permission is hidden.
23
+ - [ ] Defaults prefill what the system knows; each field has the control and `inputMode` its data needs;
24
+ rare settings sit behind a disclosure.
25
+ - [ ] Tabs, filters, search, page and the open record are in the URL.
26
+ - [ ] I looked at the screen in a browser: both themes, 1280 and 375 px, the longest real values, an
27
+ empty list, an error on every field.
28
+
11
29
  ## Grid and spacing
12
30
  - [ ] Every spacing, size and gap in my CSS is a `--prime-space-*` (or another `--prime-*`) token on the 4px grid.
13
31
  - [ ] Spacing is `gap` on the parent; no margins on children; no outer padding duplicating `AppShell.Main` / `PageContent.Body`.
14
- - [ ] Proximity holds: field→field 20, inline controls 8, group→group 32, section→section 40.
32
+ - [ ] Proximity holds: inline controls 8, tile→tile 16 (one `PageContent.Grid`), field→field 20, block→block 24, group→group 32, section→section 40 (`PageContent.Section`).
15
33
  - [ ] Inside a group things are visibly closer than between groups.
16
34
 
17
35
  ## Sizes and alignment
@@ -8,7 +8,7 @@ confuse. Links: [components.md](components.md). Whole screens: [composition.md](
8
8
  | The user needs to… | Category |
9
9
  |---|---|
10
10
  | trigger an action (save, delete, go on) | Actions — Button, ButtonGroup, LinkButton |
11
- | type a value (text, number, code, file, photos), group the fields of a form | Inputs — Input, Textarea, DigitInput, FileUpload, Label, Hint, Fieldset |
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
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 |
14
14
  | hold a conversation: pick a dialog, read and send chat messages of every kind | Messages — Chat, ChatList, Message, Gallery, VoicePlayer, VideoNote, Composer |
@@ -33,7 +33,8 @@ Input with `Input.InlineAffix`. Search and filters above a list or table → Sma
33
33
  (`fields={[]}`); a lone search
34
34
  field outside a list (a picker, a settings section, a panel) → Input with `Input.Icon` + `type="search"`
35
35
  and `aria-label`. Several fields of a form under one heading → Fieldset (`label`, `disabled` while the
36
- form saves).
36
+ form saves). Several records of the same fields that a person adds, edits and removes (invoice lines,
37
+ contacts, specs, prices by period) → FieldList: one row per record, the headings once.
37
38
 
38
39
  **Selection.**
39
40
 
@@ -62,12 +63,15 @@ numbers/content → Card (pick the template; a whole tile that opens its details
62
63
  with a link). Read-only properties of one record («label — value») → DescriptionList, never a
63
64
  hand-made grid of text pairs. Rows with columns, sorting, selection → DataTable.
64
65
  Items that flow through statuses and are moved by hand → Kanban (a single list to reorder, or items
65
- dropped onto folders or assignees → Dnd directly).
66
+ dropped onto folders or assignees → Dnd directly). Its look: `plain` (default; `line` for a quieter header over a line) when the board is
67
+ the page's main region or sits in a Card / Drawer — headers and cards stand on the host, one level of
68
+ nesting; `variant="well"` when columns must read as bounded queues (WIP limits, many sparse or empty
69
+ columns, a dense board of narrow columns). Never your own box around a column.
66
70
  Records with a start and an end compared across time — who is busy when, a project plan with
67
71
  phases and milestones → Gantt (never a DataTable of dates or hand-drawn bars); moved and resized by
68
72
  hand through `onBarChange`.
69
73
  Chronological events → Timeline; a conversation between people (support, a team chat) → Message —
70
- 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"`; 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
+ 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.
71
75
 
72
76
  **Messages.** A whole conversation → `Chat`: `Chat.Root` with `Chat.Header` (avatar, `Chat.Title`,
73
77
  `Chat.Description`, `Chat.Actions` with `Chat.SearchButton`), `Chat.Search`, `Chat.Pinned`,
@@ -138,6 +142,7 @@ collapsible`; a section with sub-pages → `Sidebar.Sub`; the signed-in user →
138
142
  | Pair | Rule |
139
143
  |---|---|
140
144
  | List / Sidebar / Tabs / Card list / DataTable | List: the records of a list + detail screen (dialogs, tickets, mail), one open at a time (`current`), each row a button or a link with a text line, a preview, meta and a count. Sidebar: the sections of the **app**. Tabs: panels of **one screen**. `Card.List` (`variant="list"`): static facts in a card, nothing opens. DataTable: many records compared by columns, sorted, filtered, selected in bulk. |
145
+ | FieldList / DataTable / Fieldset / TagSelect | FieldList: records of one shape a person **edits** in a form, a row of fields each, added and removed by hand, up to four short fields per row. DataTable: records a person **reads**, sorts, filters and selects; edits open a Drawer. Fieldset: **one** record's fields under a heading. TagSelect: values **picked** from options, shown as tags. One value in several units is none of them — fields in one row under one heading. |
141
146
  | Tabs / SegmentedControl | Tabs switch **panels of content** (navigation inside a screen). SegmentedControl switches a **value or mode** of the same content (period, view mode, filter). |
142
147
  | Modal / Drawer / Sheet / Popover / Tooltip | Modal: blocking decision or a short form (≤ 4 fields) that needs full attention. Drawer: long form, filters, record details beside the page, while keeping page context. Sheet: a short task that ends by itself — a pick, an action, a short note — raised from the bottom edge. Popover: small non-modal panel anchored to a control (quick edit, explanation with a link). Tooltip: a few words naming or explaining a control, no interactive content. |
143
148
  | Select / NativeSelect | Select: the kit's floating list — icons, descriptions, groups, search, `multiple`, `clearable`. NativeSelect: a plain text list where the OS picker is better (phones) or the value must post with a form by `name`. |
@@ -151,7 +156,7 @@ collapsible`; a section with sub-pages → `Sidebar.Sub`; the signed-in user →
151
156
  | Gallery / MediaViewer / Thumbnail | Thumbnail: one picture or video of an object in a list, card or picker (selectable with `checked`). Gallery: an album of 1–10 photos and videos laid out by their ratios; it opens MediaViewer itself. MediaViewer: the full-screen viewer on its own, opened from your own thumbnails or a button (`getOrigin` for the zoom out of the tile). A grid of many images to browse → a CSS grid of Thumbnails, not a Gallery. |
152
157
  | Sparkline / ChartFrame | Sparkline: one series as a headline number with its change and a small line or bars you can scrub (`variant="bars"` for signed values). ChartFrame: a panel around a real chart (axes, several series, legend, tooltip, states) drawn by a library or your SVG. Rows to compare → DataTable. |
153
158
  | VoicePlayer / VideoNote | The same player props (`src`, `duration`, `playing`, transcript, `transfer`). VoicePlayer: audio, a waveform seek slider and a playback rate. VideoNote: a round face-to-camera video with a `poster`; alone in `Message.Root` it is the message. A video with controls → `Thumbnail.Video` / `Message.Media`. |
154
- | Card / plain section | Card for a bounded block of related content that stands out (metric, settings panel, side facts). A page section that is the only thing in its region needs no card: a `title-m` heading and the content. Never wrap a single field, a DataTable or a whole page in a Card. |
159
+ | Card / plain section / Grid | Card for a bounded block of related content that stands out (metric, settings panel, side facts). A page section that is the only thing in its region needs no card: `PageContent.Section label` and the content. Several tiles of one screen → one `PageContent.Grid` with a `PageContent.Cell span` each; a grid inside one block (thumbnails, fields) is the block's own CSS. Never wrap a single field, a DataTable or a whole page in a Card. |
155
160
  | EmptyPage / empty DataTable | Empty table (no rows yet or nothing matches the filter): the DataTable `empty` prop — headers and toolbar stay. Whole page or region with no data at all (first run): EmptyPage with one action. |
156
161
  | Accordion / Tabs | Accordion when the user may need several sections open or reads them in order (FAQ, settings groups). Tabs when sections are alternatives viewed one at a time. |
157
162
  | Divider / gap | Divider inside a block between rows or a form and its footer. Between blocks and groups use air (`gap`), not lines. |
@@ -30,6 +30,7 @@ Type a value, and the field anatomy: label, hint, error.
30
30
  | Label | Field label (native `<label>`) with required and optional markers. | [COMPONENT.md](reference/components/label/COMPONENT.md) · [examples](reference/components/label/examples/) |
31
31
  | Hint | Help text or a validation error under a field. | [COMPONENT.md](reference/components/hint/COMPONENT.md) · [examples](reference/components/hint/examples/) |
32
32
  | Fieldset | A frameless group of form fields: a `title-s` legend, the form rhythm and one `disabled` for all of them. | [COMPONENT.md](reference/components/fieldset/COMPONENT.md) · [examples](reference/components/fieldset/examples/) |
33
+ | FieldList | An editable list of records of one shape: one row of fields per record, the column headings once, add, remove, reorder, pinned rows. | [COMPONENT.md](reference/components/field-list/COMPONENT.md) · [examples](reference/components/field-list/examples/) |
33
34
 
34
35
  ## Selection (`selection`)
35
36
 
@@ -60,13 +61,13 @@ Show data: labels, people, objects, cards, tables, boards, feeds, code, disclose
60
61
  | Badge | The kit's one chip: a static label for a status, category or count, a removable value or applied filter, and a pressable toggle with a hover action — in a palette color. | [COMPONENT.md](reference/components/badge/COMPONENT.md) · [examples](reference/components/badge/examples/) |
61
62
  | Avatar | A round photo of a person or organization with an initials or icon fallback, a presence dot and overlapping groups. | [COMPONENT.md](reference/components/avatar/COMPONENT.md) · [examples](reference/components/avatar/examples/) |
62
63
  | Thumbnail | A preview of an object — product, vehicle, file, cover — at a fixed aspect ratio (`ratio`), an image or a video (`Thumbnail.Video`, first frame and length), a colored icon fallback, and a selectable tile (`checked`). | [COMPONENT.md](reference/components/thumbnail/COMPONENT.md) · [examples](reference/components/thumbnail/examples/) |
63
- | Sparkline | A small line or bar chart with its headline — the value at rest, its change against the point before and its date — where any point can be scrubbed; `layout="compact"` — just the plot, for a table cell. | [COMPONENT.md](reference/components/sparkline/COMPONENT.md) · [examples](reference/components/sparkline/examples/) |
64
+ | 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; a tooltip beside the scrubbed point; `layout="compact"` — just the plot, for a table cell. | [COMPONENT.md](reference/components/sparkline/COMPONENT.md) · [examples](reference/components/sparkline/examples/) |
64
65
  | ChartFrame | A dashboard panel around any chart (Recharts, ECharts, visx, Chart.js or a hand-drawn SVG): title, headline value, a measured plot area, a series legend that hides series (`hiddenSeries`), loading / empty / error states (`state`, `onRetry`) that cross-fade, a tooltip body and a data table for screen readers. | [COMPONENT.md](reference/components/chart-frame/COMPONENT.md) · [examples](reference/components/chart-frame/examples/) |
65
66
  | Kbd | A key cap for a keyboard key or a shortcut, rendered as a native `<kbd>`. | [COMPONENT.md](reference/components/kbd/COMPONENT.md) · [examples](reference/components/kbd/examples/) |
66
67
  | Card | A filled surface block with structural templates for metrics, charts, lists, calls to action and covers. | [COMPONENT.md](reference/components/card/COMPONENT.md) · [examples](reference/components/card/examples/) |
67
68
  | DescriptionList | Read-only properties of a record: «label — value» pairs on a `<dl>` that stack by themselves where the room is narrow, with a muted dash for an empty value. | [COMPONENT.md](reference/components/description-list/COMPONENT.md) · [examples](reference/components/description-list/examples/) |
68
69
  | DataTable | A data table with sorting, pages or infinite scroll (on the client or by the server, `source="server"`), row selection, row groups, rows reordered by drag (`onReorder`), rows that open a record (link or in place), a column chooser (`DataTable.ColumnsMenu`), nested rows and loading / empty / error states. | [COMPONENT.md](reference/components/data-table/COMPONENT.md) · [examples](reference/components/data-table/examples/) |
69
- | Kanban | A board of status columns: cards move by drag, touch hold and Alt + arrows, columns reorder by a grip in their header (`reorderableColumns`, `columnOrder`), with column descriptions and colors, WIP limits, loading and empty columns. | [COMPONENT.md](reference/components/kanban/COMPONENT.md) · [examples](reference/components/kanban/examples/) |
70
+ | Kanban | A board of status columns — without wells — cards on the page under a header plate (`plain`, default) or a header line (`line`) — or filled wells (`well`): cards move by drag, touch hold and Alt + arrows, columns reorder by a grip in their header (`reorderableColumns`, `columnOrder`), with column descriptions and colors, WIP limits, loading and empty columns. | [COMPONENT.md](reference/components/kanban/COMPONENT.md) · [examples](reference/components/kanban/examples/) |
70
71
  | Gantt | A resource or project schedule: rows with bars on a day / week / month axis, lanes for overlaps, milestones, a today line, a pinned first column, moves and resizes by pointer and keyboard through an async `onBarChange`, new bars drawn on empty space (`onBarCreate`), virtualized rows and days for thousands of rows. | [COMPONENT.md](reference/components/gantt/COMPONENT.md) · [examples](reference/components/gantt/examples/) |
71
72
  | 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/) |
72
73
  | 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/) |
@@ -140,7 +141,7 @@ The frame and structure of a screen: app shell, app header, page column, page pa
140
141
  |---|---|---|
141
142
  | AppShell | The app frame: a content panel that is the page itself and a navigation rail one layer above it. | [COMPONENT.md](reference/layout/app-shell/COMPONENT.md) · [examples](reference/layout/app-shell/examples/) |
142
143
  | AppHeader | The bar at the top of the content panel: where you are, ⌘K search, a few actions, the phone menu button; as high as the Sidebar brand row. May carry the page title (`AppHeader.Title as="h1"`) and the page tools (`AppHeader.Tools`). | [COMPONENT.md](reference/layout/app-header/COMPONENT.md) · [examples](reference/layout/app-header/examples/) |
143
- | PageContent | Page structure inside the main column: title, description, page actions and content sections; a page as high as the panel (`fill`) and list + detail (`PageContent.Split`). | [COMPONENT.md](reference/components/page-content/COMPONENT.md) · [examples](reference/components/page-content/examples/) |
144
+ | PageContent | Page structure inside the main column: title, page actions, titled sections (`PageContent.Section`) and the tile grid (`PageContent.Grid`); a page as high as the panel (`fill`) and list + detail (`PageContent.Split`). | [COMPONENT.md](reference/components/page-content/COMPONENT.md) · [examples](reference/components/page-content/examples/) |
144
145
  | PageToolbar | The panel at the top of a page — search and filter, sections, view options and the primary action — laid out from its own width: one row when wide, stacked rows when narrow, the active filters right under the search. | [COMPONENT.md](reference/components/page-toolbar/COMPONENT.md) · [examples](reference/components/page-toolbar/examples/) |
145
146
  | Divider | A hairline separator inside one surface, horizontal or vertical, with or without a label. | [COMPONENT.md](reference/components/divider/COMPONENT.md) · [examples](reference/components/divider/examples/) |
146
147
  | ScrollContainer | The kit's one scroll region: a thin scrollbar or none (`scrollbar="hidden"`), soft edges where content is hidden (`edges` fade / shadow), `snap`, `lock`, correct shrinking inside flex and grid parents; pinned to the end for chats (`anchor="end"`), edge events for loading more (`onReachStart` / `onReachEnd`). | [COMPONENT.md](reference/components/scroll-container/COMPONENT.md) · [examples](reference/components/scroll-container/examples/) |