prime-ui-kit 0.9.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +30 -12
  2. package/dist/chunks/{chunk-NHOWNIAR.js → chunk-6JTZOA5I.js} +1472 -671
  3. package/dist/chunks/{chunk-DB2Y3XXA.js → chunk-OAE4IQX2.js} +2845 -1732
  4. package/dist/chunks/{chunk-KCQFOORX.js → chunk-U5SR2LZR.js} +17 -4
  5. package/dist/color-picker.js +2 -2
  6. package/dist/components/button/Button.d.ts +19 -2
  7. package/dist/components/card/Card.d.ts +1 -1
  8. package/dist/components/code-block/CodeBlock.d.ts +1 -1
  9. package/dist/components/color-picker/ColorPicker.d.ts +1 -1
  10. package/dist/components/data-table/DataTable.d.ts +6 -1
  11. package/dist/components/data-table/Row.d.ts +3 -1
  12. package/dist/components/data-table/rows.d.ts +4 -0
  13. package/dist/components/data-table/types.d.ts +5 -0
  14. package/dist/components/digit-input/DigitInput.d.ts +6 -1
  15. package/dist/components/dnd/Dnd.d.ts +1 -1
  16. package/dist/components/dnd/dragSession.d.ts +9 -0
  17. package/dist/components/dnd/useDropTarget.d.ts +5 -0
  18. package/dist/components/dnd/useFlipList.d.ts +3 -3
  19. package/dist/components/drawer/Drawer.d.ts +8 -5
  20. package/dist/components/dropdown/Dropdown.d.ts +17 -0
  21. package/dist/components/index.d.ts +9 -2
  22. package/dist/components/input/Input.d.ts +16 -1
  23. package/dist/components/input/passwordStrength.d.ts +9 -0
  24. package/dist/components/kanban/Kanban.d.ts +129 -0
  25. package/dist/components/modal/Modal.d.ts +5 -2
  26. package/dist/components/page-toolbar/PageToolbar.d.ts +53 -0
  27. package/dist/components/progress-bar/ProgressBar.d.ts +16 -1
  28. package/dist/components/segmented-control/SegmentedControl.d.ts +5 -2
  29. package/dist/components/sparkline/Sparkline.d.ts +26 -0
  30. package/dist/components/tabs/Tabs.d.ts +5 -2
  31. package/dist/hooks/useEdgeOverflow.d.ts +16 -0
  32. package/dist/hooks/useMediaQuery.d.ts +7 -0
  33. package/dist/hooks/useSwipeDismiss.d.ts +34 -0
  34. package/dist/icons/registry.d.ts +2 -0
  35. package/dist/index.css +2977 -1306
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +17 -3
  38. package/dist/internal/CheckMark.d.ts +12 -0
  39. package/dist/internal/MorphText.d.ts +12 -0
  40. package/dist/internal/RollingNumber.d.ts +14 -0
  41. package/dist/internal/overlay/FloatingPanel.d.ts +3 -1
  42. package/dist/internal/overlay/useFloatingLayer.d.ts +11 -1
  43. package/dist/internal/surfaceDepth.d.ts +25 -0
  44. package/dist/internal/swatch.d.ts +4 -1
  45. package/dist/internal/touchTarget.d.ts +4 -0
  46. package/dist/layout/app-header/AppHeader.d.ts +110 -0
  47. package/dist/layout/app-shell/AppShell.d.ts +16 -11
  48. package/dist/layout/bottom-nav/BottomNav.d.ts +80 -0
  49. package/dist/layout/index.d.ts +5 -1
  50. package/dist/motion/celebrate.d.ts +18 -0
  51. package/dist/tokens/layers.d.ts +37 -0
  52. package/dist/tokens/naming.d.ts +1 -1
  53. package/dist/tokens/primitives.d.ts +4 -0
  54. package/dist/tokens/semantic.d.ts +122 -56
  55. package/dist/tokens/themes/dark.d.ts +10 -14
  56. package/package.json +3 -2
  57. package/src/components/accordion/COMPONENT.md +5 -5
  58. package/src/components/avatar/COMPONENT.md +1 -1
  59. package/src/components/badge/COMPONENT.md +2 -2
  60. package/src/components/banner/COMPONENT.md +1 -1
  61. package/src/components/button/COMPONENT.md +26 -5
  62. package/src/components/button/examples/download.tsx +45 -0
  63. package/src/components/button/examples/hold-to-confirm.tsx +23 -0
  64. package/src/components/button/examples/label-morph.tsx +33 -0
  65. package/src/components/card/COMPONENT.md +4 -2
  66. package/src/components/card/examples/narrow.tsx +2 -2
  67. package/src/components/card/examples/panel-chart.tsx +1 -1
  68. package/src/components/card/examples/variants.tsx +1 -1
  69. package/src/components/checkbox/examples/examples.module.css +5 -3
  70. package/src/components/code-block/COMPONENT.md +6 -6
  71. package/src/components/code-block/examples/overview.tsx +1 -1
  72. package/src/components/code-block/examples/variants.tsx +1 -1
  73. package/src/components/color-picker/COMPONENT.md +1 -1
  74. package/src/components/color-swatches/COMPONENT.md +5 -5
  75. package/src/components/command-menu/COMPONENT.md +1 -1
  76. package/src/components/crossfade/COMPONENT.md +1 -1
  77. package/src/components/crossfade/examples/overview.tsx +3 -3
  78. package/src/components/data-table/COMPONENT.md +24 -10
  79. package/src/components/data-table/examples/columns-visibility.tsx +167 -0
  80. package/src/components/digit-input/COMPONENT.md +15 -13
  81. package/src/components/digit-input/examples/examples.module.css +8 -0
  82. package/src/components/digit-input/examples/in-form.tsx +2 -5
  83. package/src/components/digit-input/examples/on-complete.tsx +7 -6
  84. package/src/components/digit-input/examples/sizes.tsx +1 -1
  85. package/src/components/divider/COMPONENT.md +1 -1
  86. package/src/components/dnd/COMPONENT.md +3 -3
  87. package/src/components/dnd/examples/examples.module.css +4 -4
  88. package/src/components/drawer/COMPONENT.md +17 -9
  89. package/src/components/drawer/examples/date-sheet.tsx +41 -0
  90. package/src/components/drawer/examples/examples.module.css +5 -0
  91. package/src/components/drawer/examples/placement.tsx +4 -2
  92. package/src/components/dropdown/COMPONENT.md +20 -6
  93. package/src/components/dropdown/examples/checkbox-items.tsx +48 -0
  94. package/src/components/dropdown/examples/examples.module.css +1 -1
  95. package/src/components/empty-page/COMPONENT.md +1 -1
  96. package/src/components/empty-page/examples/examples.module.css +1 -1
  97. package/src/components/example-frame/COMPONENT.md +1 -1
  98. package/src/components/input/COMPONENT.md +10 -1
  99. package/src/components/input/examples/password-strength.tsx +12 -0
  100. package/src/components/kanban/COMPONENT.md +181 -0
  101. package/src/components/kanban/examples/controlled.tsx +78 -0
  102. package/src/components/kanban/examples/examples.module.css +31 -0
  103. package/src/components/kanban/examples/limits.tsx +58 -0
  104. package/src/components/kanban/examples/narrow.tsx +76 -0
  105. package/src/components/kanban/examples/overview.tsx +130 -0
  106. package/src/components/kanban/examples/states.tsx +75 -0
  107. package/src/components/kanban/examples/structure.tsx +76 -0
  108. package/src/components/login-form/COMPONENT.md +5 -5
  109. package/src/components/login-form/examples/register.tsx +2 -2
  110. package/src/components/login-form/examples/reset-password.tsx +2 -2
  111. package/src/components/modal/COMPONENT.md +11 -8
  112. package/src/components/modal/examples/examples.module.css +2 -2
  113. package/src/components/notification/COMPONENT.md +2 -2
  114. package/src/components/page-content/COMPONENT.md +1 -1
  115. package/src/components/page-content/examples/examples.module.css +1 -1
  116. package/src/components/page-toolbar/COMPONENT.md +142 -0
  117. package/src/components/page-toolbar/examples/examples.module.css +16 -0
  118. package/src/components/page-toolbar/examples/narrow.tsx +103 -0
  119. package/src/components/page-toolbar/examples/overview.tsx +113 -0
  120. package/src/components/page-toolbar/examples/save-changes.tsx +40 -0
  121. package/src/components/page-toolbar/examples/without-search.tsx +60 -0
  122. package/src/components/popover/COMPONENT.md +5 -4
  123. package/src/components/progress-bar/COMPONENT.md +6 -2
  124. package/src/components/progress-bar/examples/examples.module.css +7 -0
  125. package/src/components/progress-bar/examples/milestone.tsx +43 -0
  126. package/src/components/progress-bar/examples/steps.tsx +35 -0
  127. package/src/components/progress-circle/COMPONENT.md +1 -1
  128. package/src/components/scroll-container/COMPONENT.md +1 -1
  129. package/src/components/segmented-control/COMPONENT.md +8 -8
  130. package/src/components/segmented-control/examples/full-width.tsx +1 -1
  131. package/src/components/select/COMPONENT.md +2 -1
  132. package/src/components/skeleton/COMPONENT.md +1 -1
  133. package/src/components/skeleton/examples/with-crossfade.tsx +3 -3
  134. package/src/components/smart-filter/COMPONENT.md +2 -0
  135. package/src/components/sparkline/COMPONENT.md +98 -0
  136. package/src/components/sparkline/examples/examples.module.css +13 -0
  137. package/src/components/sparkline/examples/overview.tsx +27 -0
  138. package/src/components/sparkline/examples/period.tsx +46 -0
  139. package/src/components/spinner/COMPONENT.md +1 -1
  140. package/src/components/tabs/COMPONENT.md +24 -22
  141. package/src/components/tabs/examples/closable.tsx +1 -1
  142. package/src/components/tabs/examples/examples.module.css +14 -0
  143. package/src/components/tabs/examples/orientation.tsx +87 -33
  144. package/src/components/tooltip/COMPONENT.md +8 -6
  145. package/src/components/tooltip/examples/examples.module.css +2 -2
  146. package/src/layout/app-header/COMPONENT.md +178 -0
  147. package/src/layout/app-header/examples/breadcrumbs.tsx +36 -0
  148. package/src/layout/app-header/examples/examples.module.css +47 -0
  149. package/src/layout/app-header/examples/in-app-shell.tsx +70 -0
  150. package/src/layout/app-header/examples/narrow.tsx +29 -0
  151. package/src/layout/app-header/examples/overview.tsx +36 -0
  152. package/src/layout/app-shell/COMPONENT.md +35 -29
  153. package/src/layout/app-shell/examples/examples.module.css +17 -2
  154. package/src/layout/app-shell/examples/footer.tsx +59 -0
  155. package/src/layout/app-shell/examples/overview.tsx +19 -8
  156. package/src/layout/bottom-nav/COMPONENT.md +153 -0
  157. package/src/layout/bottom-nav/examples/examples.module.css +39 -0
  158. package/src/layout/bottom-nav/examples/floating.tsx +89 -0
  159. package/src/layout/bottom-nav/examples/icon-only.tsx +41 -0
  160. package/src/layout/bottom-nav/examples/in-app-shell.tsx +64 -0
  161. package/src/layout/bottom-nav/examples/overview.tsx +44 -0
  162. package/src/layout/bottom-nav/examples/router.tsx +31 -0
  163. package/src/layout/bottom-nav/examples/structure.tsx +47 -0
  164. package/src/layout/sidebar/COMPONENT.md +14 -13
  165. package/src/layout/sidebar/examples/examples.module.css +5 -5
  166. package/src/styles/globals.css +125 -2
  167. package/src/styles/theme-dark.css +39 -13
  168. package/src/styles/theme-light.css +84 -16
  169. package/src/styles/tokens.css +2 -0
@@ -1,44 +1,37 @@
1
- /**
2
- * Semantic layer (`--prime-*`). Components read only these variables.
3
- * Path → variable: `color.bg.canvas` → `--prime-color-bg-canvas`, camelCase → kebab-case.
4
- * Values below are the light theme; `themes/dark.ts` overrides colors and shadows.
5
- *
6
- * Design rules (see docs/foundation.md):
7
- * - Depth comes from fills, not borders. `border.subtle` is a hairline, `border.control` is transparent
8
- * (it becomes visible only under `prefers-contrast: more`).
9
- * - Every size is on the 4px grid. Controls share one size axis: xs 28 · s 32 · m 36 · l 40 · xl 48.
10
- * - A nested radius equals the outer radius minus the padding between them.
11
- */
12
1
  export declare const semanticTokens: {
13
2
  readonly color: {
3
+ /**
4
+ * Surface ladder, computed by `layers.ts`: `0` the page … `4` the deepest nested layer, `floating`
5
+ * for menus, popovers, modals and drawers (`floating1`/`floating2` nest inside it). Each row:
6
+ * `bg` the layer, `fill` controls on it, `fillHover`, `selected` the thumb on its track.
7
+ * Components read them through the depth context variables (globals.css), not directly.
8
+ */
9
+ readonly layer: Record<string, import("./layers").LayerColors>;
14
10
  readonly bg: {
15
- /** App background. */
16
- readonly canvas: "{color.gray.50}";
17
- /** Cards, panels, table bodies. */
18
- readonly surface: "{color.gray.0}";
19
- /** Floating layers: menus, popovers, modals, drawers. */
20
- readonly raised: "{color.gray.0}";
21
- /** Inset areas inside a surface: modal footer, table head, code. */
22
- readonly sunken: "{color.gray.75}";
23
11
  readonly inverse: "{color.gray.925}";
24
12
  readonly scrim: "rgba(17, 19, 24, 0.44)";
13
+ /** Soft shade over an edge that hides scrolled content (DataTable columns). */
14
+ readonly edgeShadow: "rgba(17, 19, 24, 0.14)";
15
+ /**
16
+ * Translucent glass over content (floating BottomNav), always with a light backdrop blur:
17
+ * clear enough that the content reads through it, as Liquid Glass.
18
+ */
19
+ readonly glass: "color-mix(in srgb, var(--prime-ref-color-gray-0) 58%, transparent)";
20
+ /** The specular top edge of a glass surface: the line that makes it read as glass. */
21
+ readonly glassEdge: "rgba(255, 255, 255, 0.85)";
25
22
  };
23
+ /**
24
+ * Translucent state washes: they sit on any background, including ones off the ladder (an
25
+ * image, a tinted banner). Resting fills of controls come from the ladder instead
26
+ * (`--prime-color-fill-muted`, `--prime-color-field-bg`).
27
+ */
26
28
  readonly fill: {
27
- /** Transparent wash: ghost hover, row hover. Works on any background. */
29
+ /** Ghost hover, row hover. */
28
30
  readonly subtle: "rgba(17, 19, 24, 0.04)";
29
31
  readonly subtleActive: "rgba(17, 19, 24, 0.07)";
30
- /**
31
- * The faintest wash: hover of a tab in the sunken strip (Tabs). Stays below the
32
- * sunken → surface step in both themes, so a hovered tab never reads as the selected one.
33
- */
32
+ /** The faintest wash: hover of a tab in the tab strip, below the selected tab's step. */
34
33
  readonly faint: "rgba(17, 19, 24, 0.04)";
35
- /**
36
- * Neutral buttons, chips, segmented track. Translucent ink wash: always one step off whatever
37
- * it sits on (canvas, card, sunken tile, a custom container), in both themes.
38
- */
39
- readonly muted: "color-mix(in srgb, var(--prime-ref-color-gray-925) 7%, transparent)";
40
- readonly mutedHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 10%, transparent)";
41
- /** Unchecked checkbox, switch track, slider track. Same wash, stronger. */
34
+ /** Unchecked checkbox, switch track, slider track. */
42
35
  readonly strong: "color-mix(in srgb, var(--prime-ref-color-gray-925) 15%, transparent)";
43
36
  readonly strongHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 22%, transparent)";
44
37
  };
@@ -96,39 +89,29 @@ export declare const semanticTokens: {
96
89
  readonly soft: "{color.sky.100}";
97
90
  readonly text: "{color.sky.800}";
98
91
  };
99
- /**
100
- * Field fill depends on what it sits on. `bg` is the canvas value; every surface component
101
- * (Card, Modal, Drawer, Popover, Sidebar…) sets `--prime-color-field-bg: var(--prime-color-field-bg-surface)`.
102
- */
92
+ /** The field fill at rest, hover and focus comes from the ladder (`--prime-color-field-bg*`). */
103
93
  readonly field: {
104
- /** Translucent wash, visible on any background; `bgSurface` kept equal for surface contexts. */
105
- readonly bg: "color-mix(in srgb, var(--prime-ref-color-gray-925) 6%, transparent)";
106
- readonly bgSurface: "color-mix(in srgb, var(--prime-ref-color-gray-925) 6%, transparent)";
107
- /** Hover wash of a field: the `bg` wash, twice as dense (≈ `bg` mixed 94% with the text color). */
108
- readonly bgHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 12%, transparent)";
109
- readonly bgFocus: "{color.gray.0}";
94
+ /** A disabled field: a faint wash on whatever it sits on. */
110
95
  readonly bgDisabled: "color-mix(in srgb, var(--prime-ref-color-gray-925) 4%, transparent)";
111
96
  };
112
- /**
113
- * Card fill depends on what it sits on (same idea as `field`). On the canvas a card is white and raised;
114
- * every surface plane (AppShell content, Card, Modal, Drawer, Popover) sets
115
- * `--prime-color-card-bg: var(--prime-color-bg-sunken)`, and a card on a sunken fill drops its shadow.
116
- */
117
- readonly card: {
118
- readonly bg: "{color.bg.surface}";
119
- };
120
97
  readonly focus: {
121
98
  readonly ring: "{color.cobalt.700}";
122
99
  };
123
100
  readonly control: {
124
101
  /** Switch thumb, slider thumb. */
125
102
  readonly thumb: "{color.white}";
126
- /** Selected segment in a segmented track: one step lighter than the track. */
127
- readonly selected: "{color.gray.0}";
128
103
  };
104
+ /**
105
+ * The tooltip is an inverse chip: everything inside it (a Kbd, a secondary line) reads its
106
+ * colors from here, through the context the chip sets (`--prime-color-text-*`, fills).
107
+ */
129
108
  readonly tooltip: {
130
109
  readonly bg: "{color.gray.925}";
131
110
  readonly text: "{color.gray.0}";
111
+ /** Secondary text on the chip (a shortcut, a second line). */
112
+ readonly muted: "{color.gray.300}";
113
+ /** A key cap or chip on the chip: a light wash over the dark fill. */
114
+ readonly fill: "rgba(255, 255, 255, 0.14)";
132
115
  };
133
116
  readonly palette: Record<string, {
134
117
  soft: string;
@@ -317,6 +300,8 @@ export declare const semanticTokens: {
317
300
  };
318
301
  /** Step between items of a staggered first render. */
319
302
  readonly stagger: "{duration.80}";
303
+ /** Blur that hides a change of shape while two states swap in place (icon → check, letters). */
304
+ readonly blur: "2px";
320
305
  /** `:active` scale of pressable controls; `compact` for icon-only and small targets. */
321
306
  readonly press: {
322
307
  readonly scale: "0.98";
@@ -430,6 +415,13 @@ export declare const semanticTokens: {
430
415
  readonly choice: "{icon.24}";
431
416
  readonly track: "0.5rem";
432
417
  };
418
+ /**
419
+ * Coarse pointers (`@media (pointer: coarse)`): the hit area of a control below this size grows
420
+ * to it (WCAG 2.5.5); the visual size stays the tier's.
421
+ */
422
+ readonly touchTarget: "2.75rem";
423
+ /** Touch screens and narrow viewports: the least text size of an editable value (iOS zoom). */
424
+ readonly touchTextSize: "{font.size.16}";
433
425
  };
434
426
  /** Badge / Kbd tiers. Inside a control of tier T use the badge tier one step down. */
435
427
  readonly badge: {
@@ -545,8 +537,6 @@ export declare const semanticTokens: {
545
537
  readonly minWidth: "12rem";
546
538
  };
547
539
  readonly card: {
548
- /** Context: surfaces set it to `none` together with `--prime-color-card-bg` (see color.card). */
549
- readonly shadow: "{shadow.raised}";
550
540
  readonly radius: "{radius.12}";
551
541
  readonly paddingS: "{space.4}";
552
542
  readonly paddingM: "{space.5}";
@@ -569,10 +559,86 @@ export declare const semanticTokens: {
569
559
  readonly widthL: "40rem";
570
560
  readonly widthXl: "50rem";
571
561
  };
572
- readonly tooltip: {
573
- readonly radius: "{radius.6}";
562
+ /** Bottom sheets: Drawer `side="bottom"`, Modal and floating layers on a narrow viewport. */
563
+ readonly sheet: {
564
+ readonly radius: "{radius.16}";
565
+ /** The grab bar at the top edge and the strip that holds it. */
566
+ readonly handleWidth: "2.25rem";
567
+ readonly handleHeight: "{space.1}";
568
+ readonly handleArea: "{space.5}";
569
+ /** Room kept above a sheet so the page behind stays visible. */
570
+ readonly topGap: "{space.10}";
571
+ /** A bottom sheet on a tablet or desktop is never narrower than this (= Modal m). */
572
+ readonly minWidth: "35rem";
573
+ };
574
+ readonly bottomNav: {
575
+ /** Bar height without the safe-area inset: icon 20 + gap 4 + label 12 and air around them. */
576
+ readonly height: "3.25rem";
577
+ /** An icon-only flat bar: no label row, so it is lower. */
578
+ readonly iconOnlyHeight: "{space.12}";
579
+ /**
580
+ * Floating capsule (iOS 26 tab bar): items 44 high — icon-only items are 44 squares, the touch
581
+ * size — inset 4 inside the capsule, so the selection capsule sits clear of its edge (52 in all).
582
+ */
583
+ readonly floatingItemSize: "2.75rem";
584
+ readonly floatingPadding: "{space.1}";
574
585
  readonly paddingX: "{space.2}";
575
- readonly paddingY: "{space.1}";
586
+ /** Between the icon and the label. */
587
+ readonly itemGap: "{space.1}";
588
+ /** The box that holds the icon and anchors the count. */
589
+ readonly indicatorWidth: "{space.7}";
590
+ readonly indicatorHeight: "{space.5}";
591
+ /** A light icon (20) over a small label (10, as the iOS tab bar): the bar stays quiet. */
592
+ readonly iconSize: "{icon.20}";
593
+ readonly labelSize: "{font.size.10}";
594
+ readonly labelLineHeight: "{font.lineHeight.12}";
595
+ /**
596
+ * Floating: the capsule's gap from the screen edges and the blur behind it — light, as Liquid
597
+ * Glass: the content stays recognisable under the glass, only softened.
598
+ */
599
+ readonly floatingInset: "{space.3}";
600
+ readonly blur: "0.5rem";
601
+ };
602
+ /**
603
+ * Tooltip tiers: a chip of tier T is as tall as a menu item of tier T (24 · 28 · 32 · 36 · 40),
604
+ * and text, padding and radius grow together, one step per tier.
605
+ */
606
+ readonly tooltip: {
607
+ readonly xs: {
608
+ readonly textSize: "{font.size.12}";
609
+ readonly lineHeight: "{font.lineHeight.16}";
610
+ readonly paddingX: "0.375rem";
611
+ readonly paddingY: "{space.1}";
612
+ readonly radius: "{radius.4}";
613
+ };
614
+ readonly s: {
615
+ readonly textSize: "{font.size.12}";
616
+ readonly lineHeight: "{font.lineHeight.16}";
617
+ readonly paddingX: "{space.2}";
618
+ readonly paddingY: "0.375rem";
619
+ readonly radius: "{radius.6}";
620
+ };
621
+ readonly m: {
622
+ readonly textSize: "{font.size.13}";
623
+ readonly lineHeight: "{font.lineHeight.20}";
624
+ readonly paddingX: "0.625rem";
625
+ readonly paddingY: "0.375rem";
626
+ readonly radius: "{radius.6}";
627
+ };
628
+ readonly l: {
629
+ readonly textSize: "{font.size.14}";
630
+ readonly lineHeight: "{font.lineHeight.20}";
631
+ readonly paddingX: "{space.3}";
632
+ readonly paddingY: "{space.2}";
633
+ readonly radius: "{radius.8}";
634
+ };
635
+ readonly xl: {
636
+ readonly textSize: "{font.size.16}";
637
+ readonly lineHeight: "{font.lineHeight.24}";
638
+ readonly paddingX: "{space.4}";
639
+ readonly paddingY: "{space.2}";
640
+ readonly radius: "{radius.8}";
641
+ };
576
642
  readonly maxWidth: "17.5rem";
577
643
  /** Arrow base and depth; the arrow points at the trigger's centre. */
578
644
  readonly arrowWidth: "0.625rem";
@@ -1,20 +1,18 @@
1
1
  export declare const darkThemeOverrides: {
2
2
  readonly color: {
3
+ readonly layer: Record<string, import("../layers").LayerColors>;
3
4
  readonly bg: {
4
- readonly canvas: "{color.gray.950}";
5
- readonly surface: "{color.gray.900}";
6
- readonly raised: "{color.gray.875}";
7
- /** = canvas: nested tiles on the 900 surface need a clear step (925 read as 1.03:1). */
8
- readonly sunken: "{color.gray.950}";
9
5
  readonly inverse: "{color.gray.100}";
10
6
  readonly scrim: "rgba(0, 0, 0, 0.6)";
7
+ /** Darker than the scrim's share: a shade on a 900 surface needs more ink to read. */
8
+ readonly edgeShadow: "rgba(0, 0, 0, 0.5)";
9
+ readonly glass: "color-mix(in srgb, var(--prime-ref-color-gray-875) 56%, transparent)";
10
+ readonly glassEdge: "rgba(255, 255, 255, 0.16)";
11
11
  };
12
12
  readonly fill: {
13
13
  readonly subtle: "rgba(233, 235, 240, 0.05)";
14
14
  readonly subtleActive: "rgba(233, 235, 240, 0.09)";
15
15
  readonly faint: "rgba(233, 235, 240, 0.02)";
16
- readonly muted: "color-mix(in srgb, var(--prime-ref-color-gray-100) 7%, transparent)";
17
- readonly mutedHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 10%, transparent)";
18
16
  readonly strong: "color-mix(in srgb, var(--prime-ref-color-gray-100) 16%, transparent)";
19
17
  readonly strongHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 22%, transparent)";
20
18
  };
@@ -68,10 +66,6 @@ export declare const darkThemeOverrides: {
68
66
  readonly text: "{color.sky.300}";
69
67
  };
70
68
  readonly field: {
71
- readonly bg: "color-mix(in srgb, var(--prime-ref-color-gray-100) 6%, transparent)";
72
- readonly bgSurface: "color-mix(in srgb, var(--prime-ref-color-gray-100) 6%, transparent)";
73
- readonly bgHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 12%, transparent)";
74
- readonly bgFocus: "{color.gray.925}";
75
69
  readonly bgDisabled: "color-mix(in srgb, var(--prime-ref-color-gray-100) 4%, transparent)";
76
70
  };
77
71
  readonly focus: {
@@ -79,11 +73,13 @@ export declare const darkThemeOverrides: {
79
73
  };
80
74
  readonly control: {
81
75
  readonly thumb: "{color.gray.100}";
82
- readonly selected: "{color.gray.750}";
83
76
  };
77
+ /** A light chip over the dark UI: inverse like in light, so it reads over any layer. */
84
78
  readonly tooltip: {
85
- readonly bg: "{color.gray.750}";
86
- readonly text: "{color.gray.100}";
79
+ readonly bg: "{color.gray.100}";
80
+ readonly text: "{color.gray.925}";
81
+ readonly muted: "{color.gray.600}";
82
+ readonly fill: "rgba(17, 19, 24, 0.08)";
87
83
  };
88
84
  readonly palette: Record<string, {
89
85
  soft: string;
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "prime-ui-kit",
3
- "version": "0.9.1",
4
- "description": "React 19 UI kit for product interfaces: 57 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y. Ships COMPONENT.md docs and copyable examples.",
3
+ "version": "0.9.3",
4
+ "packageManager": "bun@1.3.13",
5
+ "description": "React 19 UI kit for product interfaces: 61 composable components on one design contract — forms, selects, tables, overlays, navigation, app shell. CSS Modules, --prime-* tokens, light and dark themes, TypeScript, a11y. Ships COMPONENT.md docs and copyable examples.",
5
6
  "main": "./dist/index.js",
6
7
  "module": "./dist/index.js",
7
8
  "types": "./dist/index.d.ts",
@@ -1,6 +1,6 @@
1
1
  # Accordion
2
2
 
3
- **Category:** layout
3
+ **Category:** data-display
4
4
  **Kind:** navigation
5
5
 
6
6
  > Collapsible sections: FAQ, settings groups, checkout steps.
@@ -96,8 +96,8 @@ Accordion.Root frame (grouped) or column of cards (separate)
96
96
  ### layout
97
97
  | Value | Looks like | Use when | Default |
98
98
  |---|---|---|---|
99
- | `grouped` | One `card-bg` surface with card radius, `border-subtle` hairlines between items, no shadow of its own | FAQ, short settings lists | yes |
100
- | `separate` | Each item is a separate card (`card-bg`, card radius, `card-shadow`) with `--prime-space-2` between them | Heavier sections, checkout steps | |
99
+ | `grouped` | One surface — a layer of the ladder (`layer-current`) — with card radius, `border-subtle` hairlines between items, no shadow of its own | FAQ, short settings lists | yes |
100
+ | `separate` | Each item is a separate card (`layer-current`, card radius, the raised whisper on the page) with `--prime-space-2` between them | Heavier sections, checkout steps | |
101
101
 
102
102
  ### size
103
103
  | Value | Looks like | Use when | Default |
@@ -122,7 +122,7 @@ Accordion.Root frame (grouped) or column of cards (separate)
122
122
  |---|---|---|
123
123
  | open / closed | `value` / `defaultValue` + `onValueChange` | `data-state="open" \| "closed"` on Item, Trigger and Content; `aria-expanded` on Trigger; `inert` on a closed Content |
124
124
  | disabled | `disabled` on Item | `data-disabled` on Item and Trigger, native `disabled`, `text-disabled`, `cursor: not-allowed` |
125
- | hover / active | pointer | trigger `fill-subtle` / `fill-subtle-active` |
125
+ | hover / active | pointer | trigger `fill-muted-hover` (two ladder steps off the accordion's layer, so it never lands on the host's color at the edges) / `fill-strong` |
126
126
  | focus-visible | keyboard | inset focus ring inside the trigger |
127
127
 
128
128
  Root: `data-size`, `data-layout`. The panel height transitions through `grid-template-rows: 0fr → 1fr` (no JS measuring, follows content that resizes while open) and the body fades in while settling by `--prime-space-1`; open uses `base` + `enter`, close `fast` + `exit`. The chevron rotates over `base`. Instant under `prefers-reduced-motion`.
@@ -130,7 +130,7 @@ Root: `data-size`, `data-layout`. The panel height transitions through `grid-tem
130
130
  ## Layout & spacing
131
131
  - Takes the full width of its parent (`width: 100%`); the parent sets the measure.
132
132
  - Panel content is a flex column with `--prime-space-3` gap; it reserves `--prime-focus-space` at the top so focus rings of fields inside are not clipped.
133
- - Fields inside panels get the surface field fill automatically.
133
+ - The root is a layer of the surface ladder (`data-depth`): fields inside panels take that layer's fill automatically.
134
134
  - Stack several accordions or an accordion and other blocks with the page gap (`--prime-space-8` between groups).
135
135
 
136
136
  ## Accessibility
@@ -156,7 +156,7 @@ No keyboard interaction.
156
156
  - `alt="Анна Климова"` while the name is printed next to it → `alt=""`.
157
157
  - A random `color` on every render → derive it from the user id.
158
158
  - A custom presence dot → use `Avatar.Status`.
159
- - A group on a custom filled block with a canvas-colored ring → set `--avatar-ring` to that fill.
159
+ - A group on a custom filled block with a ring in the wrong color → set `--avatar-ring` to that fill (by default the ring is the current layer, `--prime-color-layer-current`).
160
160
  - `size` on every avatar in a group → set it once on `Avatar.Group`.
161
161
 
162
162
  ## Related
@@ -30,7 +30,7 @@ Badge.Root <span>: fill, tier dimensions
30
30
  ├─ [start segment] Badge.Icon or Badge.Dot at the start edge: full height, mark centred
31
31
  ├─ body text, Badge.Dot, inline Badge.Icon
32
32
  │ (a <button> with onPress; a <span> with a trailing segment; none when read-only)
33
- ├─ Badge.Action optional end segment revealed on hover / focus
33
+ ├─ Badge.Action optional end segment revealed on hover / focus (always shown on touch screens)
34
34
  └─ remove segment with onRemove: full height, the whole end is the hit area (Icon action.close)
35
35
  ```
36
36
 
@@ -70,7 +70,7 @@ Badge.Root <span>: fill, tier dimensions
70
70
  | `…rest` | `HTMLAttributes<HTMLSpanElement>` | — | `className` and the other span attributes. |
71
71
 
72
72
  ### Badge.Action
73
- `ref` → `HTMLButtonElement`. A `<button>` segment at the end, revealed on hover and focus; the badge reserves its room, so the width never changes. One per badge, not together with `onRemove`.
73
+ `ref` → `HTMLButtonElement`. A `<button>` segment at the end, revealed on hover and focus, always shown on touch screens; the badge reserves its room, so the width never changes. One per badge, not together with `onRemove`.
74
74
 
75
75
  | Prop | Type | Default | Description |
76
76
  |---|---|---|---|
@@ -1,6 +1,6 @@
1
1
  # Banner
2
2
 
3
- **Category:** feedback
3
+ **Category:** status
4
4
  **Kind:** primitive
5
5
 
6
6
  > Full-width in-flow message for a page, section or card: status icon, title, description, actions and dismiss.
@@ -7,7 +7,9 @@
7
7
 
8
8
  ## When to use
9
9
  - Primary, secondary and destructive actions in forms, toolbars, dialogs, cards and empty states.
10
- - Async actions: `loading` shows a spinner, blocks clicks and keeps the width.
10
+ - Async actions: `loading` shows a spinner, blocks clicks and keeps the width; when it ends, a new label («Сохранено») flows in.
11
+ - Long actions with known progress (downloads, exports): `progress` fills the button itself.
12
+ - Destructive actions without a dialog: `holdToConfirm` needs a held press.
11
13
  - Compact toolbars: icon-only buttons (only `Button.Icon` inside) are square.
12
14
  - Action-styled navigation: `asChild` with an `<a>` or a router link.
13
15
 
@@ -26,8 +28,11 @@ import { Button } from "prime-ui-kit";
26
28
  ## Anatomy
27
29
  ```
28
30
  Button.Root <button> (or the single child with asChild); variant, tone, size; size tier for nested icons
31
+ ├─ (fill) progress / hold wash under the label, rendered by Root once used, aria-hidden
29
32
  ├─ (Spinner) rendered by Root while loading, aria-hidden
33
+ ├─ (label) text children; a changed label flows in letter by letter
30
34
  └─ Button.Icon decorative icon wrapper (aria-hidden), sized to the tier
35
+ (hold hint) visually hidden description beside the button with holdToConfirm
31
36
  ```
32
37
 
33
38
  ## API
@@ -44,10 +49,14 @@ Button.Root <button> (or the single child with asChild); variant, tone, s
44
49
  | `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `host tier, else "m"` | Control tier: height 28 · 32 · 36 · 40 · 48, padding, text, icon, radius. Without it the button takes the tier of its host (LoginForm, Popover, a field, a panel with a size), else `m`. |
45
50
  | `fullWidth` | `boolean` | — | Stretches to the container width. |
46
51
  | `loading` | `boolean` | `false` | Shows a `Spinner` in place of the leading icon or over the label, sets `aria-busy`, blocks clicks; width does not change. With `asChild` no spinner is added — the child owns its content. |
52
+ | `progress` | `number` | — | Progress of a long action started by the button, `0…1`: a wash of the text color grows inside from the start edge, `aria-busy` is set and the button stays pressable (to cancel). Put the number in the label («Скачивание 42%»); remove the prop when done and the fill fades out. |
53
+ | `holdToConfirm` | `boolean` | `false` | The action needs a held press: the fill runs for 1.2 s (a gesture clock, kept under reduced motion) and `onConfirm` fires at its end. Releasing, leaving or losing focus earlier rolls it back. Space and Enter hold too; the gesture is described to assistive tech by `labels.holdHint`. |
54
+ | `onConfirm` | `() => void` | — | Fires when a `holdToConfirm` press completes; the action goes here, not in `onClick`. |
55
+ | `labels` | `Partial<ButtonLabels>` | — | Built-in strings, see Labels. |
47
56
  | `asChild` | `boolean` | `false` | Merges Button props and styles onto the single child element instead of rendering `<button>`. `disabled`/`loading` become `aria-disabled`. |
48
57
  | `type` | `"button" \| "submit" \| "reset"` | `"button"` | Native button type; not forwarded with `asChild`. |
49
58
  | `disabled` | `boolean` | — | Disabled state; `loading` also disables. |
50
- | `children` | `ReactNode` | — | Label and `Button.Icon`. Only `Button.Icon` children → square icon-only button; give it `aria-label`. |
59
+ | `children` | `ReactNode` | — | Label and `Button.Icon`. Only `Button.Icon` children → square icon-only button; give it `aria-label`. A changed text label flows into the new one letter by letter while the width glides; a change of digits only («58 с» → «57 с», «42%» → «43%») stays in place. |
51
60
  | `…rest` | `Omit<ButtonHTMLAttributes<HTMLButtonElement>, "size">` | — | `onClick`, `className`, `aria-*`, `data-*` and the other button attributes. |
52
61
 
53
62
  ### Button.Icon
@@ -109,6 +118,9 @@ A Button lines up exactly with Input, Select, Datepicker trigger, SegmentedContr
109
118
  | focus-visible | keyboard | outer focus ring with `--prime-focus-offset`; `tone="inherit"` draws it in `currentColor` |
110
119
  | disabled | `disabled` | native `disabled`, `data-disabled="true"`, `fill-muted` + `text-disabled`, `cursor: not-allowed`; ghost stays transparent |
111
120
  | loading | `loading` (native `<button>`) | `data-loading="true"`, native `disabled`, `aria-busy="true"`, colors kept (busy, not unavailable), no hover or press; a `Spinner` (`aria-hidden`) replaces the leading (or only) icon, otherwise it is centered over the hidden label (`data-loading-overlay="true"`) |
121
+ | progress | `progress` (0…1) | `data-progress="true"`, `aria-busy="true"`, still pressable; a plate of the text color with the button's radius slides in from the start edge (`fast` · standard), so corners match at any value; removing the prop fades it where it is |
122
+ | holding | `holdToConfirm` + a held pointer, `Space` or `Enter` | `data-hold="holding"`: the fill runs linearly for 1.2 s (kept under reduced motion — it is a gesture clock), then `onConfirm` and `data-hold="done"` (on release the fill fades in place, `data-hold="idle"`); releasing earlier → `data-hold="cancel"`, the fill rolls back (`fast` · exit) |
123
+ | label change | new text children | the old letters leave upward, the new ones rise from below one short step apart (`base` · emphasized, blur `--prime-motion-blur`) while the width glides; digits-only changes and reduced motion change in place |
112
124
  | asChild disabled / loading | `asChild` + `disabled`/`loading` | `aria-disabled="true"`, `pointer-events: none`, click `preventDefault`; no native `disabled`, no automatic spinner |
113
125
 
114
126
  Other data attributes: `data-variant`, `data-tone`, `data-size`, `data-full-width`, `data-icon-only`, `data-leading-icon`, `data-trailing-icon`.
@@ -123,19 +135,23 @@ Other data attributes: `data-variant`, `data-tone`, `data-size`, `data-full-widt
123
135
  ### Keyboard
124
136
  | Key | Action |
125
137
  |---|---|
126
- | `Enter` · `Space` | Presses the button (native `<button>`). |
138
+ | `Enter` · `Space` | Presses the button (native `<button>`); with `holdToConfirm` the key is held until the fill completes. |
127
139
  | `Tab` | Moves focus; a disabled button is skipped, with `asChild` it stays focusable with `aria-disabled`. |
128
140
 
129
141
  ### ARIA
130
142
  - Native `<button>`, `type="button"` by default, so it never submits a form by accident.
131
143
  - Icon-only buttons need `aria-label`; `Button.Icon` is `aria-hidden`.
132
- - `loading` sets `aria-busy="true"` and blocks the press.
144
+ - `loading` sets `aria-busy="true"` and blocks the press; `progress` sets `aria-busy` and keeps the button pressable.
145
+ - `holdToConfirm` describes the gesture through `aria-describedby` (`labels.holdHint`); `Space` / `Enter` hold like a pointer.
146
+ - A changed label is announced once: the animated letters are hidden, the name is the new text.
133
147
  - With `asChild` the disabled state is `aria-disabled="true"` without a native `disabled`.
134
148
 
135
149
  ### Labels
136
150
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
137
151
 
138
- No `labels`.
152
+ | Key | Default | Used for |
153
+ |---|---|---|
154
+ | `holdHint` | `"Удерживайте, чтобы подтвердить"` | Description of a `holdToConfirm` button for assistive tech (`aria-describedby`). |
139
155
 
140
156
  ## Examples
141
157
  | Example | Shows |
@@ -145,6 +161,9 @@ No `labels`.
145
161
  | [sizes.tsx](examples/sizes.tsx) | Every size tier, 28 to 48 px high — `size`. |
146
162
  | [states.tsx](examples/states.tsx) | Disabled and loading next to the default; the spinner keeps the width — `disabled`, `loading`. |
147
163
  | [with-icon.tsx](examples/with-icon.tsx) | An icon before or after the label, and a square icon-only button — `Button.Icon`, `aria-label`. |
164
+ | [download.tsx](examples/download.tsx) | A long download inside the button: the fill shows how far it got, the label counts and then offers the file — `progress`. |
165
+ | [hold-to-confirm.tsx](examples/hold-to-confirm.tsx) | A destructive action that needs a held press; letting go early rolls the fill back and does nothing — `holdToConfirm`, `onConfirm`. |
166
+ | [label-morph.tsx](examples/label-morph.tsx) | A new label flows into the button letter by letter while the width glides: save, then saved; a step flow — `children`, `loading`. |
148
167
  | [on-colored-host.tsx](examples/on-colored-host.tsx) | Actions on a colored strip take its text color: a ghost icon close and a soft action — `tone="inherit"`. |
149
168
  | [as-child.tsx](examples/as-child.tsx) | The button look on a real link; a disabled link blocks navigation — `asChild`, `disabled`. |
150
169
  | [in-form.tsx](examples/in-form.tsx) | A full-width submit button that shows the request in progress — `type`, `loading`, `fullWidth`. |
@@ -156,6 +175,8 @@ No `labels`.
156
175
  - Putting a `<Spinner />` inside a loading button → just set `loading`; the button places it.
157
176
  - `<a>` inside `<Button.Root>` → use `asChild` so there is one interactive element.
158
177
  - Submit button without `type="submit"` → the default is `"button"` and will not submit.
178
+ - A destructive `holdToConfirm` action in `onClick` → it fires on any click; put it in `onConfirm`.
179
+ - Rebuilding a progress label with a new word every tick («Загрузка…» / «Скачивание…») → keep the words, change only the number; digits update in place, words morph.
159
180
 
160
181
  ## Related
161
182
  - **Built from:** [Spinner](../spinner/COMPONENT.md)
@@ -0,0 +1,45 @@
1
+ /** A long download inside the button: the fill shows how far it got, the label counts and then offers the file — `progress`. */
2
+ import { Button, Icon } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ /** Share of the file received at each network tick. */
6
+ const TICKS = [0.06, 0.15, 0.27, 0.34, 0.48, 0.61, 0.66, 0.79, 0.88, 0.97, 1];
7
+
8
+ export default function ButtonDownloadExample() {
9
+ const [tick, setTick] = React.useState<number | null>(null);
10
+ const [ready, setReady] = React.useState(false);
11
+
12
+ React.useEffect(() => {
13
+ if (tick === null) return;
14
+ const timer = window.setTimeout(() => {
15
+ if (tick + 1 < TICKS.length) setTick(tick + 1);
16
+ else {
17
+ setTick(null);
18
+ setReady(true);
19
+ }
20
+ }, 220);
21
+ return () => window.clearTimeout(timer);
22
+ }, [tick]);
23
+
24
+ const progress = tick === null ? undefined : TICKS[tick];
25
+
26
+ return (
27
+ <Button.Root
28
+ variant={ready ? "soft" : "solid"}
29
+ progress={progress}
30
+ onClick={() => {
31
+ if (ready) setReady(false);
32
+ else if (tick === null) setTick(0);
33
+ }}
34
+ >
35
+ <Button.Icon>
36
+ <Icon name={ready ? "action.externalLink" : "action.download"} />
37
+ </Button.Icon>
38
+ {progress !== undefined
39
+ ? `Скачивание ${Math.round(progress * 100)}%`
40
+ : ready
41
+ ? "Открыть отчёт"
42
+ : "Скачать отчёт за октябрь"}
43
+ </Button.Root>
44
+ );
45
+ }
@@ -0,0 +1,23 @@
1
+ /** A destructive action that needs a held press; letting go early rolls the fill back and does nothing — `holdToConfirm`, `onConfirm`. */
2
+ import { Button, Icon } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ export default function ButtonHoldToConfirmExample() {
6
+ const [deleted, setDeleted] = React.useState(false);
7
+
8
+ return deleted ? (
9
+ <Button.Root variant="ghost" tone="neutral" onClick={() => setDeleted(false)}>
10
+ <Button.Icon>
11
+ <Icon name="action.refresh" />
12
+ </Button.Icon>
13
+ Восстановить проект
14
+ </Button.Root>
15
+ ) : (
16
+ <Button.Root variant="soft" tone="danger" holdToConfirm onConfirm={() => setDeleted(true)}>
17
+ <Button.Icon>
18
+ <Icon name="action.delete" />
19
+ </Button.Icon>
20
+ Удерживайте, чтобы удалить проект
21
+ </Button.Root>
22
+ );
23
+ }
@@ -0,0 +1,33 @@
1
+ /** A new label flows into the button letter by letter while the width glides: save, then saved; a step flow — `children`, `loading`. */
2
+ import { Button } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ const STEPS = ["Продолжить", "Подтвердить оплату", "Оплачено"];
6
+
7
+ export default function ButtonLabelMorphExample() {
8
+ const [save, setSave] = React.useState<"idle" | "saving" | "saved">("idle");
9
+ const [step, setStep] = React.useState(0);
10
+
11
+ React.useEffect(() => {
12
+ if (save === "idle") return;
13
+ const timer = window.setTimeout(
14
+ () => setSave(save === "saving" ? "saved" : "idle"),
15
+ save === "saving" ? 1200 : 2000,
16
+ );
17
+ return () => window.clearTimeout(timer);
18
+ }, [save]);
19
+
20
+ return (
21
+ <>
22
+ <Button.Root
23
+ variant="soft"
24
+ tone="neutral"
25
+ loading={save === "saving"}
26
+ onClick={() => setSave("saving")}
27
+ >
28
+ {save === "saved" ? "Изменения сохранены" : "Сохранить изменения"}
29
+ </Button.Root>
30
+ <Button.Root onClick={() => setStep((step + 1) % STEPS.length)}>{STEPS[step]}</Button.Root>
31
+ </>
32
+ );
33
+ }
@@ -39,7 +39,7 @@ Card.Root variant="cover" Media · Body(Title, Description) · Footer
39
39
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
40
40
 
41
41
  ### Card.Root
42
- `ref` → `HTMLDivElement`. The filled surface (card fill, radius 12, raised shadow, no border) and a size container; `variant` picks the template layout of its parts.
42
+ `ref` → `HTMLDivElement`. A layer of the surface ladder (`data-depth` one above the surface around it: white on the light page, the next layer when nested; radius 12, the raised whisper only on the page, no border) and a size container; `variant` picks the template layout of its parts.
43
43
 
44
44
  | Prop | Type | Default | Description |
45
45
  |---|---|---|---|
@@ -143,13 +143,15 @@ Card.Root variant="cover" Media · Body(Title, Description) · Footer
143
143
  |---|---|---|
144
144
  | template | `variant` | `data-variant` |
145
145
  | flat | `flat` | `data-flat` |
146
+ | depth | nesting: one above the nearest surface (the page and the AppShell panel are 0) | `data-depth` (`1`–`4`, `floating-1`/`floating-2` inside a floating layer); the fill is `--prime-color-layer-current`, the shadow only at depth 1 |
146
147
  | card width | container queries | `split` stacks below 22rem, `Header` wraps below 22rem, the `stat-trend` value changes at 20rem / 36rem |
147
148
 
148
149
  Card is static: no hover, focus or press of its own.
149
150
 
150
151
  ## Layout & spacing
151
152
  - Grid of cards: `repeat(auto-fit, minmax(min(100%, 14rem), 1fr))`, gap `--prime-space-4`.
152
- - Padding `--prime-card-padding-m` (20); `Body` gap `--prime-card-gap` (16); fields inside `Body` 20 apart, on the surface field fill.
153
+ - Padding `--prime-card-padding-m` (20); `Body` gap `--prime-card-gap` (16); fields inside `Body` 20 apart, one ladder step off the card.
154
+ - Depth comes from nesting (foundation §4): on the page — the AppShell panel is the page — a card is layer 1 (white in light) with the raised whisper; inside another card it is the next layer, a flat tile. The ladder stops at layer 4.
153
155
  - Long values wrap, labels truncate; a chart SVG needs a CSS height.
154
156
  - In a `Header` the title takes the free width; a control or a caption after it sits at the end without a wrapper.
155
157
 
@@ -28,7 +28,7 @@ export default function CardNarrowExample() {
28
28
  <Icon name="object.wallet" />
29
29
  </Card.Icon>
30
30
  <Card.Label>Средний чек</Card.Label>
31
- <Card.Value>₽ 2 450</Card.Value>
31
+ <Card.Value>2 450 ₽</Card.Value>
32
32
  </div>
33
33
  </Card.Body>
34
34
  </Card.Root>
@@ -36,7 +36,7 @@ export default function CardNarrowExample() {
36
36
  <div className={width.className}>
37
37
  <Card.Root variant="stat-trend">
38
38
  <Card.Label>Выручка</Card.Label>
39
- <Card.Value>₽ 4,2 млн</Card.Value>
39
+ <Card.Value>4,2 млн ₽</Card.Value>
40
40
  <Card.Delta tone="success">+18% к прошлому месяцу</Card.Delta>
41
41
  </Card.Root>
42
42
  </div>
@@ -23,7 +23,7 @@ export default function CardPanelChartExample() {
23
23
  </Card.Header>
24
24
  <Card.Body>
25
25
  <Typography variant="body-s" tone="secondary">
26
- С начала квартала: ₽ 12,6 млн, план выполнен на 84%.
26
+ С начала квартала: 12,6 млн ₽, план выполнен на 84%.
27
27
  </Typography>
28
28
  </Card.Body>
29
29
  <Card.Media>