prime-ui-kit 0.20.0 → 0.21.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 (151) hide show
  1. package/README.md +1 -1
  2. package/dist/chunks/{chunk-4KONMO7Z.js → chunk-6L7VWEY6.js} +1 -1
  3. package/dist/chunks/{chunk-3PY52JP3.js → chunk-S27VR4ON.js} +5828 -5596
  4. package/dist/chunks/{chunk-W6EGTNG6.js → chunk-S4H2ZKWJ.js} +1099 -661
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/badge/Badge.d.ts +19 -2
  7. package/dist/components/card/Card.d.ts +10 -3
  8. package/dist/components/composer/Composer.d.ts +49 -1
  9. package/dist/components/crossfade/Crossfade.d.ts +6 -4
  10. package/dist/components/data-table/DataTable.d.ts +10 -2
  11. package/dist/components/data-table/Row.d.ts +12 -1
  12. package/dist/components/data-table/SelectionMenu.d.ts +20 -0
  13. package/dist/components/data-table/useRowSelection.d.ts +2 -0
  14. package/dist/components/empty-page/EmptyPage.d.ts +5 -2
  15. package/dist/components/hint/Hint.d.ts +1 -1
  16. package/dist/components/index.d.ts +1 -1
  17. package/dist/components/input/Input.d.ts +15 -3
  18. package/dist/components/message/Message.d.ts +6 -3
  19. package/dist/components/segmented-control/SegmentedControl.d.ts +5 -1
  20. package/dist/components/smart-filter/SmartFilter.d.ts +4 -0
  21. package/dist/components/spinner/Spinner.d.ts +3 -2
  22. package/dist/components/tabs/Tabs.d.ts +10 -1
  23. package/dist/hooks/useModalLayer.d.ts +15 -8
  24. package/dist/index.css +1 -1
  25. package/dist/index.js +2 -2
  26. package/dist/internal/RegionSwap.d.ts +18 -10
  27. package/dist/internal/RollingNumber.d.ts +7 -5
  28. package/dist/internal/ToolRow.d.ts +6 -0
  29. package/dist/internal/mediaSlot.d.ts +29 -0
  30. package/dist/internal/overlay/DialogParts.d.ts +30 -6
  31. package/dist/internal/overlay/focus.d.ts +18 -3
  32. package/dist/internal/partPresence.d.ts +19 -2
  33. package/dist/layout/sidebar/Sidebar.d.ts +3 -2
  34. package/dist/skill/anti-slop.md +35 -37
  35. package/dist/skill/api-contract.md +50 -2
  36. package/dist/skill/cheatsheet.md +15 -10
  37. package/dist/skill/checklist.md +19 -6
  38. package/dist/skill/choosing.md +79 -6
  39. package/dist/skill/components.md +1 -1
  40. package/dist/skill/composition.md +211 -51
  41. package/dist/skill/foundations.md +17 -6
  42. package/dist/skill/layouts.md +64 -14
  43. package/dist/skill/motion.md +9 -3
  44. package/dist/skill/reference/components/avatar/COMPONENT.md +1 -0
  45. package/dist/skill/reference/components/badge/COMPONENT.md +29 -5
  46. package/dist/skill/reference/components/badge/examples/examples.module.css +14 -0
  47. package/dist/skill/reference/components/badge/examples/overflow-count.tsx +32 -0
  48. package/dist/skill/reference/components/banner/COMPONENT.md +1 -1
  49. package/dist/skill/reference/components/card/COMPONENT.md +8 -4
  50. package/dist/skill/reference/components/card/examples/person-header.tsx +38 -0
  51. package/dist/skill/reference/components/composer/COMPONENT.md +35 -5
  52. package/dist/skill/reference/components/composer/examples/bot-keyboard.tsx +43 -0
  53. package/dist/skill/reference/components/confirm-dialog/COMPONENT.md +1 -1
  54. package/dist/skill/reference/components/crossfade/COMPONENT.md +18 -10
  55. package/dist/skill/reference/components/data-table/COMPONENT.md +17 -11
  56. package/dist/skill/reference/components/data-table/examples/screen.tsx +6 -9
  57. package/dist/skill/reference/components/data-table/examples/selection.tsx +60 -18
  58. package/dist/skill/reference/components/dnd/COMPONENT.md +3 -3
  59. package/dist/skill/reference/components/drawer/COMPONENT.md +41 -19
  60. package/dist/skill/reference/components/drawer/examples/footer-actions.tsx +54 -0
  61. package/dist/skill/reference/components/drawer/examples/header-actions.tsx +46 -25
  62. package/dist/skill/reference/components/drawer/examples/link-loading.tsx +57 -0
  63. package/dist/skill/reference/components/drawer/examples/screen.tsx +3 -5
  64. package/dist/skill/reference/components/empty-page/COMPONENT.md +2 -2
  65. package/dist/skill/reference/components/field-list/COMPONENT.md +7 -7
  66. package/dist/skill/reference/components/field-list/examples/row-menu.tsx +1 -1
  67. package/dist/skill/reference/components/hint/COMPONENT.md +1 -1
  68. package/dist/skill/reference/components/input/COMPONENT.md +16 -3
  69. package/dist/skill/reference/components/input/examples/password-policy.tsx +65 -0
  70. package/dist/skill/reference/components/list/COMPONENT.md +1 -1
  71. package/dist/skill/reference/components/login-form/COMPONENT.md +13 -11
  72. package/dist/skill/reference/components/login-form/examples/overview.tsx +5 -5
  73. package/dist/skill/reference/components/login-form/examples/states.tsx +7 -6
  74. package/dist/skill/reference/components/message/COMPONENT.md +5 -5
  75. package/dist/skill/reference/components/message/examples/bot-actions.tsx +47 -28
  76. package/dist/skill/reference/components/modal/COMPONENT.md +31 -12
  77. package/dist/skill/reference/components/modal/examples/structure.tsx +2 -4
  78. package/dist/skill/reference/components/segmented-control/COMPONENT.md +11 -6
  79. package/dist/skill/reference/components/segmented-control/examples/router.tsx +22 -0
  80. package/dist/skill/reference/components/select/COMPONENT.md +4 -1
  81. package/dist/skill/reference/components/sheet/COMPONENT.md +31 -12
  82. package/dist/skill/reference/components/sheet/examples/header-actions.tsx +5 -3
  83. package/dist/skill/reference/components/skeleton/COMPONENT.md +3 -2
  84. package/dist/skill/reference/components/smart-filter/COMPONENT.md +2 -1
  85. package/dist/skill/reference/components/smart-filter/examples/ranges.tsx +8 -2
  86. package/dist/skill/reference/components/spinner/COMPONENT.md +1 -1
  87. package/dist/skill/reference/components/tabs/COMPONENT.md +7 -4
  88. package/dist/skill/reference/components/tabs/examples/in-drawer.tsx +18 -2
  89. package/dist/skill/reference/components/thumbnail/COMPONENT.md +1 -0
  90. package/dist/skill/reference/components/tooltip/COMPONENT.md +3 -3
  91. package/dist/skill/reference/foundation.md +15 -9
  92. package/dist/skill/reference/layout/page/COMPONENT.md +1 -0
  93. package/dist/skill/reference/layout/sidebar/COMPONENT.md +4 -2
  94. package/dist/skill/reference/layout/sidebar/examples/account-profile.tsx +66 -0
  95. package/dist/skill/responsive.md +28 -6
  96. package/dist/skill/ux.md +97 -2
  97. package/dist/tokens/semantic.d.ts +2 -1
  98. package/dist/tokens/themes/dark.d.ts +1 -0
  99. package/package.json +1 -1
  100. package/src/components/avatar/COMPONENT.md +1 -0
  101. package/src/components/badge/COMPONENT.md +29 -5
  102. package/src/components/badge/examples/examples.module.css +14 -0
  103. package/src/components/badge/examples/overflow-count.tsx +32 -0
  104. package/src/components/banner/COMPONENT.md +1 -1
  105. package/src/components/card/COMPONENT.md +8 -4
  106. package/src/components/card/examples/person-header.tsx +38 -0
  107. package/src/components/composer/COMPONENT.md +35 -5
  108. package/src/components/composer/examples/bot-keyboard.tsx +43 -0
  109. package/src/components/confirm-dialog/COMPONENT.md +1 -1
  110. package/src/components/crossfade/COMPONENT.md +18 -10
  111. package/src/components/data-table/COMPONENT.md +17 -11
  112. package/src/components/data-table/examples/screen.tsx +6 -9
  113. package/src/components/data-table/examples/selection.tsx +60 -18
  114. package/src/components/dnd/COMPONENT.md +3 -3
  115. package/src/components/drawer/COMPONENT.md +41 -19
  116. package/src/components/drawer/examples/footer-actions.tsx +54 -0
  117. package/src/components/drawer/examples/header-actions.tsx +46 -25
  118. package/src/components/drawer/examples/link-loading.tsx +57 -0
  119. package/src/components/drawer/examples/screen.tsx +3 -5
  120. package/src/components/empty-page/COMPONENT.md +2 -2
  121. package/src/components/field-list/COMPONENT.md +7 -7
  122. package/src/components/field-list/examples/row-menu.tsx +1 -1
  123. package/src/components/hint/COMPONENT.md +1 -1
  124. package/src/components/input/COMPONENT.md +16 -3
  125. package/src/components/input/examples/password-policy.tsx +65 -0
  126. package/src/components/list/COMPONENT.md +1 -1
  127. package/src/components/login-form/COMPONENT.md +13 -11
  128. package/src/components/login-form/examples/overview.tsx +5 -5
  129. package/src/components/login-form/examples/states.tsx +7 -6
  130. package/src/components/message/COMPONENT.md +5 -5
  131. package/src/components/message/examples/bot-actions.tsx +47 -28
  132. package/src/components/modal/COMPONENT.md +31 -12
  133. package/src/components/modal/examples/structure.tsx +2 -4
  134. package/src/components/segmented-control/COMPONENT.md +11 -6
  135. package/src/components/segmented-control/examples/router.tsx +22 -0
  136. package/src/components/select/COMPONENT.md +4 -1
  137. package/src/components/sheet/COMPONENT.md +31 -12
  138. package/src/components/sheet/examples/header-actions.tsx +5 -3
  139. package/src/components/skeleton/COMPONENT.md +3 -2
  140. package/src/components/smart-filter/COMPONENT.md +2 -1
  141. package/src/components/smart-filter/examples/ranges.tsx +8 -2
  142. package/src/components/spinner/COMPONENT.md +1 -1
  143. package/src/components/tabs/COMPONENT.md +7 -4
  144. package/src/components/tabs/examples/in-drawer.tsx +18 -2
  145. package/src/components/thumbnail/COMPONENT.md +1 -0
  146. package/src/components/tooltip/COMPONENT.md +3 -3
  147. package/src/layout/page/COMPONENT.md +1 -0
  148. package/src/layout/sidebar/COMPONENT.md +4 -2
  149. package/src/layout/sidebar/examples/account-profile.tsx +66 -0
  150. package/src/styles/theme-dark.css +1 -0
  151. package/src/styles/theme-light.css +1 -0
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ import {
88
88
  useSidebar,
89
89
  useTokenValue,
90
90
  useUploadQueue
91
- } from "./chunks/chunk-W6EGTNG6.js";
91
+ } from "./chunks/chunk-S4H2ZKWJ.js";
92
92
  import {
93
93
  Avatar,
94
94
  Badge,
@@ -123,7 +123,7 @@ import {
123
123
  tierMappings,
124
124
  tones,
125
125
  variants
126
- } from "./chunks/chunk-3PY52JP3.js";
126
+ } from "./chunks/chunk-S27VR4ON.js";
127
127
  import {
128
128
  ControlSizeProvider,
129
129
  useOptionalControlSize
@@ -2,7 +2,8 @@ import * as React from "react";
2
2
  type RegionSwapProps = React.HTMLAttributes<HTMLDivElement> & {
3
3
  /**
4
4
  * Key of what the region shows (`"loading"`, `"ready"`, `"empty"`, `"error"`, a record id). A new
5
- * value swaps the old content for the new one; the same value updates in place.
5
+ * value swaps the old content for the new one; the same value updates in place. `"loading"` is
6
+ * the loading state: a swap into or out of it never glides the height.
6
7
  */
7
8
  state: string | number;
8
9
  /** The region takes its parent's height and every state fills it; the height never glides. */
@@ -17,25 +18,32 @@ type RegionSwapProps = React.HTMLAttributes<HTMLDivElement> & {
17
18
  /**
18
19
  * The region swap of the kit (foundation §7 rule 8): a region that changes what it shows (loading /
19
20
  * data / empty / error, another record) cross-fades — the old content stays as a leaving layer over
20
- * the new one and fades out (`swapMotion.swapOut`, `fast` · exit) while the new one fades in
21
- * (`swapMotion.swapIn`, `base` · enter) — and glides from the old height to the new one, the
22
- * "height glide" exception of foundation §7 rule 5. Nothing moves on the first render. Focus in
23
- * the leaving state moves into the new one (`handoffFocus`). Under reduced motion the old state
24
- * goes at once and the height jumps; the new one still fades in (rule 12). A region with nothing
25
- * to show takes no room. `Crossfade` is this region, public.
21
+ * the new one and fades out (`swapMotion.swapOut`, `fast` · exit) while the new one fades in.
22
+ * Between settled states (data / empty / error, another record) the new one fades in on `base`
23
+ * (`swapMotion.swapIn`) and the region glides from the old height to the new one, the "height
24
+ * glide" exception of foundation §7 rule 5. A swap into or out of `"loading"` is a load: the
25
+ * Skeleton and the data replace each other in place — the height settles in one frame (the old
26
+ * layer clipped to it) and the fade is short (`swapMotion.settleIn`, `fast`). Nothing moves on the
27
+ * first render. Focus in the leaving state moves into the new one (`handoffFocus`). Under reduced
28
+ * motion the old state goes at once and the height jumps; the new one still fades in (rule 12).
29
+ * A region with nothing to show takes no room. `Crossfade` is this region, public.
26
30
  */
27
31
  export declare function RegionSwap({ state, fill, onSwap, children, className, ref, ...rest }: RegionSwapProps): import("react/jsx-runtime").JSX.Element;
28
32
  type SwapIn = {
29
33
  /** Key of the swapped content: it remounts under a new state. `undefined` before the first swap. */
30
34
  key: string | undefined;
31
- /** The fade-in of the new state (`swapMotion.swapIn`); `undefined` before the first swap. */
35
+ /**
36
+ * The fade-in of the new state: `swapMotion.settleIn` into or out of `"loading"`, else
37
+ * `swapMotion.swapIn`; `undefined` before the first swap.
38
+ */
32
39
  className: string | undefined;
33
40
  };
34
41
  /**
35
42
  * The region swap in place, for a host whose states cannot be laid over each other: a table body
36
43
  * (rows leave the table's layout when positioned) or the phrasing content of a button. The new
37
- * state remounts under `key` and fades in through `className`; nothing fades out and the height
38
- * does not glide. Nothing moves on the first render. Every other region is a `RegionSwap`.
44
+ * state remounts under `key` and fades in through `className` (short into or out of `"loading"`);
45
+ * nothing fades out and the height does not glide. Nothing moves on the first render. Every other
46
+ * region is a `RegionSwap`.
39
47
  */
40
48
  export declare function useSwapIn(state: string | number): SwapIn;
41
49
  export {};
@@ -5,11 +5,13 @@ type RollingNumberProps = {
5
5
  };
6
6
  /**
7
7
  * A number whose digits roll to their new values column by column, like an odometer (foundation §7
8
- * rule 8: counters ease, they do not flip). The DOM holds the real digits (text, copy, assistive
9
- * tech and a page without CSS read the value); each digit column draws the 0–9 strip in a
10
- * pseudo-element. Columns are keyed from the right, so an unchanged digit stays still and a new
11
- * leading digit appears without rolling. A value that changes by itself every frame (a running
12
- * percentage) stays plain tabular text: rolling there is noise.
8
+ * rule 8: counters ease, they do not flip). The DOM holds the value as one run of text — copy, a
9
+ * page without CSS and assistive tech read it as one word («12», never «1 2»: separate boxes per
10
+ * digit make a browser split the name) — and an aria-hidden row of columns, with no text of its own,
11
+ * paints it: each digit column draws the 0–9 strip in a pseudo-element. Columns are keyed from the
12
+ * right, so an unchanged digit stays still and a new leading digit appears without rolling. A value
13
+ * that changes by itself every frame (a running percentage) stays plain tabular text: rolling there
14
+ * is noise.
13
15
  */
14
16
  export declare function RollingNumber({ children, className }: RollingNumberProps): import("react/jsx-runtime").JSX.Element;
15
17
  /**
@@ -8,6 +8,12 @@ type ToolGroupProps = DivProps;
8
8
  * title before its zoom): they wrap inside it.
9
9
  */
10
10
  export declare function ToolGroup({ className, ...rest }: ToolGroupProps): import("react/jsx-runtime").JSX.Element;
11
+ type ToolCellProps = DivProps;
12
+ /**
13
+ * A cell the host draws itself, standing in the row as given: a part that comes and goes takes its
14
+ * presence (`usePartPresence`) on the cell, so the row closes up when it leaves. A fixed cell.
15
+ */
16
+ export declare function ToolCell({ className, ...rest }: ToolCellProps): import("react/jsx-runtime").JSX.Element;
11
17
  type ToolRowProps = DivProps & {
12
18
  /** The row has a line of its own: it wraps and its cells fill it by kind. Off — one line, every cell at its natural width. */
13
19
  fill?: boolean;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The media slot of a leading mark (`Modal.Icon`, `Drawer.Icon`, `Sheet.Icon`, `Card.Icon`): a box
3
+ * that is a filled glyph tile, unless the record's picture stands in it. A person or an object
4
+ * stands for itself, so an `Avatar.Root` or a `Thumbnail.Root` anywhere inside the slot — a direct
5
+ * child, under a wrapper, or rendered by the consumer's own component — reports itself and takes the
6
+ * slot's size, the size of the tile it replaces (40): an avatar `l`, a thumbnail `m` (its height).
7
+ * The report runs in a layout effect, so the tile never paints around a picture.
8
+ */
9
+ import * as React from "react";
10
+ import type { ControlSize } from "./states";
11
+ /** The sizes a picture takes in the slot: the 40 box of the glyph tile. */
12
+ declare const MEDIA_SLOT_SIZE: {
13
+ readonly avatar: "l";
14
+ readonly thumbnail: "m";
15
+ };
16
+ /**
17
+ * The host side: wraps the slot's children and tells whether a picture stands in it. Children
18
+ * render inside `provide(…)`.
19
+ */
20
+ export declare function useMediaSlotHost(): {
21
+ media: boolean;
22
+ provide: (children: React.ReactNode) => React.ReactElement;
23
+ };
24
+ /**
25
+ * The picture side (Avatar.Root, Thumbnail.Root): inside a media slot it reports itself and
26
+ * returns the size it must take there; outside one, `undefined`.
27
+ */
28
+ export declare function useMediaSlotSize(kind: keyof typeof MEDIA_SLOT_SIZE): ControlSize | undefined;
29
+ export {};
@@ -7,6 +7,7 @@
7
7
  import * as React from "react";
8
8
  import { type SlotPartProps } from "../slot";
9
9
  import type { ControlSize, Tone } from "../states";
10
+ import type { FocusStart } from "./focus";
10
11
  /** System strings of a dialog: its close button and the built-in «discard changes?» confirm. */
11
12
  export type DialogLabels = {
12
13
  /** `aria-label` of the header close button. */
@@ -116,6 +117,8 @@ type DialogPanelProps = React.HTMLAttributes<HTMLDivElement> & {
116
117
  enterConfirm?: boolean;
117
118
  /** The tier of the controls inside (Modal: `m`); without it they keep the surrounding tier. */
118
119
  controlSize?: ControlSize;
120
+ /** Where focus lands on open (`useModalLayer`): the first tab stop, or the panel (Drawer). */
121
+ focusStart?: FocusStart;
119
122
  ref?: React.Ref<HTMLDivElement>;
120
123
  };
121
124
  /**
@@ -131,19 +134,32 @@ export type DialogIconProps = Omit<React.HTMLAttributes<HTMLSpanElement>, "child
131
134
  */
132
135
  tone?: Tone;
133
136
  /**
134
- * An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (sized to the tile) or a `Thumbnail.Root`.
137
+ * An `Icon` (sized to the m icon), or the record's picture: an `Avatar.Root` (size `l`) or a `Thumbnail.Root` (size `m`), set by the slot.
135
138
  */
136
139
  children: React.ReactNode;
137
140
  ref?: React.Ref<HTMLSpanElement>;
138
141
  };
142
+ /**
143
+ * The leading mark: a glyph on the tone tile, or the record's picture (an `Avatar.Root` or a
144
+ * `Thumbnail.Root` anywhere inside, internal/mediaSlot) with no tile, at the tile's 40 box.
145
+ */
139
146
  export declare function DialogIcon({ tone, children, className, ...rest }: DialogIconProps): import("react/jsx-runtime").JSX.Element;
140
147
  export declare namespace DialogIcon {
141
148
  var displayName: string;
142
149
  }
143
150
  export type DialogTitleProps = Omit<React.HTMLAttributes<HTMLHeadingElement>, "id"> & {
151
+ /**
152
+ * The name loads (a record opened by a link): a `Skeleton` holds the title line and `aria-busy` is set, so the header never moves; the name fades in.
153
+ */
154
+ loading?: boolean;
144
155
  ref?: React.Ref<HTMLHeadingElement>;
145
156
  };
146
- export declare function DialogTitle({ className, ...rest }: DialogTitleProps): import("react/jsx-runtime").JSX.Element;
157
+ /**
158
+ * The dialog's name. While it loads (`loading`: a record opened by a link knows only its id) a
159
+ * Skeleton holds the title line, so the header never moves when the name arrives; the name then
160
+ * fades in under the same element.
161
+ */
162
+ export declare function DialogTitle({ loading, className, children, ...rest }: DialogTitleProps): import("react/jsx-runtime").JSX.Element;
147
163
  export declare namespace DialogTitle {
148
164
  var displayName: string;
149
165
  }
@@ -157,6 +173,9 @@ export type DialogDescriptionProps = Omit<React.HTMLAttributes<HTMLParagraphElem
157
173
  /**
158
174
  * The line under the title. While its data loads (`loading`) it keeps its place with a Skeleton, so
159
175
  * the header never moves when the data arrives; the data then fades in under the same element.
176
+ * The record's status badges lead it: a Badge inside takes chip tier `s` (one step under the `m`
177
+ * it provides), the height of the line, and the badges stand before the text whatever the order
178
+ * they are written in.
160
179
  */
161
180
  export declare function DialogDescription({ loading, className, children, ...rest }: DialogDescriptionProps): import("react/jsx-runtime").JSX.Element;
162
181
  export declare namespace DialogDescription {
@@ -182,8 +201,11 @@ export type DialogActionsProps = React.HTMLAttributes<HTMLDivElement> & {
182
201
  ref?: React.Ref<HTMLDivElement>;
183
202
  };
184
203
  /**
185
- * A row of controls at the end of the header, before the close button: a status Badge, a ⋯
186
- * Dropdown, an icon button. Controls inside take tier `s`, the close button's tier.
204
+ * The buttons at the end of the header, before the close button: ghost icon buttons (each named,
205
+ * with a Tooltip) and a ⋯ Dropdown — never a status, which leads the `Description`. They take tier
206
+ * `s`, the close button's, and sit on the title's line. In the footer
207
+ * it is the start group — an action apart from the main pair («Удалить», «Выйти») — and its
208
+ * buttons keep the footer's tier.
187
209
  */
188
210
  export declare function DialogActions({ className, children, ...rest }: DialogActionsProps): import("react/jsx-runtime").JSX.Element;
189
211
  export declare namespace DialogActions {
@@ -227,8 +249,10 @@ export type DialogFooterProps = React.HTMLAttributes<HTMLElement> & {
227
249
  };
228
250
  /**
229
251
  * Actions in DOM order, primary last. Stacks full width when the dialog is narrower than 360px.
230
- * With a `Meta` the buttons move as one row: when the line runs short the status text goes above
231
- * them first, so the buttons never split over two lines while there is room for them in one.
252
+ * An `Actions` holds the start group (an action apart from the main pair) and a `Meta` the status
253
+ * text, both at the start; the buttons then move as one row at the end: when the line runs short
254
+ * the start goes above them first, so the buttons never split over two lines while there is room
255
+ * for them in one. Stacked, the start comes first and the primary stays last.
232
256
  */
233
257
  export declare function DialogFooter({ layout, className, children, ...rest }: DialogFooterProps): import("react/jsx-runtime").JSX.Element;
234
258
  export declare namespace DialogFooter {
@@ -5,13 +5,20 @@ export declare function getFocusable(container: HTMLElement): HTMLElement[];
5
5
  * reaches it without an added `tabindex`.
6
6
  */
7
7
  export declare function hasNativeTabStop(el: HTMLElement): boolean;
8
+ /**
9
+ * Where focus lands in a layer that just opened: `tabStop` — its first tab stop; `container` — the
10
+ * layer itself (a record panel: the person hears its name and description, and Tab goes on to the
11
+ * first control).
12
+ */
13
+ export type FocusStart = "tabStop" | "container";
8
14
  /**
9
15
  * Moves focus into a layer that just opened, unless focus is already inside (a child `autoFocus`):
10
16
  * the first match of `first` (a menu's first enabled item, which is no tab stop of its own), else
11
- * `[data-autofocus]`, else the first tab stop, else the container itself. `quiet` — a pointer
12
- * opened the layer: focus lands without the focus ring until the person turns to the keyboard.
17
+ * `[data-autofocus]`, else per `start` the first tab stop, else the container itself. `quiet` — a
18
+ * pointer opened the layer: focus lands without the focus ring until the person turns to the
19
+ * keyboard.
13
20
  */
14
- export declare function focusInto(container: HTMLElement, first?: string, quiet?: boolean): void;
21
+ export declare function focusInto(container: HTMLElement, first?: string, quiet?: boolean, start?: FocusStart): void;
15
22
  /**
16
23
  * Focuses `el` when it is still in the document. `quiet` — the layer was opened with a pointer:
17
24
  * focus returns without the focus ring, also when a key (Escape) closed the layer; the next key
@@ -35,6 +42,14 @@ export declare function handoffFocus(leaving: Element | null | undefined, next:
35
42
  * `el` do not count.
36
43
  */
37
44
  export declare function tabStopBeside(el: Element): HTMLElement | null;
45
+ /**
46
+ * Whether focus on `el` came from the keyboard, so a part that shows on focus (a tooltip) may
47
+ * show: the last input was a key (or nothing yet, as `:focus-visible` has it), and `el` is not
48
+ * focused quietly (a layer a pointer opened or closed). Focus a click causes, or a layer moves after
49
+ * a click, is no keyboard focus — read from the input itself, since `:focus-visible` is not
50
+ * reported everywhere (jsdom) and a quiet focus keeps it until the next key.
51
+ */
52
+ export declare function focusFromKeyboard(el: Element): boolean;
38
53
  /**
39
54
  * Who opened a layer, to return focus to on close: `element` — the control (or the focused
40
55
  * element); `target` — the exact element a press landed on (a picture inside a link, for a layer
@@ -1,4 +1,21 @@
1
1
  import * as React from "react";
2
+ type EnterOnAppear<E extends Element> = {
3
+ /** On the node that appears (the one its host inserts); it may differ from the one that moves. */
4
+ ref: React.RefCallback<E>;
5
+ /** Join `enterMotion.*` while true. */
6
+ entering: boolean;
7
+ };
8
+ /**
9
+ * Whether a part plays its enter motion (foundation §1 rule 7, §7): only when it appears after its
10
+ * host is on screen — `active` turns on after the first render (an error, a tab opened again), or
11
+ * its node is inserted on its own into a host already shown (a banner after an event, a bar that
12
+ * selection brings, a keyed excerpt that replaces the last). A part that mounts together with its
13
+ * page, region or overlay is just there. Spread `ref` on the node the host inserts:
14
+ *
15
+ * const appear = useEnterOnAppear<HTMLDivElement>();
16
+ * <div ref={useMergedRefs(appear.ref, ref)} className={cx(appear.entering && enterMotion.enter)} />
17
+ */
18
+ export declare function useEnterOnAppear<E extends Element>(active?: boolean): EnterOnAppear<E>;
2
19
  type PartPresenceOptions = {
3
20
  /**
4
21
  * Whether the host shows the part now. It enters when this turns on after the host's first
@@ -18,8 +35,8 @@ type PartPresence<E extends HTMLElement> = {
18
35
  * Presence of an optional part that comes and goes while its host stays (a clear button, a chip
19
36
  * row, a remove segment, a change badge): it rises in when it appears (`enterMotion`; the host
20
37
  * tunes it with `--enter-*`) and closes up in place when it goes (`leaveInPlace`), so the parts
21
- * around it glide instead of jumping. Called by the host that owns the condition; spread the
22
- * result on the part:
38
+ * around it glide instead of jumping. Called by the host that owns the condition; spread the result
39
+ * on the part:
23
40
  *
24
41
  * const clear = usePartPresence<HTMLButtonElement>({ present: hasValue, axis: "inline" });
25
42
  * {hasValue ? <button ref={clear.ref} className={cx(styles.clear, clear.className)} /> : null}
@@ -374,8 +374,9 @@ export type SidebarAccountProps = Omit<React.ComponentPropsWithoutRef<"button">,
374
374
  ref?: React.Ref<HTMLButtonElement>;
375
375
  };
376
376
  /**
377
- * The signed-in person at the bottom of the rail: avatar, name, a muted line and a chevron. A
378
- * button, so it can be a `Dropdown.Trigger` child; in compact mode only the avatar stays.
377
+ * The signed-in person at the bottom of the rail: avatar, name, a muted line and, as a menu
378
+ * trigger, a chevron. A button, so it can be a `Dropdown.Trigger` child; in compact mode only the
379
+ * avatar stays.
379
380
  */
380
381
  declare function SidebarAccount({ description, className, children, type, ...rest }: SidebarAccountProps): import("react/jsx-runtime").JSX.Element;
381
382
  declare namespace SidebarAccount {
@@ -242,8 +242,8 @@ The same for one value in several units — one heading, the fields in a row, th
242
242
  | `<Button.Root onClick={() => location.assign(url)}>` | `<Button.Root asChild><a href={url}>…</a></Button.Root>` or `LinkButton` |
243
243
  | a hand-built spinner, shimmer, grey placeholder box or «Loading…» text | `loading` on Button / Select / DataTable; a region: `Skeleton` of its content inside `Crossfade`; no shape to hold: `Spinner` |
244
244
  | `{loading ? <Spinner /> : <List />}` — a state that flips | `<Crossfade state={status}>` around the region, `Skeleton` while loading |
245
- | a search Input + a row of filter Selects above a table | `SmartFilter` next to the table it filters: its toolbar and chips in the table's `toolbar` / `toolbarChips` beside KPI cards, in `Page.Tools` / `Page.Chips` when the table is the page ([where the filters go](composition.md#where-the-filters-go)) |
246
- | the table's search, filter or «Колонки» in the page's bar above KPI cards | the bar keeps the title, the period that drives every block and the primary action; the table's tools go in its own `toolbar` ([where the filters go](composition.md#where-the-filters-go)) |
245
+ | a search Input + a row of filter Selects above a table | `SmartFilter` with the table it filters: its toolbar and chips in the table's `toolbar` / `toolbarChips` on every page ([where the filters go](composition.md#where-the-filters-go)) |
246
+ | the table's search, filter or «Колонки» in the page's bar, the page that is only the table included | the bar keeps the title, the sections, the view, a period that drives every block and the buttons; the table's tools go in its own `toolbar` ([where the filters go](composition.md#where-the-filters-go)) |
247
247
  | a grid of «label: value» text pairs for one record | `DescriptionList.Root` + `DescriptionList.Item` |
248
248
  | a Modal hand-built as «Удалить?» with your own pending flag | `ConfirmDialog.Root` with an async `onConfirm` |
249
249
  | a `<fieldset>` reset by hand, or `disabled` on every field while saving | `<Fieldset label disabled={saving}>` |
@@ -310,40 +310,38 @@ cell in every row:
310
310
  ```
311
311
 
312
312
  ```tsx good
313
- // good — a page that is the table, no row actions: the chooser is in the bar, on the state the table reads
313
+ // good — no row actions: the chooser stands beside the table's search, on the state the table reads
314
+ // declare: const FIELDS: Parameters<typeof SmartFilter.Root>[0]["fields"]
314
315
  // declare: const orders: { id: string; client: string; total: number }[]
315
316
  // declare: const hidden: string[]
316
317
  // declare: const setHidden: (hidden: string[]) => void
317
- <Page.Root>
318
- <Page.TopBar>
319
- <Page.Heading>
320
- <Page.Title>Заказы</Page.Title>
321
- </Page.Heading>
322
- <Page.Tools>
323
- <DataTableColumnsMenu
324
- columns={[
325
- { id: "id", header: "Заказ", accessor: "id", hideable: false },
326
- { id: "client", header: "Клиент", accessor: "client" },
327
- { id: "total", header: "Сумма", accessor: "total", numeric: true },
328
- ]}
329
- hiddenColumns={hidden}
330
- onHiddenColumnsChange={setHidden}
331
- />
332
- </Page.Tools>
333
- </Page.TopBar>
334
- <Page.Body>
335
- <DataTable
336
- columns={[
337
- { id: "id", header: "Заказ", accessor: "id", hideable: false },
338
- { id: "client", header: "Клиент", accessor: "client" },
339
- { id: "total", header: "Сумма", accessor: "total", numeric: true },
340
- ]}
341
- hiddenColumns={hidden}
342
- rows={orders}
343
- getRowKey={(row) => row.id}
344
- />
345
- </Page.Body>
346
- </Page.Root>
318
+ <SmartFilter.Root fields={FIELDS}>
319
+ <DataTable
320
+ columns={[
321
+ { id: "id", header: "Заказ", accessor: "id", hideable: false },
322
+ { id: "client", header: "Клиент", accessor: "client" },
323
+ { id: "total", header: "Сумма", accessor: "total", numeric: true },
324
+ ]}
325
+ hiddenColumns={hidden}
326
+ rows={orders}
327
+ getRowKey={(row) => row.id}
328
+ toolbar={
329
+ <>
330
+ <SmartFilter.Toolbar />
331
+ <DataTableColumnsMenu
332
+ columns={[
333
+ { id: "id", header: "Заказ", accessor: "id", hideable: false },
334
+ { id: "client", header: "Клиент", accessor: "client" },
335
+ { id: "total", header: "Сумма", accessor: "total", numeric: true },
336
+ ]}
337
+ hiddenColumns={hidden}
338
+ onHiddenColumnsChange={setHidden}
339
+ />
340
+ </>
341
+ }
342
+ toolbarChips={<SmartFilter.Chips />}
343
+ />
344
+ </SmartFilter.Root>
347
345
  ```
348
346
 
349
347
  ## 7b. A top bar out of its groups
@@ -409,13 +407,13 @@ Buttons in two places, buttons with only text:
409
407
  </Page.Actions>
410
408
  ```
411
409
 
412
- `SmartFilter.Root` inside the bar:
410
+ `SmartFilter.Root` inside the bar of a page whose filter narrows several views (a board and its list):
413
411
 
414
412
  ```tsx partial
415
413
  // bad — the filter's state wraps one group, its chips land outside it
416
414
  <Page.TopBar>
417
415
  <SmartFilter.Root fields={FIELDS}>
418
- <Page.Tools aria-label="Заказы">
416
+ <Page.Tools aria-label="Сделки">
419
417
  <SmartFilter.Toolbar />
420
418
  </Page.Tools>
421
419
  </SmartFilter.Root>
@@ -429,9 +427,9 @@ Buttons in two places, buttons with only text:
429
427
  <Page.Root>
430
428
  <Page.TopBar>
431
429
  <Page.Heading>
432
- <Page.Title>Заказы</Page.Title>
430
+ <Page.Title>Сделки</Page.Title>
433
431
  </Page.Heading>
434
- <Page.Tools aria-label="Заказы">
432
+ <Page.Tools aria-label="Сделки">
435
433
  <SmartFilter.Toolbar />
436
434
  </Page.Tools>
437
435
  <Page.Chips>
@@ -25,7 +25,7 @@ import type { ControlSize, PaletteColor, TextTone, Tone, Variant } from "prime-u
25
25
  | Text value | native `value` / `onChange` + `onValueChange(string)` | Input (`Input.Field`), Textarea. |
26
26
  | Other state | `x` / `defaultX` / `onXChange(x)` | Badge `pressed`; Button `pressed`; ChartFrame `hiddenSeries`; Chat `selected`, `search`; ChatList `search`; ColorPicker `format`; CommandMenu `search`; Composer `recordMode`; DataTable `hiddenColumns`, `columnOrder`, `page`, `sort`, `selected`, `expanded`, `collapsedGroups`; ExampleFrame `viewport`, `colorScheme`; Gallery `index`; Gantt `scale`; Kanban `columnOrder`; MediaViewer `index`; Message.Media `index`; Select `search`; SmartFilter `search`; VideoNote `playing`, `transcriptExpanded`; VoicePlayer `rate`, `playing`, `transcriptExpanded`; Sidebar `mode`. |
27
27
  | Checked | `checked` / `defaultChecked` / `onCheckedChange(checked)` | Checkbox, Dropdown.CheckboxItem, Switch, Thumbnail. |
28
- | Open | `open` / `defaultOpen` / `onOpenChange(open)` | ColorPresets, CommandMenu, ConfirmDialog, Datepicker, Drawer, Dropdown, Gallery, MediaViewer, Message.Media, Modal, Popover, Select, Sheet, TagSelect, Tooltip, Page.Split, Sidebar, Sidebar.Group, Sidebar.Sub. |
28
+ | Open | `open` / `defaultOpen` / `onOpenChange(open)` | ColorPresets, CommandMenu, Composer.Keyboard, ConfirmDialog, Datepicker, Drawer, Dropdown, Gallery, MediaViewer, Message.Media, Modal, Popover, Select, Sheet, TagSelect, Tooltip, Page.Split, Sidebar, Sidebar.Group, Sidebar.Sub. |
29
29
  | Dismiss | `closeOnOutsideClick` (default `true`), `closeOnEscape` (default `true`) | Both on every overlay that opens a layer: the dialogs, the floating panels, the panels of Select, TagSelect, Datepicker and ColorPresets, the `suggestions` list of Input, the off-canvas Sidebar and the reaction bar of a message. `ConfirmDialog` takes both with `closeOnOutsideClick` default `false` (Escape is held while `onConfirm` runs). Tooltip and Notification take neither. A form holds closing with `Modal.Guard` / `Drawer.Guard` / `Sheet.Guard`. |
30
30
  | Selection mode | `multiple` | One vs many (Select, Accordion) — never `type`. `mode` only for structurally different values (Datepicker `single \| range`). |
31
31
  | Status words | `selected`, `current`, `pressed` | `current` marks a navigation location (`Sidebar.Item`, `Breadcrumb.Item`) and sets `aria-current`; `pressed` a toggle button. |
@@ -168,6 +168,54 @@ export function InviteForm() {
168
168
  (`.form { display: grid; gap: var(--prime-space-5); }`.) A full create form with groups, validation,
169
169
  loading and a toast: [the Drawer screen](reference/components/drawer/examples/screen.tsx).
170
170
 
171
+ ### Field outcomes: a mark for success, words for a problem
172
+
173
+ A field answers a check in one of two ways. Success is quiet; a problem is said.
174
+
175
+ - **A confirmed value is a mark.** A repeat that matches, a code that passed, a free address: `valid`
176
+ on the root draws a check at the field's end and says it to a screen reader — no words under the
177
+ field. Never a `hint` with `hintTone="success"` that writes «Пароли совпадают»: the person reads a
178
+ sentence to learn that nothing is wrong. `hintTone` stays for the status of a value that is not a
179
+ check (a deadline that nears).
180
+ - **A problem is words.** `error` says what is wrong and how to fix it, under the field it concerns.
181
+ Red rings and a shake alone leave the person guessing.
182
+ - **A policy is enforced, not shown.** A new password is `strength` with `minStrength`: below the
183
+ level the form refuses to submit and says why, the `hint` names the requirement in words («От 10
184
+ символов, буквы и цифры»), and the server checks the same rule. A meter alone lets the weak
185
+ password through ([password-policy.tsx](reference/components/input/examples/password-policy.tsx)).
186
+ - **A refused sign-in** marks the e-mail `invalid` and puts the reason on the password as its `error`
187
+ («Неверная почта или пароль») — one sentence that names neither field as the culprit, no Banner
188
+ over the form ([LoginForm](reference/components/login-form/COMPONENT.md)).
189
+
190
+ ```tsx partial
191
+ // bad — a meter that lets anything through and a green sentence for a match
192
+ <Input.Root label="Новый пароль" strength>…</Input.Root>
193
+ <Input.Root label="Повторите пароль" hint="Пароли совпадают" hintTone="success">…</Input.Root>
194
+ ```
195
+
196
+ ```tsx good
197
+ // good — the policy stops the submit, the match is a check, a mismatch is words
198
+ // declare: const matches: boolean
199
+ // declare: const mismatch: boolean
200
+ <>
201
+ <Input.Root label="Новый пароль" strength minStrength={3} hint="От 10 символов, буквы и цифры">
202
+ <Input.Wrapper>
203
+ <Input.Field type="password" autoComplete="new-password" />
204
+ <Input.RevealButton />
205
+ </Input.Wrapper>
206
+ </Input.Root>
207
+ <Input.Root
208
+ label="Повторите пароль"
209
+ valid={matches}
210
+ error={mismatch ? "Пароли не совпадают" : undefined}
211
+ >
212
+ <Input.Wrapper>
213
+ <Input.Field type="password" autoComplete="new-password" />
214
+ </Input.Wrapper>
215
+ </Input.Root>
216
+ </>
217
+ ```
218
+
171
219
  ### Choice controls
172
220
 
173
221
  Checkbox, Switch and Radio: the root renders the `<label>` with the input and the visible text is
@@ -263,7 +311,7 @@ A standalone destructive action that is easy to redo by hand is `Button.Root hol
263
311
  action in `onConfirm` ([the recipe](motion.md#5-recipes)).
264
312
 
265
313
  `tone="danger"`. A standalone trigger that opens a confirm (danger zone) is `variant="outline"`; inside
266
- a bar of other actions (DataTable bulk bar, card actions) it is `soft`; in a Dropdown it is the last item
314
+ a bar of other actions (card actions) it is `soft`; in a Dropdown it is the last item
267
315
  with `tone="danger"`; an inline remove in a media or file row is `ghost`. The confirm itself is
268
316
  [ConfirmDialog](reference/components/confirm-dialog/COMPONENT.md) — never a hand-built Modal: it is a
269
317
  Modal `s` with a solid danger confirm (`tone="accent"` for an outward action that destroys nothing, like