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,6 +1,6 @@
1
1
  # Dnd
2
2
 
3
- **Category:** layout
3
+ **Category:** interaction
4
4
  **Kind:** composite
5
5
 
6
6
  > Pointer-driven drag and drop: reorderable lists, draggable items and drop zones on one shared session, with a lifted clone, a gap where the item lands, auto-scroll, touch support and a keyboard path.
@@ -131,10 +131,10 @@ Generic `Dnd.DropZone<TData>`; `ref` → `HTMLElement`. Renders the `as` element
131
131
  | State | Driven by | DOM |
132
132
  |---|---|---|
133
133
  | idle | — | items show `cursor: grab` |
134
- | carried | a drag | the clone follows the pointer, lifted on `--prime-shadow-modal`, scale 1.02; a sortable source leaves the layout (`data-lifted`) and a dashed accent gap of its size and radius fades in; neighbours glide aside; a `Dnd.Draggable` stays at 0.4 opacity (`data-dragging`) |
134
+ | carried | a drag | the clone follows the pointer, lifted on `--prime-shadow-modal`, scale 1.02; a sortable source leaves the layout (`data-lifted`) and a dashed accent gap of its size and radius fades in and glides with the items as the landing spot moves; entering a connected list, it fades out in the list it left and in where it opens; crossing empty space (the gutter between lists) leaves the gap in the last list that takes the item; neighbours glide aside; a `Dnd.Draggable` stays at 0.4 opacity (`data-dragging`) |
135
135
  | over a zone | pointer over an accepting zone | `data-dnd-over`: `accent-soft` fill, dashed accent outline |
136
136
  | refused | `canDrop` returns `false` | `data-dnd-reject`: `danger-soft` fill; the clone gets a dashed `danger` outline |
137
- | dropped | release | the clone glides to the item's new place and is swapped for the real element; `data-dnd-flash` with `flashOnDrop` |
137
+ | dropped | release over a target, or over empty space while a list holds the gap | the clone glides to the item's new place and is swapped for the real element; `data-dnd-flash` with `flashOnDrop`; the clone sets down into the look the item will have in place — its resting shadow, or under the mouse its hover look: the item gets `data-dnd-hover` until the pointer leaves it or presses, so a host styles its hover lift on `:is(:hover, [data-dnd-hover])` and the card never dips to flat and lifts again |
138
138
  | cancelled | Escape, a system-cancelled pointer | the clone flies back, nothing changes |
139
139
  | disabled | `disabled` | `data-disabled`, no `aria-roledescription` |
140
140
 
@@ -14,8 +14,8 @@
14
14
  min-width: 0;
15
15
  padding: var(--prime-space-3) var(--prime-space-4);
16
16
  border-radius: var(--prime-radius-m);
17
- background: var(--prime-color-card-bg);
18
- box-shadow: var(--prime-card-shadow);
17
+ background: var(--prime-color-layer-nested);
18
+ box-shadow: var(--prime-shadow-raised);
19
19
  }
20
20
 
21
21
  /* The grip sits closer to the edge: it is a square button with its own inner room. */
@@ -45,7 +45,7 @@
45
45
  min-height: calc(var(--prime-space-24) * 2);
46
46
  padding: var(--prime-space-3);
47
47
  border-radius: var(--prime-radius-l);
48
- background: var(--prime-color-bg-sunken);
48
+ background: var(--prime-color-fill-muted);
49
49
  }
50
50
 
51
51
  .columnHeader {
@@ -61,7 +61,7 @@
61
61
  gap: var(--prime-space-1);
62
62
  padding: var(--prime-space-3) var(--prime-space-4);
63
63
  border-radius: var(--prime-radius-m);
64
- background: var(--prime-color-bg-surface);
64
+ background: var(--prime-color-layer-nested);
65
65
  box-shadow: var(--prime-shadow-raised);
66
66
  }
67
67
 
@@ -3,13 +3,14 @@
3
3
  **Category:** overlays
4
4
  **Kind:** overlay
5
5
 
6
- > A modal side panel that slides in from the edge: filters, forms and record details.
6
+ > A modal panel that slides in from the edge: filters, forms and record details at the side, a bottom sheet on a phone.
7
7
 
8
8
  ## When to use
9
9
  - Editing settings or a record without leaving the page.
10
10
  - Filters or navigation on narrow screens (`side="left"`).
11
11
  - Read-only details of a list row (order, user, event).
12
12
  - Long content (history, logs) that needs a tall scrolling area.
13
+ - A short task on a phone — pick a date, choose an action, fill two fields — in a bottom sheet (`side="bottom"`).
13
14
 
14
15
  ## When not to use
15
16
  - Confirmations and short decisions → use [Modal](../modal/COMPONENT.md).
@@ -27,6 +28,7 @@ import { Drawer } from "prime-ui-kit";
27
28
  Drawer.Root state and dismiss policy (no DOM)
28
29
  ├─ Drawer.Trigger clones its child; opens on click
29
30
  └─ Drawer.Content portal + scrim + role="dialog" panel at the edge
31
+ ├─ (grab handle) side="bottom" only: aria-hidden bar, a drag starts here
30
32
  ├─ Drawer.Header grid: [Icon] [Title + Description] [close Button]
31
33
  │ ├─ Drawer.Icon
32
34
  │ ├─ Drawer.Title <h2>
@@ -49,7 +51,7 @@ No DOM, no ref. State and dismiss policy.
49
51
  | `defaultOpen` | `boolean` | `false` | Initial visibility, uncontrolled. |
50
52
  | `onOpenChange` | `(open: boolean) => void` | — | Called on every open and close: trigger, close button, `Close`, Escape, scrim click, code. |
51
53
  | `closeOnEscape` | `boolean` | `true` | Escape closes the dialog. |
52
- | `closeOnOutsideClick` | `boolean` | `true` | A click on the scrim closes the dialog; turn off for destructive confirms. |
54
+ | `closeOnOutsideClick` | `boolean` | `true` | A click on the scrim (and a swipe on a bottom sheet) closes the dialog; turn off for destructive confirms. |
53
55
  | `labels` | `Partial<DrawerLabels>` | — | Built-in strings, see Labels. |
54
56
  | `children` | `ReactNode` | — | Trigger and Content. |
55
57
 
@@ -58,8 +60,8 @@ No DOM, no ref. State and dismiss policy.
58
60
 
59
61
  | Prop | Type | Default | Description |
60
62
  |---|---|---|---|
61
- | `side` | `"left" \| "right"` | `"right"` | Edge the panel slides from; rounded only on the edge facing the page. |
62
- | `size` | `"s" \| "m" \| "l" \| "xl"` | `"m"` | Width: 360 · 480 · 640 · 800. Below 640 px of viewport — full width, square corners. |
63
+ | `side` | `"left" \| "right" \| "bottom"` | `"right"` | Edge the panel slides from; rounded only on the edge facing the page. `bottom` is a sheet: a grab handle on top, height by content. A swipe toward the edge closes the panel (with `closeOnOutsideClick`): a bottom sheet from its handle or header, a side drawer by touch anywhere (by mouse from the header). |
64
+ | `size` | `"s" \| "m" \| "l" \| "xl"` | `"m"` | Width: 360 · 480 · 640 · 800; a bottom sheet is centred and at least 560 (`--prime-sheet-min-width`). Below 640 px of viewport — full width; side drawers lose their corners. |
63
65
  | `aria-label` | `string` | — | Dialog name when there is no Title. |
64
66
  | `aria-labelledby` | `string` | — | Overrides the Title id. |
65
67
  | `aria-describedby` | `string` | — | Overrides the Description id. |
@@ -94,7 +96,7 @@ No DOM, no ref. State and dismiss policy.
94
96
 
95
97
  | Prop | Type | Default | Description |
96
98
  |---|---|---|---|
97
- | `layout` | `"fill" \| "end"` | `"end"` | `fill`: equal-width buttons in one row; `end`: auto width, at the end. Narrower than 360 px — stacked. |
99
+ | `layout` | `"fill" \| "end"` | `"end"` | `fill`: equal-width buttons in one row; `end`: auto width, at the end. Stacked on phones (viewport below 640 px) and in a dialog narrower than 360 px. |
98
100
 
99
101
  ### Drawer.Trigger · Drawer.Close
100
102
  No DOM: clone the single child and chain its `onClick` (unless the child's handler calls `preventDefault()`).
@@ -110,6 +112,7 @@ No DOM: clone the single child and chain its `onClick` (unless the child's handl
110
112
  |---|---|---|---|
111
113
  | `right` | slides from the right, rounded on its left edge | details and forms | yes |
112
114
  | `left` | slides from the left, rounded on its right edge | filters, mobile navigation | |
115
+ | `bottom` | rises from the bottom, rounded on top, grab handle, height by content | a short task on a phone | |
113
116
 
114
117
  ### size (Content)
115
118
  | Value | Looks like | Use when | Default |
@@ -119,7 +122,7 @@ No DOM: clone the single child and chain its `onClick` (unless the child's handl
119
122
  | `l` | 640 wide | wide forms | |
120
123
  | `xl` | 800 wide | tables, previews | |
121
124
 
122
- Below 640px of viewport every size is full width with square corners.
125
+ A bottom sheet is centred at its width, never narrower than 560 (`--prime-sheet-min-width`), so a tablet gets a real sheet. Below 640px of viewport every size is full width; side drawers lose their corners, the sheet keeps its top radius.
123
126
 
124
127
  ### layout (Footer)
125
128
  | Value | Looks like | Use when | Default |
@@ -135,13 +138,15 @@ Below 640px of viewport every size is full width with square corners.
135
138
  |---|---|---|
136
139
  | open / closed | `open` / `defaultOpen` / `onOpenChange` | `data-state` on the scrim and the panel; mounted until the slide-out ends |
137
140
  | side / size | Content props | `data-side`, `data-size` on the panel |
141
+ | swiping | a drag toward the edge (with `closeOnOutsideClick`) | `data-swiping` and `--swipe-offset` on the panel while the pointer drags it; `data-swipe-dismissed` while it glides out after a closing release; `--swipe-progress` (0…1) on the scrim |
138
142
  | open: trapped | while open | focus trapped, page scroll locked, siblings of the portal `inert` (the toast region stays usable); a drawer opened from a Modal or another drawer stacks above it |
139
143
 
140
- Motion: the scrim fades, the panel slides from its side over `slow` and leaves over `base` (`overlayMotion`); under `prefers-reduced-motion` it unmounts at once.
144
+ Motion: the scrim fades, the panel slides from its side over `slow` and leaves over `base` (`overlayMotion`); under `prefers-reduced-motion` it unmounts at once. A swipe moves the panel with the pointer; on release it closes past 30% of its size or on a quick flick and glides the rest of the way out from where it was let go, otherwise it glides back — both over `base` on the `emphasized` (iOS sheet) curve. A pull the other way meets growing resistance and stops at 24; the panel's fill continues that far past the screen edge, so it never comes away from it. The scrim follows the panel: its fill thins in proportion to how far out the panel is, tracks the finger while dragging and glides with the panel on release. A bottom sheet drags from its handle or header; a side drawer by touch anywhere (not on text fields, sliders or sideways scrollers), by mouse from the header.
141
145
 
142
146
  ## Layout & spacing
143
147
  - Zone padding `--prime-drawer-padding`; hairlines between header, body and footer.
144
148
  - Full viewport height; only the body scrolls, with its own side padding.
149
+ - Bottom sheet: width by `size`, at least `--prime-sheet-min-width` (560); height by content up to `100dvh − --prime-sheet-top-gap`, radius `--prime-sheet-radius`, a `--prime-sheet-handle-area` strip with the handle bar, the bottom safe-area inset below the content.
145
150
  - Fields inside get the surface field fill.
146
151
 
147
152
  ## Accessibility
@@ -157,6 +162,7 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
157
162
  - Focus moves into the panel on open and returns to the opener on close, including after a scrim click.
158
163
  - The page behind is `inert`, scroll is locked; only the topmost layer reacts.
159
164
  - A drawer opened from a Modal stacks above it.
165
+ - A swipe toward the edge is an extra way out; the close button and Escape stay. The grab handle is `aria-hidden`.
160
166
 
161
167
  ### Labels
162
168
  <!-- Generated from api.ts by `bun run docs:build`. Edit api.ts, not this section. -->
@@ -171,7 +177,8 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
171
177
  | [overview.tsx](examples/overview.tsx) | Order details next to the list: a trigger, a header with a close button and a read-only body — `Drawer.Trigger`, `Drawer.Body`. |
172
178
  | [structure.tsx](examples/structure.tsx) | Optional parts: an icon tile in the header and one full-width action in the footer — `Drawer.Icon`, `Drawer.Footer`, `layout`. |
173
179
  | [sizes.tsx](examples/sizes.tsx) | Every panel width, 360 to 800 px; below 640 px of viewport the panel takes the full width — `size`. |
174
- | [placement.tsx](examples/placement.tsx) | The panel slides from the right for details and from the left for filters — `side`. |
180
+ | [placement.tsx](examples/placement.tsx) | The panel slides from the right for details, from the left for filters and up from the bottom as a sheet — `side`. |
181
+ | [date-sheet.tsx](examples/date-sheet.tsx) | A date picked in a bottom sheet: a grab handle on top, a swipe down or a pick closes it — `side`, `Drawer.Body`. |
175
182
  | [long-content.tsx](examples/long-content.tsx) | A long body scrolls on its own while the header and the footer stay in place — `Drawer.Body`. |
176
183
  | [dismiss.tsx](examples/dismiss.tsx) | An import closes only from its buttons, and not at all while it runs — `closeOnOutsideClick`, `closeOnEscape`. |
177
184
  | [controlled-open.tsx](examples/controlled-open.tsx) | The parent owns the state and opens the drawer from a link, without a trigger — `open`, `onOpenChange`. |
@@ -180,7 +187,8 @@ Motion: the scrim fades, the panel slides from its side over `slow` and leaves o
180
187
  ## Mistakes
181
188
  - A delete confirmation in a Drawer → use Modal.
182
189
  - Expecting Enter to submit via `Drawer.Confirm` → Drawer has no Confirm; use a `<form>` with a submit button.
183
- - `side="top"` / `"bottom"` → only `left` and `right`.
190
+ - `side="top"` → only `left`, `right` and `bottom`.
191
+ - Swipe as the only way to close → keep `Drawer.Header` with its close button (or `Drawer.Close`); a swipe is an extra gesture.
184
192
  - Padding on a wrapper inside Body → Body already pads and scrolls.
185
193
 
186
194
  ## Related
@@ -0,0 +1,41 @@
1
+ /** A date picked in a bottom sheet: a grab handle on top, a swipe down or a pick closes it — `side`, `Drawer.Body`. */
2
+ import { Button, Datepicker, Drawer, Icon } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ import styles from "./examples.module.css";
6
+
7
+ const FORMAT = new Intl.DateTimeFormat("ru-RU", { day: "numeric", month: "long", year: "numeric" });
8
+
9
+ export default function DrawerDateSheetExample() {
10
+ const [open, setOpen] = React.useState(false);
11
+ const [date, setDate] = React.useState<Date | null>(new Date(2026, 2, 8));
12
+
13
+ return (
14
+ <Drawer.Root open={open} onOpenChange={setOpen}>
15
+ <Drawer.Trigger>
16
+ <Button.Root variant="soft" tone="neutral">
17
+ <Button.Icon>
18
+ <Icon name="field.calendar" />
19
+ </Button.Icon>
20
+ {date ? `Отгрузка ${FORMAT.format(date)}` : "Дата отгрузки"}
21
+ </Button.Root>
22
+ </Drawer.Trigger>
23
+ <Drawer.Content side="bottom">
24
+ <Drawer.Header>
25
+ <Drawer.Title>Дата отгрузки</Drawer.Title>
26
+ </Drawer.Header>
27
+ <Drawer.Body>
28
+ <Datepicker.Panel
29
+ mode="single"
30
+ className={styles.calendar}
31
+ value={date}
32
+ onValueChange={(next) => {
33
+ setDate(next);
34
+ setOpen(false);
35
+ }}
36
+ />
37
+ </Drawer.Body>
38
+ </Drawer.Content>
39
+ </Drawer.Root>
40
+ );
41
+ }
@@ -19,3 +19,8 @@
19
19
  margin: 0;
20
20
  font-variant-numeric: tabular-nums;
21
21
  }
22
+
23
+ /* date-sheet.tsx: the calendar keeps its own width, centred in the sheet. */
24
+ .calendar {
25
+ align-self: center;
26
+ }
@@ -1,9 +1,10 @@
1
- /** The panel slides from the right for details and from the left for filters — `side`. */
1
+ /** The panel slides from the right for details, from the left for filters and up from the bottom as a sheet — `side`. */
2
2
  import { Button, Drawer, Typography } from "prime-ui-kit";
3
3
 
4
4
  const SIDES = [
5
5
  { side: "left", title: "Фильтры" },
6
6
  { side: "right", title: "Детали заказа" },
7
+ { side: "bottom", title: "Действия с заказом" },
7
8
  ] as const;
8
9
 
9
10
  export default function DrawerPlacementExample() {
@@ -22,7 +23,8 @@ export default function DrawerPlacementExample() {
22
23
  </Drawer.Header>
23
24
  <Drawer.Body>
24
25
  <Typography variant="body-m" tone="secondary">
25
- Скругление только со стороны страницы, подложка затемняет остальное.
26
+ Скругление только со стороны страницы, подложка затемняет остальное. Свайп к краю
27
+ закрывает панель.
26
28
  </Typography>
27
29
  </Drawer.Body>
28
30
  </Drawer.Content>
@@ -35,6 +35,7 @@ Dropdown.Root state and dismiss policy (no DOM)
35
35
  │ └── Dropdown.Item <button role="menuitem">
36
36
  │ ├── Dropdown.ItemIcon leading glyph (aria-hidden)
37
37
  │ └── Dropdown.ItemShortcut Kbd at the end
38
+ ├── Dropdown.CheckboxItem <button role="menuitemcheckbox">: label + check mark at the end while checked; stays open
38
39
  └── Dropdown.Item
39
40
  ```
40
41
 
@@ -62,7 +63,7 @@ No DOM: clones the single child, merges `ref` and `onClick` (toggles), sets `ari
62
63
  | `children` | `ReactElement` | — (required) | One element, usually a Button. |
63
64
 
64
65
  ### Dropdown.Content
65
- `ref` → `HTMLDivElement`. Portal + `role="menu"` on the floating surface (a ScrollContainer), named by the trigger; renders while open and during its exit animation. Focus moves to the first item; Tab closes the menu and returns focus to the trigger.
66
+ `ref` → `HTMLDivElement`. Portal + `role="menu"` on the floating surface (a ScrollContainer), named by the trigger; renders while open and during its exit animation. Focus moves to the first item; Tab closes the menu and returns focus to the trigger. Below 640px of viewport the menu is a bottom sheet: a scrim, a grab handle, swipe down to close (with `closeOnOutsideClick`), page scroll locked.
66
67
 
67
68
  | Prop | Type | Default | Description |
68
69
  |---|---|---|---|
@@ -82,6 +83,17 @@ No DOM: clones the single child, merges `ref` and `onClick` (toggles), sets `ari
82
83
  | `tone` | `"neutral" \| "danger"` | `"neutral"` | `danger`: destructive action — danger text, icon and hover fill. |
83
84
  | `children` | `ReactNode` | — | `Dropdown.ItemIcon`, the label, `Dropdown.ItemShortcut`. |
84
85
 
86
+ ### Dropdown.CheckboxItem
87
+ `ref` → `HTMLButtonElement`. A `<button role="menuitemcheckbox">` with `aria-checked`: the label (and an `ItemIcon`) on the left like any item, an accent check mark at the end while checked and nothing while not; a click, Enter or Space toggles it and the menu stays open. + native button props.
88
+
89
+ | Prop | Type | Default | Description |
90
+ |---|---|---|---|
91
+ | `checked` | `boolean` | — | Checked state (controlled). |
92
+ | `defaultChecked` | `boolean` | `false` | Initial state (uncontrolled). |
93
+ | `onCheckedChange` | `(checked: boolean) => void` | — | Called with the new state. |
94
+ | `disabled` | `boolean` | `false` | Muted, `aria-disabled`, skipped by the arrow keys, does not toggle. |
95
+ | `children` | `ReactNode` | — | The label (optionally after a `Dropdown.ItemIcon`). |
96
+
85
97
  ### Dropdown.ItemIcon · Dropdown.ItemShortcut
86
98
  `ref` → `HTMLSpanElement` / `HTMLElement` (the `<kbd>`). An `aria-hidden` `<span>` holding the leading glyph at the menu icon size (a kit `Icon` follows it) / a `Kbd` one tier below the menu, pushed to the end of the item — a hint, not a handler. + native props.
87
99
 
@@ -103,7 +115,7 @@ No DOM: clones the single child, merges `ref` and `onClick` (toggles), sets `ari
103
115
  `ref` → `HTMLDivElement`. A non-interactive row at the top (who is signed in, the plan): an avatar, Title + Description stacked in one column, a trailing badge or button — in the written order / the medium heading line / the muted line under it; both truncate. + native `<div>` props.
104
116
 
105
117
  ## Variants
106
- The panel is the shared floating surface: `bg-raised`, `--prime-panel-radius` (12), padding 4, `shadow-overlay`; rows have radius 8 and the tier item height.
118
+ The panel is the shared floating surface: the floating layer of the ladder (`data-depth="floating"`), `--prime-panel-radius` (12), padding 4, `shadow-overlay`; rows have radius 8 and the tier item height.
107
119
 
108
120
  ### size (Content)
109
121
  | Value | Looks like | Use when | Default |
@@ -145,9 +157,10 @@ The panel is the shared floating surface: `bg-raised`, `--prime-panel-radius` (1
145
157
  | open / closed | `open` / `defaultOpen` / `onOpenChange` | `data-state` on the menu (closed while the exit animation plays) and on the trigger, `aria-expanded` on the trigger |
146
158
  | side | `side` and the room next to the trigger | `data-side` (resolved) |
147
159
  | size | `size` on Content | `data-size` |
148
- | item hover / focus | pointer, arrow keys | subtle fill (danger-soft for `danger`), no movement |
160
+ | item hover / focus | a hovering pointer, arrow keys | subtle fill (danger-soft for `danger`), no movement; a tap leaves no hover fill |
149
161
  | item disabled | `disabled` | `aria-disabled="true"`, `data-disabled="true"` |
150
162
  | item tone | `tone` | `data-tone` |
163
+ | sheet | viewport below 640px | a scrim and a full-width bottom sheet with an `aria-hidden` grab handle around the menu; the sheet carries `data-state`, the menu keeps its role and ref; page scroll locked; a swipe down from the handle closes it (with `closeOnOutsideClick`) |
151
164
 
152
165
  ## Layout & spacing
153
166
  - Width is `max-content` between `--prime-panel-min-width` and twice that; items never wrap.
@@ -160,14 +173,14 @@ The panel is the shared floating surface: `bg-raised`, `--prime-panel-radius` (1
160
173
  ### Keyboard
161
174
  | Key | Action |
162
175
  |---|---|
163
- | `Enter` · `Space` | On the trigger opens the menu (focus on the first item); on an item runs it and returns focus to the trigger. |
176
+ | `Enter` · `Space` | On the trigger opens the menu (focus on the first item); on an item runs it and returns focus to the trigger; on a checkbox item toggles it and the menu stays open. |
164
177
  | `ArrowDown` · `ArrowUp` | Move focus to the next / previous enabled item, wrapping. |
165
178
  | `Home` · `End` | First / last enabled item. |
166
179
  | `Escape` | Closes the menu (`closeOnEscape`); focus returns to the trigger. |
167
180
  | `Tab` · `Shift+Tab` | Closes the menu; focus returns to the trigger (Tab then goes on to the next stop). |
168
181
 
169
182
  ### ARIA
170
- - The menu is `role="menu"` named by the trigger; items are `role="menuitem"`, disabled ones `aria-disabled`.
183
+ - The menu is `role="menu"` named by the trigger; items are `role="menuitem"`, checkbox items `role="menuitemcheckbox"` with `aria-checked`, disabled ones `aria-disabled`.
171
184
  - The trigger gets `aria-haspopup="menu"`, `aria-expanded` and `aria-controls`.
172
185
  - `Dropdown.Group` is `role="group"` named by its `label`.
173
186
  - `Dropdown.ItemIcon` is hidden from screen readers; a key hint is text only — the app handles the shortcut.
@@ -187,6 +200,7 @@ No `labels`.
187
200
  | [placement.tsx](examples/placement.tsx) | Every side and alignment relative to the trigger; near the viewport edge the menu flips and shifts — `side`, `align`. |
188
201
  | [states.tsx](examples/states.tsx) | A regular, a disabled and a destructive item; arrow keys skip the disabled one — `disabled`, `tone`. |
189
202
  | [match-trigger-width.tsx](examples/match-trigger-width.tsx) | Under a full-width button the menu is at least as wide as the trigger — `matchTriggerWidth`. |
203
+ | [checkbox-items.tsx](examples/checkbox-items.tsx) | A column chooser: toggles in the menu keep it open, the key column cannot be hidden — `Dropdown.CheckboxItem`, `checked`, `onCheckedChange`. |
190
204
  | [long-list.tsx](examples/long-list.tsx) | More projects than fit: the grouped list scrolls inside the panel, capped by the room next to the trigger — `Dropdown.Group`. |
191
205
  | [dismiss.tsx](examples/dismiss.tsx) | An onboarding step keeps the menu open on an outside press and on Escape until an item is picked — `closeOnOutsideClick`, `closeOnEscape`. |
192
206
  | [controlled-open.tsx](examples/controlled-open.tsx) | The parent owns the open state and opens the menu from another button; the picked step updates the trigger — `open`, `onOpenChange`. |
@@ -199,5 +213,5 @@ No `labels`.
199
213
  - A destructive item without `tone="danger"`, or in the middle of the list → mark it and put it last.
200
214
 
201
215
  ## Related
202
- - **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (the menu), [Kbd](../kbd/COMPONENT.md) (`ItemShortcut`), [Divider](../divider/COMPONENT.md) (`Separator`)
216
+ - **Built from:** [ScrollContainer](../scroll-container/COMPONENT.md) (the menu), [Kbd](../kbd/COMPONENT.md) (`ItemShortcut`), [Divider](../divider/COMPONENT.md) (`Separator`), Icon (`action.check` of `CheckboxItem`)
203
217
  - **See also:** [Select](../select/COMPONENT.md), [CommandMenu](../command-menu/COMPONENT.md), [Popover](../popover/COMPONENT.md), [Button](../button/COMPONENT.md)
@@ -0,0 +1,48 @@
1
+ /** A column chooser: toggles in the menu keep it open, the key column cannot be hidden — `Dropdown.CheckboxItem`, `checked`, `onCheckedChange`. */
2
+ import { Button, Dropdown, Icon } from "prime-ui-kit";
3
+ import * as React from "react";
4
+
5
+ const COLUMNS = [
6
+ { id: "number", label: "Номер", locked: true },
7
+ { id: "client", label: "Клиент", locked: false },
8
+ { id: "status", label: "Статус", locked: false },
9
+ { id: "amount", label: "Сумма", locked: false },
10
+ { id: "due", label: "Срок оплаты", locked: false },
11
+ ];
12
+
13
+ export default function DropdownCheckboxItemsExample() {
14
+ const [hidden, setHidden] = React.useState<string[]>(["due"]);
15
+ const shown = COLUMNS.length - hidden.length;
16
+
17
+ const toggle = (id: string, visible: boolean) =>
18
+ setHidden((list) => (visible ? list.filter((item) => item !== id) : [...list, id]));
19
+
20
+ return (
21
+ <Dropdown.Root>
22
+ <Dropdown.Trigger>
23
+ <Button.Root variant="soft" tone="neutral">
24
+ <Button.Icon>
25
+ <Icon name="action.settings" />
26
+ </Button.Icon>
27
+ Колонки: {shown} из {COLUMNS.length}
28
+ </Button.Root>
29
+ </Dropdown.Trigger>
30
+ <Dropdown.Content>
31
+ <Dropdown.Group label="Показывать">
32
+ {COLUMNS.map((column) => (
33
+ <Dropdown.CheckboxItem
34
+ key={column.id}
35
+ checked={!hidden.includes(column.id)}
36
+ disabled={column.locked}
37
+ onCheckedChange={(visible) => toggle(column.id, visible)}
38
+ >
39
+ {column.label}
40
+ </Dropdown.CheckboxItem>
41
+ ))}
42
+ </Dropdown.Group>
43
+ <Dropdown.Separator />
44
+ <Dropdown.Item onSelect={() => setHidden([])}>Показать все</Dropdown.Item>
45
+ </Dropdown.Content>
46
+ </Dropdown.Root>
47
+ );
48
+ }
@@ -18,5 +18,5 @@
18
18
  padding-block: var(--prime-space-1);
19
19
  padding-inline: var(--prime-space-4) var(--prime-space-1);
20
20
  border-radius: var(--prime-radius-l);
21
- background: var(--prime-color-card-bg);
21
+ background: var(--prime-color-layer-nested);
22
22
  }
@@ -1,6 +1,6 @@
1
1
  # EmptyPage
2
2
 
3
- **Category:** feedback
3
+ **Category:** status
4
4
  **Kind:** composite
5
5
 
6
6
  > Empty state of a page, a block or a menu: icon, title, explanation and an action.
@@ -27,7 +27,7 @@
27
27
  width: min(100%, var(--prime-panel-min-width));
28
28
  padding: var(--prime-panel-padding);
29
29
  border-radius: var(--prime-panel-radius);
30
- background: var(--prime-color-bg-raised);
30
+ background: var(--prime-color-layer-floating-bg);
31
31
  box-shadow: var(--prime-shadow-overlay);
32
32
  }
33
33
 
@@ -74,7 +74,7 @@ ExampleFrame frame: toolbar + preview stage or code pane
74
74
  | `light` | stage and code pane in the light theme | default | yes |
75
75
  | `dark` | stage and code pane in the dark theme (`data-theme="dark"`), switched without color transitions | checking dark mode | |
76
76
 
77
- Frame look: `bg-sunken` chrome with a `border-subtle` hairline and `radius-xl`; the stage and code pane are `bg-canvas` wells with `radius-l` (16 − 4 padding = 12). The stage resets the card context (`--prime-color-card-bg: bg-surface`).
77
+ Frame look: the chrome is a layer of the surface ladder (`data-depth`, one above its host) with a `border-subtle` hairline and `radius-xl`; the stage and code pane are wells with `radius-l` (16 − 4 padding = 12). Both wells carry `data-theme`, so each is a page of its own: layer 0 of its scheme, and surfaces in the example count from there.
78
78
 
79
79
  ## States
80
80
  | State | Driven by | DOM |
@@ -57,6 +57,8 @@ The trailing side has a fixed CSS `order`, independent of JSX order: value · en
57
57
  | `counter` | `ReactNode` | — | Right side of the support row, usually `<Input.Counter />`. |
58
58
  | `reserveSupportRow` | `boolean` | `false` | Always render the support row (min height = hint line height), so an appearing error does not shift the layout. |
59
59
  | `id` | `string` | — | Id of the `<input>` (auto-generated when omitted); hint id is `<id>-hint`, error id is `<id>-error`. |
60
+ | `strength` | `boolean` | `false` | A password strength meter: a stepped `ProgressBar` (4 steps) under the field and the level word at the end of the support row — weak, easy, medium, hard (`labels.strengthWeak` … `strengthHard`), toned danger → warning → accent → success. Follows the value of `Input.Field`, controlled or not. For new-password fields. |
61
+ | `getStrength` | `(value: string) => 0 \| 1 \| 2 \| 3 \| 4` | `getPasswordStrength` | Replaces the kit's estimate: every trait adds a point on its own, in any order — lowercase, uppercase, digits, symbols, 10+ and 14+ characters; 0–2 points weak, 3 easy, 4 medium, 5+ hard, and under 8 characters at most easy. Return 0 for empty, 1 weak … 4 hard. A hint for the person typing, not a security check. |
60
62
  | `labels` | `Partial<InputLabels>` | — | Built-in strings, see Labels. |
61
63
  | `children` | `ReactNode` | — (required) | Usually `Input.Wrapper`. |
62
64
  | `…rest` | `Omit<HTMLAttributes<HTMLDivElement>, "id" \| "children" \| "defaultValue" \| "defaultChecked" \| "onChange">` | — | `className`, `data-*` and the other attributes of the field frame `<div>` (field-root rule: `className`, `ref` and the rest → frame, `id` → control). |
@@ -168,7 +170,8 @@ Input has no `variant` or `tone`: there is one field treatment (fill, no visible
168
170
  | disabled | native `disabled` on `Input.Field` | disabled fill, disabled text and icons, `cursor: not-allowed`, clear button disabled look |
169
171
  | read-only | native `readOnly` on `Input.Field` | no hover, default cursor |
170
172
  | reserved support row | `reserveSupportRow` | `data-reserve="true"` on the support row |
171
- | counter over limit | `current > max` | `data-invalid="true"` on `Input.Counter` |
173
+ | counter over limit | `current > max` | `data-invalid="true"` on `Input.Counter`; the current count rolls its digits to each new value |
174
+ | strength | `strength` + the value of `Input.Field` | a stepped `ProgressBar` (4 cells, `aria-label` = `labels.strength`) under the field fills one cell after another, weak · easy · medium · hard, toned danger → warning → accent → success; the level word at the end of the support row flows in letter by letter and is announced (`aria-live`); the support row is reserved |
172
175
 
173
176
  Size: `data-size` on root and wrapper. Slots: `data-side="start" | "end"`.
174
177
 
@@ -204,6 +207,11 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
204
207
  | `optional` | `"необязательно"` | Marker after the label when `optional`. |
205
208
  | `clear` | `"Очистить"` | Accessible name of `Input.ClearButton`. |
206
209
  | `counter` | `"{current} из {max} символов"` | Screen-reader text of `Input.Counter`; `{current}` and `{max}` are replaced. |
210
+ | `strength` | `"Надёжность пароля"` | Accessible name of the `strength` meter and the spoken prefix of the level word. |
211
+ | `strengthWeak` | `"Слабый"` | Level word for strength 1. |
212
+ | `strengthEasy` | `"Лёгкий"` | Level word for strength 2. |
213
+ | `strengthMedium` | `"Средний"` | Level word for strength 3. |
214
+ | `strengthHard` | `"Сложный"` | Level word for strength 4. |
207
215
 
208
216
  ## Examples
209
217
  | Example | Shows |
@@ -215,6 +223,7 @@ Controlled: `value` + `onChange` (or `onValueChange`) on `Input.Field`. Uncontro
215
223
  | [with-icon.tsx](examples/with-icon.tsx) | A decorative icon at either end of the value — `Input.Icon`, `side`. |
216
224
  | [affixes.tsx](examples/affixes.tsx) | A fixed prefix and suffix flush with the edges and a unit next to the value — `Input.Affix`, `Input.InlineAffix`. |
217
225
  | [without-focus-ring.tsx](examples/without-focus-ring.tsx) | A single search field where the caret and the lighter fill show focus — `focusRing`. |
226
+ | [password-strength.tsx](examples/password-strength.tsx) | A new password with a meter that fills step by step and names the level beside the hint — `strength`. |
218
227
  | [controlled.tsx](examples/controlled.tsx) | The parent owns the value: a clear button and a character counter follow it — `value`, `onValueChange`, `Input.ClearButton`, `Input.Counter`. |
219
228
  | [in-form.tsx](examples/in-form.tsx) | Company details form: required fields checked on submit, neighbours keep their bottoms aligned — `required`, `error`, `reserveSupportRow`. |
220
229
 
@@ -0,0 +1,12 @@
1
+ /** A new password with a meter that fills step by step and names the level beside the hint — `strength`. */
2
+ import { Input } from "prime-ui-kit";
3
+
4
+ export default function InputPasswordStrengthExample() {
5
+ return (
6
+ <Input.Root label="Новый пароль" hint="Не короче 8 символов" strength>
7
+ <Input.Wrapper>
8
+ <Input.Field type="password" autoComplete="new-password" defaultValue="Graphite2026" />
9
+ </Input.Wrapper>
10
+ </Input.Root>
11
+ );
12
+ }