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.
- package/README.md +30 -12
- package/dist/chunks/{chunk-NHOWNIAR.js → chunk-6JTZOA5I.js} +1472 -671
- package/dist/chunks/{chunk-DB2Y3XXA.js → chunk-OAE4IQX2.js} +2845 -1732
- package/dist/chunks/{chunk-KCQFOORX.js → chunk-U5SR2LZR.js} +17 -4
- package/dist/color-picker.js +2 -2
- package/dist/components/button/Button.d.ts +19 -2
- package/dist/components/card/Card.d.ts +1 -1
- package/dist/components/code-block/CodeBlock.d.ts +1 -1
- package/dist/components/color-picker/ColorPicker.d.ts +1 -1
- package/dist/components/data-table/DataTable.d.ts +6 -1
- package/dist/components/data-table/Row.d.ts +3 -1
- package/dist/components/data-table/rows.d.ts +4 -0
- package/dist/components/data-table/types.d.ts +5 -0
- package/dist/components/digit-input/DigitInput.d.ts +6 -1
- package/dist/components/dnd/Dnd.d.ts +1 -1
- package/dist/components/dnd/dragSession.d.ts +9 -0
- package/dist/components/dnd/useDropTarget.d.ts +5 -0
- package/dist/components/dnd/useFlipList.d.ts +3 -3
- package/dist/components/drawer/Drawer.d.ts +8 -5
- package/dist/components/dropdown/Dropdown.d.ts +17 -0
- package/dist/components/index.d.ts +9 -2
- package/dist/components/input/Input.d.ts +16 -1
- package/dist/components/input/passwordStrength.d.ts +9 -0
- package/dist/components/kanban/Kanban.d.ts +129 -0
- package/dist/components/modal/Modal.d.ts +5 -2
- package/dist/components/page-toolbar/PageToolbar.d.ts +53 -0
- package/dist/components/progress-bar/ProgressBar.d.ts +16 -1
- package/dist/components/segmented-control/SegmentedControl.d.ts +5 -2
- package/dist/components/sparkline/Sparkline.d.ts +26 -0
- package/dist/components/tabs/Tabs.d.ts +5 -2
- package/dist/hooks/useEdgeOverflow.d.ts +16 -0
- package/dist/hooks/useMediaQuery.d.ts +7 -0
- package/dist/hooks/useSwipeDismiss.d.ts +34 -0
- package/dist/icons/registry.d.ts +2 -0
- package/dist/index.css +2977 -1306
- package/dist/index.d.ts +1 -0
- package/dist/index.js +17 -3
- package/dist/internal/CheckMark.d.ts +12 -0
- package/dist/internal/MorphText.d.ts +12 -0
- package/dist/internal/RollingNumber.d.ts +14 -0
- package/dist/internal/overlay/FloatingPanel.d.ts +3 -1
- package/dist/internal/overlay/useFloatingLayer.d.ts +11 -1
- package/dist/internal/surfaceDepth.d.ts +25 -0
- package/dist/internal/swatch.d.ts +4 -1
- package/dist/internal/touchTarget.d.ts +4 -0
- package/dist/layout/app-header/AppHeader.d.ts +110 -0
- package/dist/layout/app-shell/AppShell.d.ts +16 -11
- package/dist/layout/bottom-nav/BottomNav.d.ts +80 -0
- package/dist/layout/index.d.ts +5 -1
- package/dist/motion/celebrate.d.ts +18 -0
- package/dist/tokens/layers.d.ts +37 -0
- package/dist/tokens/naming.d.ts +1 -1
- package/dist/tokens/primitives.d.ts +4 -0
- package/dist/tokens/semantic.d.ts +122 -56
- package/dist/tokens/themes/dark.d.ts +10 -14
- package/package.json +3 -2
- package/src/components/accordion/COMPONENT.md +5 -5
- package/src/components/avatar/COMPONENT.md +1 -1
- package/src/components/badge/COMPONENT.md +2 -2
- package/src/components/banner/COMPONENT.md +1 -1
- package/src/components/button/COMPONENT.md +26 -5
- package/src/components/button/examples/download.tsx +45 -0
- package/src/components/button/examples/hold-to-confirm.tsx +23 -0
- package/src/components/button/examples/label-morph.tsx +33 -0
- package/src/components/card/COMPONENT.md +4 -2
- package/src/components/card/examples/narrow.tsx +2 -2
- package/src/components/card/examples/panel-chart.tsx +1 -1
- package/src/components/card/examples/variants.tsx +1 -1
- package/src/components/checkbox/examples/examples.module.css +5 -3
- package/src/components/code-block/COMPONENT.md +6 -6
- package/src/components/code-block/examples/overview.tsx +1 -1
- package/src/components/code-block/examples/variants.tsx +1 -1
- package/src/components/color-picker/COMPONENT.md +1 -1
- package/src/components/color-swatches/COMPONENT.md +5 -5
- package/src/components/command-menu/COMPONENT.md +1 -1
- package/src/components/crossfade/COMPONENT.md +1 -1
- package/src/components/crossfade/examples/overview.tsx +3 -3
- package/src/components/data-table/COMPONENT.md +24 -10
- package/src/components/data-table/examples/columns-visibility.tsx +167 -0
- package/src/components/digit-input/COMPONENT.md +15 -13
- package/src/components/digit-input/examples/examples.module.css +8 -0
- package/src/components/digit-input/examples/in-form.tsx +2 -5
- package/src/components/digit-input/examples/on-complete.tsx +7 -6
- package/src/components/digit-input/examples/sizes.tsx +1 -1
- package/src/components/divider/COMPONENT.md +1 -1
- package/src/components/dnd/COMPONENT.md +3 -3
- package/src/components/dnd/examples/examples.module.css +4 -4
- package/src/components/drawer/COMPONENT.md +17 -9
- package/src/components/drawer/examples/date-sheet.tsx +41 -0
- package/src/components/drawer/examples/examples.module.css +5 -0
- package/src/components/drawer/examples/placement.tsx +4 -2
- package/src/components/dropdown/COMPONENT.md +20 -6
- package/src/components/dropdown/examples/checkbox-items.tsx +48 -0
- package/src/components/dropdown/examples/examples.module.css +1 -1
- package/src/components/empty-page/COMPONENT.md +1 -1
- package/src/components/empty-page/examples/examples.module.css +1 -1
- package/src/components/example-frame/COMPONENT.md +1 -1
- package/src/components/input/COMPONENT.md +10 -1
- package/src/components/input/examples/password-strength.tsx +12 -0
- package/src/components/kanban/COMPONENT.md +181 -0
- package/src/components/kanban/examples/controlled.tsx +78 -0
- package/src/components/kanban/examples/examples.module.css +31 -0
- package/src/components/kanban/examples/limits.tsx +58 -0
- package/src/components/kanban/examples/narrow.tsx +76 -0
- package/src/components/kanban/examples/overview.tsx +130 -0
- package/src/components/kanban/examples/states.tsx +75 -0
- package/src/components/kanban/examples/structure.tsx +76 -0
- package/src/components/login-form/COMPONENT.md +5 -5
- package/src/components/login-form/examples/register.tsx +2 -2
- package/src/components/login-form/examples/reset-password.tsx +2 -2
- package/src/components/modal/COMPONENT.md +11 -8
- package/src/components/modal/examples/examples.module.css +2 -2
- package/src/components/notification/COMPONENT.md +2 -2
- package/src/components/page-content/COMPONENT.md +1 -1
- package/src/components/page-content/examples/examples.module.css +1 -1
- package/src/components/page-toolbar/COMPONENT.md +142 -0
- package/src/components/page-toolbar/examples/examples.module.css +16 -0
- package/src/components/page-toolbar/examples/narrow.tsx +103 -0
- package/src/components/page-toolbar/examples/overview.tsx +113 -0
- package/src/components/page-toolbar/examples/save-changes.tsx +40 -0
- package/src/components/page-toolbar/examples/without-search.tsx +60 -0
- package/src/components/popover/COMPONENT.md +5 -4
- package/src/components/progress-bar/COMPONENT.md +6 -2
- package/src/components/progress-bar/examples/examples.module.css +7 -0
- package/src/components/progress-bar/examples/milestone.tsx +43 -0
- package/src/components/progress-bar/examples/steps.tsx +35 -0
- package/src/components/progress-circle/COMPONENT.md +1 -1
- package/src/components/scroll-container/COMPONENT.md +1 -1
- package/src/components/segmented-control/COMPONENT.md +8 -8
- package/src/components/segmented-control/examples/full-width.tsx +1 -1
- package/src/components/select/COMPONENT.md +2 -1
- package/src/components/skeleton/COMPONENT.md +1 -1
- package/src/components/skeleton/examples/with-crossfade.tsx +3 -3
- package/src/components/smart-filter/COMPONENT.md +2 -0
- package/src/components/sparkline/COMPONENT.md +98 -0
- package/src/components/sparkline/examples/examples.module.css +13 -0
- package/src/components/sparkline/examples/overview.tsx +27 -0
- package/src/components/sparkline/examples/period.tsx +46 -0
- package/src/components/spinner/COMPONENT.md +1 -1
- package/src/components/tabs/COMPONENT.md +24 -22
- package/src/components/tabs/examples/closable.tsx +1 -1
- package/src/components/tabs/examples/examples.module.css +14 -0
- package/src/components/tabs/examples/orientation.tsx +87 -33
- package/src/components/tooltip/COMPONENT.md +8 -6
- package/src/components/tooltip/examples/examples.module.css +2 -2
- package/src/layout/app-header/COMPONENT.md +178 -0
- package/src/layout/app-header/examples/breadcrumbs.tsx +36 -0
- package/src/layout/app-header/examples/examples.module.css +47 -0
- package/src/layout/app-header/examples/in-app-shell.tsx +70 -0
- package/src/layout/app-header/examples/narrow.tsx +29 -0
- package/src/layout/app-header/examples/overview.tsx +36 -0
- package/src/layout/app-shell/COMPONENT.md +35 -29
- package/src/layout/app-shell/examples/examples.module.css +17 -2
- package/src/layout/app-shell/examples/footer.tsx +59 -0
- package/src/layout/app-shell/examples/overview.tsx +19 -8
- package/src/layout/bottom-nav/COMPONENT.md +153 -0
- package/src/layout/bottom-nav/examples/examples.module.css +39 -0
- package/src/layout/bottom-nav/examples/floating.tsx +89 -0
- package/src/layout/bottom-nav/examples/icon-only.tsx +41 -0
- package/src/layout/bottom-nav/examples/in-app-shell.tsx +64 -0
- package/src/layout/bottom-nav/examples/overview.tsx +44 -0
- package/src/layout/bottom-nav/examples/router.tsx +31 -0
- package/src/layout/bottom-nav/examples/structure.tsx +47 -0
- package/src/layout/sidebar/COMPONENT.md +14 -13
- package/src/layout/sidebar/examples/examples.module.css +5 -5
- package/src/styles/globals.css +125 -2
- package/src/styles/theme-dark.css +39 -13
- package/src/styles/theme-light.css +84 -16
- package/src/styles/tokens.css +2 -0
|
@@ -24,7 +24,7 @@ export default function CardVariantsExample() {
|
|
|
24
24
|
|
|
25
25
|
<Card.Root variant="stat-trend">
|
|
26
26
|
<Card.Label>Выручка за месяц</Card.Label>
|
|
27
|
-
<Card.Value
|
|
27
|
+
<Card.Value>4,2 млн ₽</Card.Value>
|
|
28
28
|
<Card.Delta tone="success">+18% к сентябрю</Card.Delta>
|
|
29
29
|
</Card.Root>
|
|
30
30
|
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
width: min(100%, var(--prime-panel-min-width));
|
|
42
42
|
padding: var(--prime-panel-padding);
|
|
43
43
|
border-radius: var(--prime-panel-radius);
|
|
44
|
-
background: var(--prime-color-bg
|
|
44
|
+
background: var(--prime-color-layer-floating-bg);
|
|
45
45
|
box-shadow: var(--prime-shadow-overlay);
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -57,8 +57,10 @@
|
|
|
57
57
|
var(--prime-motion-easing-standard);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
@media (hover: hover) {
|
|
61
|
+
.option:hover {
|
|
62
|
+
background: var(--prime-color-fill-subtle);
|
|
63
|
+
}
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
.option:focus-visible {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Category:** data-display
|
|
4
4
|
**Kind:** primitive
|
|
5
5
|
|
|
6
|
-
> A static TypeScript / TSX snippet with syntax highlighting, on a
|
|
6
|
+
> A static TypeScript / TSX snippet with syntax highlighting, on a filled panel or bare inside a host.
|
|
7
7
|
|
|
8
8
|
## When to use
|
|
9
9
|
- Code samples in docs, settings («API key usage») and onboarding.
|
|
@@ -37,7 +37,7 @@ CodeBlock <pre>; variant, colorScheme (data-theme)
|
|
|
37
37
|
| Prop | Type | Default | Description |
|
|
38
38
|
|---|---|---|---|
|
|
39
39
|
| `code` | `string` | — (required) | TS / TSX source; trailing whitespace is trimmed, then highlighted. |
|
|
40
|
-
| `variant` | `"soft" \| "ghost"` | `"soft"` | `soft` —
|
|
40
|
+
| `variant` | `"soft" \| "ghost"` | `"soft"` | `soft` — a filled panel (one ladder step off its host) with padding and the `code` text role; `ghost` — bare `pre` that inherits type and background from its host. |
|
|
41
41
|
| `colorScheme` | `"light" \| "dark"` | — | Fixes the theme for this block only (`data-theme`). Omit to follow the page theme. |
|
|
42
42
|
| `tabIndex` | `number` | — | Default `0` for `soft` (a scrolling block stays reachable from the keyboard), none for `ghost`; pass `-1` when it never overflows. |
|
|
43
43
|
| `…rest` | `Omit<HTMLAttributes<HTMLPreElement>, "children" \| "dangerouslySetInnerHTML">` | — | `className`, `aria-label` and the other `<pre>` attributes. |
|
|
@@ -54,8 +54,8 @@ CodeBlock <pre>; variant, colorScheme (data-theme)
|
|
|
54
54
|
| Value | Looks like | Use when | Default |
|
|
55
55
|
|---|---|---|---|
|
|
56
56
|
| — (omitted) | token colors and fill follow the page theme | normal case | yes |
|
|
57
|
-
| `light` | light palette; the soft fill becomes that scheme's `bg
|
|
58
|
-
| `dark` | dark palette; the soft fill becomes that scheme's `bg
|
|
57
|
+
| `light` | light palette; the soft fill becomes that scheme's floating layer (`layer-floating-bg`) | a light sample inside a dark page | |
|
|
58
|
+
| `dark` | dark palette; the soft fill becomes that scheme's floating layer (`layer-floating-bg`) | a terminal-like sample inside a light page | |
|
|
59
59
|
|
|
60
60
|
Syntax tokens: keywords purple (weight 500), strings teal, numbers orange, JSX tags blue, comments muted italic — palette text tokens, readable in both themes.
|
|
61
61
|
|
|
@@ -92,8 +92,8 @@ No `labels`.
|
|
|
92
92
|
## Examples
|
|
93
93
|
| Example | Shows |
|
|
94
94
|
|---|---|
|
|
95
|
-
| [overview.tsx](examples/overview.tsx) | An API response sample on a
|
|
96
|
-
| [variants.tsx](examples/variants.tsx) | A
|
|
95
|
+
| [overview.tsx](examples/overview.tsx) | An API response sample on a filled panel, named for screen readers — `code`, `aria-label`. |
|
|
96
|
+
| [variants.tsx](examples/variants.tsx) | A filled panel and a bare block that takes type and background from its host — `variant`. |
|
|
97
97
|
| [color-scheme.tsx](examples/color-scheme.tsx) | A block fixed to one scheme looks the same in both page themes — `colorScheme`. |
|
|
98
98
|
| [narrow.tsx](examples/narrow.tsx) | In a narrow column a long line scrolls inside the block and never wraps; Tab, then arrow keys. |
|
|
99
99
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** An API response sample on a
|
|
1
|
+
/** An API response sample on a filled panel, named for screen readers — `code`, `aria-label`. */
|
|
2
2
|
import { CodeBlock } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
import styles from "./examples.module.css";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** A
|
|
1
|
+
/** A filled panel and a bare block that takes type and background from its host — `variant`. */
|
|
2
2
|
import { CodeBlock, Typography } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
const SAMPLE = `const total = formatPrice(14990);`;
|
|
@@ -211,7 +211,7 @@ No `variant` / `tone` / `color`.
|
|
|
211
211
|
| Value | Looks like | Use when | Default |
|
|
212
212
|
|---|---|---|---|
|
|
213
213
|
| `none` | parts stacked with a 12px gap, no own background | inside a Popover or a Card | yes |
|
|
214
|
-
| `raised` | `
|
|
214
|
+
| `raised` | the floating layer of the ladder (`data-depth="floating"`), panel radius and padding, `shadow-overlay`; fields take that layer's fill | a standalone panel on the page | |
|
|
215
215
|
|
|
216
216
|
### defaultFormat (ColorPicker.Root)
|
|
217
217
|
| Value | Looks like | Use when | Default |
|
|
@@ -48,7 +48,7 @@ ColorSwatches field frame: label → swatches → hint | error
|
|
|
48
48
|
| `optional` | `boolean` | — | Muted marker right after the label text (`labels.optional`). |
|
|
49
49
|
| `hint` | `ReactNode` | — | Help text under the swatches. Hidden while `error` is shown. |
|
|
50
50
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
51
|
-
| `invalid` | `boolean` | — | Danger
|
|
51
|
+
| `invalid` | `boolean` | — | Danger inner edge on every swatch and `aria-invalid`. A non-empty `error` implies it. |
|
|
52
52
|
| `disabled` | `boolean` | `false` | Disables every swatch (grey, half transparent). |
|
|
53
53
|
| `allowEmpty` | `boolean` | `false` | Adds the «no color» checkerboard swatch after the presets (value `null`). |
|
|
54
54
|
| `name` | `string` | — | Form field name: a hidden input submits the selected color (empty string for no color). |
|
|
@@ -76,7 +76,7 @@ ColorSwatches field frame: label → swatches → hint | error
|
|
|
76
76
|
| Value | Looks like | Use when | Default |
|
|
77
77
|
|---|---|---|---|
|
|
78
78
|
| `allowEmpty` | an extra checkerboard swatch «Без цвета» at the end | the color is optional | `false` |
|
|
79
|
-
| `invalid` | the
|
|
79
|
+
| `invalid` | the inner edge of every swatch turns danger | the message is shown elsewhere; otherwise pass `error` | — |
|
|
80
80
|
| `disabled` | every swatch grey at half opacity | the choice is unavailable | `false` |
|
|
81
81
|
|
|
82
82
|
**Combinations**
|
|
@@ -87,17 +87,17 @@ ColorSwatches field frame: label → swatches → hint | error
|
|
|
87
87
|
## States
|
|
88
88
|
| State | Driven by | DOM |
|
|
89
89
|
|---|---|---|
|
|
90
|
-
| selected | `value` / `defaultValue` | `aria-checked="true"`, `data-state="checked"` on the radio;
|
|
90
|
+
| selected | `value` / `defaultValue` | `aria-checked="true"`, `data-state="checked"` on the radio; the check mark (light or dark by color contrast) scales in from half size (`base` · emphasized) and leaves faster (`fast` · exit); no ring — the check is the selection |
|
|
91
91
|
| hover | pointer | the swatch scales to 1.08 (fine pointers only) |
|
|
92
92
|
| focus-visible | keyboard | focus ring outside the swatch |
|
|
93
|
-
| invalid | `invalid` / `error` | `aria-invalid`, `data-invalid` on the group; the
|
|
93
|
+
| invalid | `invalid` / `error` | `aria-invalid`, `data-invalid` on the group; the inner edge of every swatch turns `danger-border` |
|
|
94
94
|
| disabled | `disabled` | native `disabled` on every radio, `aria-disabled` and `data-disabled` on the group |
|
|
95
95
|
|
|
96
96
|
Controlled with `value` + `onValueChange`; uncontrolled with `defaultValue`.
|
|
97
97
|
|
|
98
98
|
## Layout & spacing
|
|
99
99
|
- The swatches take the container width and wrap to new rows by themselves; no column count to set. In a content-sized container they stay in one row.
|
|
100
|
-
- The
|
|
100
|
+
- The keyboard focus ring sits outside the swatches; kit hosts (Modal / Drawer body, panels, cards) already keep `--prime-focus-space` padding, so the ring is never clipped. In your own `overflow` container, keep that padding.
|
|
101
101
|
- Label → swatches and swatches → hint use the tier `label-gap` / `hint-gap`; field → field 20.
|
|
102
102
|
|
|
103
103
|
## Accessibility
|
|
@@ -111,7 +111,7 @@ CommandMenu.Root Modal + top-aligned palette panel, query state
|
|
|
111
111
|
| `keys` | `ReactNode[]` | — (required) | FooterHint: the keys (text or icons). |
|
|
112
112
|
|
|
113
113
|
## Variants
|
|
114
|
-
One look:
|
|
114
|
+
One look: the floating layer of the ladder (it is a Modal), `--prime-modal-radius`, `shadow-modal`, `--prime-modal-width-l` wide, placed in the top part of the viewport so the search row never jumps while the list filters.
|
|
115
115
|
|
|
116
116
|
### size (Root)
|
|
117
117
|
| Value | Looks like | Use when | Default |
|
|
@@ -16,9 +16,9 @@ import styles from "./examples.module.css";
|
|
|
16
16
|
type Status = "loading" | "ready" | "empty" | "error";
|
|
17
17
|
|
|
18
18
|
const PAYMENTS = [
|
|
19
|
-
{ id: "p1", payer: "ООО «Гранит»", amount: "
|
|
20
|
-
{ id: "p2", payer: "ИП Орлов Д. С.", amount: "
|
|
21
|
-
{ id: "p3", payer: "АО «Альфа Медиа»", amount: "
|
|
19
|
+
{ id: "p1", payer: "ООО «Гранит»", amount: "94 750 ₽" },
|
|
20
|
+
{ id: "p2", payer: "ИП Орлов Д. С.", amount: "18 000 ₽" },
|
|
21
|
+
{ id: "p3", payer: "АО «Альфа Медиа»", amount: "380 000 ₽" },
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
export default function CrossfadeOverviewExample() {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
- Data that needs sorting, paging, bulk selection or a filter toolbar.
|
|
11
11
|
- Hierarchical rows (`getRowChildren`) or rows with a detail panel (`renderExpanded`).
|
|
12
12
|
- Wide numeric reports with a sticky head and first column.
|
|
13
|
+
- Wide records on narrow screens: let people choose the columns (`hiddenColumns`) instead of dropping them.
|
|
13
14
|
|
|
14
15
|
## When not to use
|
|
15
16
|
- One or two KPIs → use [Card](../card/COMPONENT.md).
|
|
@@ -28,7 +29,7 @@ Exported types: `DataTableColumn<Row>`, `DataTableProps<Row>`, `DataTableSortSta
|
|
|
28
29
|
```
|
|
29
30
|
DataTable card-fill block, radius 12, clips its content
|
|
30
31
|
├── toolbar optional panel above the table (search, filters, bulk actions)
|
|
31
|
-
├── scroll viewport ScrollContainer, both axes
|
|
32
|
+
├── scroll viewport ScrollContainer, both axes, in a frame that holds the edge shadows
|
|
32
33
|
│ └── table
|
|
33
34
|
│ ├── thead [select all] [toggle] head cells; sortable heads hold a button
|
|
34
35
|
│ └── tbody rows · sub-rows · detail rows · skeleton / empty / error row
|
|
@@ -46,6 +47,7 @@ Generic over `Row`; `ref` → `HTMLDivElement`. A card-fill block with an option
|
|
|
46
47
|
| Prop | Type | Default | Description |
|
|
47
48
|
|---|---|---|---|
|
|
48
49
|
| `columns` | `DataTableColumn<Row>[]` | — (required) | Column definitions, see `DataTableColumn<Row>`. |
|
|
50
|
+
| `hiddenColumns` | `string[]` | — | Ids of columns that are not rendered (head, body, skeleton and state rows); widths are measured again. A column with `hideable: false` stays. The list and the control that changes it (a column chooser in `toolbar`) belong to the consumer; sorting by a hidden column keeps the row order. |
|
|
49
51
|
| `rows` | `Row[]` | — (required) | Data; sorted in memory when a sort is set. |
|
|
50
52
|
| `getRowKey` | `(row: Row, index: number) => Key` | — | Stable row id (position by default); selection, expansion and the enter animation of new rows are keyed by it. Pass it whenever sorting, selection or expansion is used. |
|
|
51
53
|
| `getRowLabel` | `(row: Row) => string` | — | Human row name for the checkbox and toggle names (`{label}` in `labels`). |
|
|
@@ -78,8 +80,8 @@ Generic over `Row`; `ref` → `HTMLDivElement`. A card-fill block with an option
|
|
|
78
80
|
| `loadingMore` | `boolean` | `false` | A server batch is loading: footer status, `aria-busy`. |
|
|
79
81
|
| `onLoadMore` | `() => void \| Promise<void>` | — | Called at the end when every loaded row is shown and `hasMore` is set. |
|
|
80
82
|
| `scrollHeight` | `number \| string` | `360 with paging="infinite"` | Max height of the scroll viewport (number = px). |
|
|
81
|
-
| `stickyHeader` | `boolean` | `false` | The head sticks while the body scrolls. |
|
|
82
|
-
| `stickyFirstColumn` | `boolean` | `false` | The first column (with the selection and toggle columns) sticks while scrolling sideways. |
|
|
83
|
+
| `stickyHeader` | `boolean` | `false` | The head sticks while the body scrolls (with `scrollHeight`); on screens lower than 480px it scrolls with the rows. |
|
|
84
|
+
| `stickyFirstColumn` | `boolean` | `false` | The first visible column (with the selection and toggle columns) sticks while scrolling sideways; the start edge shadow falls from its end edge. |
|
|
83
85
|
| `showHeader` | `boolean` | `true` | Render `<thead>`. |
|
|
84
86
|
| `fullWidth` | `boolean` | `true` | The table spans its container; `false` sizes it by its content. |
|
|
85
87
|
| `rowDividers` | `boolean` | `true` | Hairlines between rows. |
|
|
@@ -108,6 +110,7 @@ One column of `columns`: data, not a part.
|
|
|
108
110
|
| `numeric` | `boolean` | — | Tabular figures, no wrapping, end alignment unless `align` is set. |
|
|
109
111
|
| `truncate` | `boolean` | — | One line with an ellipsis; width from `maxWidth` (or `width`); string values get a `title`. |
|
|
110
112
|
| `grow` | `boolean` | — | Takes the free width and wraps its text; the table then fills its container. With `minWidth` it never gets narrower: below that the table scrolls instead of cells overlapping. |
|
|
113
|
+
| `hideable` | `boolean` | `true` | The column can be hidden through `hiddenColumns`; `false` for the key column that names the row. |
|
|
111
114
|
| `width · minWidth · maxWidth` | `string` | — | CSS sizes of the column (`"14rem"`). |
|
|
112
115
|
|
|
113
116
|
## Variants
|
|
@@ -147,9 +150,10 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
|
|
|
147
150
|
| `rowDividers` | hairlines between rows | default; off together with `striped` | `true` |
|
|
148
151
|
| `columnDividers` | hairlines between content columns (none inside the lead columns) | default; off for 2–3 column key/value lists | `true` |
|
|
149
152
|
| `striped` | every second row one opaque step off the fill | long numeric grids | `false` |
|
|
150
|
-
| `highlightRowOnHover` | `fill-subtle` wash on the hovered row | interactive lists | `true` |
|
|
151
|
-
| `highlightColumnOnHover` | `fill-subtle` wash on the hovered column | comparison tables | `false` |
|
|
152
|
-
| `stickyHeader` · `stickyFirstColumn` | opaque sticky head / first column with a hairline edge | long / wide tables in a `scrollHeight` window | `false` |
|
|
153
|
+
| `highlightRowOnHover` | `fill-subtle-active` wash on the hovered row (stronger than a zebra step, so a hovered row never takes a zebra row's color) | interactive lists | `true` |
|
|
154
|
+
| `highlightColumnOnHover` | `fill-subtle-active` wash on the hovered column | comparison tables | `false` |
|
|
155
|
+
| `stickyHeader` · `stickyFirstColumn` | opaque sticky head / first column with a hairline edge; on screens lower than 480px the head scrolls with the rows | long / wide tables in a `scrollHeight` window | `false` |
|
|
156
|
+
| column `hideable` | `false`: the column stays even when listed in `hiddenColumns` | the key column that names the row | `true` |
|
|
153
157
|
| `showHeader` | head row shown | off only for self-explanatory key/value tables | `true` |
|
|
154
158
|
| `fullWidth` | spans the container; `false` = content width | `false` for short lookup tables in wide layouts | `true` |
|
|
155
159
|
| column `numeric` | tabular figures, end-aligned, no wrap | numbers, money | — |
|
|
@@ -165,16 +169,22 @@ Heads keep `start` whatever the cell alignment, with the sort icon at the end ed
|
|
|
165
169
|
| sorted | `sort` / `defaultSort` | `aria-sort` and `data-sorted` on the head; the sort icon: unsorted `text-disabled`, hover `text-muted`, sorted `text-secondary` — never accent |
|
|
166
170
|
| selected | `selected` / `defaultSelected` | `aria-selected` rows with `accent-soft` fill; polite `labels.selectedCount` |
|
|
167
171
|
| expanded | `expanded` / `defaultExpanded` | `data-expanded` on the parent row (one step darker); toggle `aria-expanded`, chevron turns 90° |
|
|
168
|
-
| new rows | rows
|
|
172
|
+
| new rows | rows new in `rows` (by `getRowKey`) | `data-animate`: top-level cells drop in from above (`enterMotion`) |
|
|
173
|
+
| sub-rows and detail panels | an expand / a collapse | both fold open and shut through `grid-template-rows` (0fr ↔ 1fr) with the fade on the same `base` curve; the row height and padding live inside the track, so nothing is left to jump. Sub-rows: `data-fold` on the row (`opening` · `open` · `closed`); a collapsed row's sub-rows stay mounted until the fold ends. Detail row: `data-state`; it unmounts after the close |
|
|
174
|
+
| overflowing sideways | the table is wider than its viewport | `data-overflow-start` (scrolled from the start) and `data-overflow-end` (more to the end) on the root: a soft shadow over that edge, from the end edge of the sticky first column for the start; fades in and out over `fast` |
|
|
175
|
+
| hidden columns | `hiddenColumns` | the listed columns (except `hideable: false`) are not rendered in the head, rows, skeleton or state rows; widths are measured again |
|
|
169
176
|
| state swap | the body moves between loading, rows, empty and error | the body fades in over `base` (opacity only, the same swap motion as [Crossfade](../crossfade/COMPONENT.md)); the head stays still; nothing moves on the first render |
|
|
170
177
|
|
|
171
|
-
Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data-sticky-header`, `data-sticky-first-column`, `data-table-width` (`fill` · `auto` · `grow`), `data-highlight-row`, `data-highlight-column`, `data-striped`, `data-loading`, `data-selectable`, `data-expandable`, `data-dragging` (while drag-selecting). Sorting and paging swap rows instantly; everything is still under `prefers-reduced-motion`.
|
|
178
|
+
Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data-sticky-header`, `data-sticky-first-column`, `data-table-width` (`fill` · `auto` · `grow`), `data-highlight-row`, `data-highlight-column`, `data-striped`, `data-loading`, `data-selectable`, `data-expandable`, `data-dragging` (while drag-selecting), `data-overflow-start` · `data-overflow-end` (edges that hide columns). Sorting and paging swap rows instantly; everything is still under `prefers-reduced-motion`.
|
|
172
179
|
|
|
173
180
|
## Layout & spacing
|
|
174
|
-
- The root fills its container on `--prime-color-
|
|
181
|
+
- The root is a layer of the surface ladder (`data-depth`, one above its host): it fills its container on `--prime-color-layer-current`, radius 12, and clips full-bleed head and rows; zebra rows and the sticky column are one ladder step off it, the head two. On a card or in a modal it is the next layer by itself — never wrap it in a Card.
|
|
175
182
|
- Toolbar padding `--prime-space-3` × cell padding, items `gap: --prime-space-2`; below 30rem of table width toolbar and footer stack.
|
|
176
183
|
- The footer has a hairline on top: range on the left, Pagination on the right. The range shows only with more than one page or with infinite scroll.
|
|
177
|
-
- Columns scroll sideways inside the table; pin the identifying column with `stickyFirstColumn`. Works from 320 px.
|
|
184
|
+
- Columns scroll sideways inside the table, never the page; pin the identifying column with `stickyFirstColumn`. Works from 320 px.
|
|
185
|
+
- While columns are hidden past an edge, that edge shows a `--prime-space-4` shadow (the theme scrim at 40%) over the content; it takes no space and follows RTL. With `stickyFirstColumn` the start shadow falls from the pinned column's end edge.
|
|
186
|
+
- Fewer columns, less scrolling: put a column chooser in `toolbar` (a Dropdown with a `Dropdown.CheckboxItem` per column, the key column `hideable: false` shown checked and disabled) and pass `hiddenColumns`. Never hide columns silently by width.
|
|
187
|
+
- The sticky head stops sticking on screens lower than 480px (landscape phones), so it never takes a large share of the height.
|
|
178
188
|
- Cell content: text, two-line text (title + `caption`), an Avatar with text, a Badge, or a control one tier down (`s` in an `m` table). Toolbar controls use `s` in an `m` table.
|
|
179
189
|
- Column widths: `width` / `minWidth` / `maxWidth` strings; never size cells with custom CSS.
|
|
180
190
|
- Sub-rows indent by `--dt-indent` per level (avatar of the tier + gap); override it on the root for other leading content.
|
|
@@ -225,6 +235,7 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
225
235
|
| [nested-rows.tsx](examples/nested-rows.tsx) | Partners with their expense lines: sub-rows indented under the parent's name, a chevron toggle, together with selection and sorting — `getRowChildren`, `expanded`, `onExpandedChange`. |
|
|
226
236
|
| [detail-panel.tsx](examples/detail-panel.tsx) | Order details in a full-width row under an expanded order, aligned with the first content column — `renderExpanded`, `defaultExpanded`. |
|
|
227
237
|
| [sticky.tsx](examples/sticky.tsx) | Monthly sales by region in a 280 px window: the head and the region column stay while scrolling both ways — `stickyHeader`, `stickyFirstColumn`, `scrollHeight`. |
|
|
238
|
+
| [columns-visibility.tsx](examples/columns-visibility.tsx) | Orders with a column chooser in the toolbar: fewer columns, less sideways scrolling; the order number always stays — `hiddenColumns`, `hideable`, `toolbar`. |
|
|
228
239
|
| [infinite-scroll.tsx](examples/infinite-scroll.tsx) | An audit log that reveals loaded rows in batches and then asks the server for more — `paging`, `infiniteBatchSize`, `hasMore`, `loadingMore`, `onLoadMore`. |
|
|
229
240
|
| [states.tsx](examples/states.tsx) | Loading skeleton, an empty period and a load error with a retry: the head stays, only the body changes — `loading`, `loadingRows`, `empty`, `error`. |
|
|
230
241
|
| [controlled.tsx](examples/controlled.tsx) | Sort and page owned by the parent (a URL or a store): a header click goes asc → desc → none, a new sort returns to page 1 — `sort`, `onSortChange`, `page`, `onPageChange`. |
|
|
@@ -239,6 +250,9 @@ Root attributes: `data-size`, `data-row-dividers`, `data-column-dividers`, `data
|
|
|
239
250
|
- A custom checkbox column → use `selectable` (range, drag and announcements built in).
|
|
240
251
|
- A long table in a `scrollHeight` window without `stickyHeader` → the head scrolls away.
|
|
241
252
|
- `paging="none"` for hundreds of rows → use `pages` or `infinite`.
|
|
253
|
+
- Dropping columns below a breakpoint → people lose data without knowing it; offer a column chooser with `hiddenColumns`.
|
|
254
|
+
- Letting the key column be hidden → set `hideable: false` on it, so every row keeps its name.
|
|
255
|
+
- Wrapping the table in an `overflow-x` box of your own → it already scrolls inside and shows the edge shadows.
|
|
242
256
|
|
|
243
257
|
## Related
|
|
244
258
|
- **Built from:** [Checkbox](../checkbox/COMPONENT.md), [Button](../button/COMPONENT.md), [Pagination](../pagination/COMPONENT.md), [ScrollContainer](../scroll-container/COMPONENT.md), [EmptyPage](../empty-page/COMPONENT.md), [Skeleton](../skeleton/COMPONENT.md) (loading rows, in the table tier)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/** Orders with a column chooser in the toolbar: fewer columns, less sideways scrolling; the order number always stays — `hiddenColumns`, `hideable`, `toolbar`. */
|
|
2
|
+
import {
|
|
3
|
+
Badge,
|
|
4
|
+
Button,
|
|
5
|
+
DataTable,
|
|
6
|
+
type DataTableColumn,
|
|
7
|
+
Dropdown,
|
|
8
|
+
Icon,
|
|
9
|
+
Typography,
|
|
10
|
+
} from "prime-ui-kit";
|
|
11
|
+
import * as React from "react";
|
|
12
|
+
|
|
13
|
+
import styles from "./examples.module.css";
|
|
14
|
+
|
|
15
|
+
type Status = "paid" | "shipped" | "waiting";
|
|
16
|
+
type Order = {
|
|
17
|
+
id: string;
|
|
18
|
+
client: string;
|
|
19
|
+
manager: string;
|
|
20
|
+
city: string;
|
|
21
|
+
status: Status;
|
|
22
|
+
date: string;
|
|
23
|
+
items: number;
|
|
24
|
+
total: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const STATUS: Record<Status, { label: string; color: "green" | "blue" | "orange" }> = {
|
|
28
|
+
paid: { label: "Оплачен", color: "green" },
|
|
29
|
+
shipped: { label: "Отгружен", color: "blue" },
|
|
30
|
+
waiting: { label: "Ждёт оплаты", color: "orange" },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const ORDERS: Order[] = [
|
|
34
|
+
{
|
|
35
|
+
id: "№ 1040",
|
|
36
|
+
client: "ООО «Северный ветер»",
|
|
37
|
+
manager: "Ольга Смирнова",
|
|
38
|
+
city: "Москва",
|
|
39
|
+
status: "paid",
|
|
40
|
+
date: "02.10.2026",
|
|
41
|
+
items: 12,
|
|
42
|
+
total: 184_500,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "№ 1041",
|
|
46
|
+
client: "ИП Козлов",
|
|
47
|
+
manager: "Игорь Волков",
|
|
48
|
+
city: "Казань",
|
|
49
|
+
status: "waiting",
|
|
50
|
+
date: "03.10.2026",
|
|
51
|
+
items: 3,
|
|
52
|
+
total: 27_900,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "№ 1042",
|
|
56
|
+
client: "АО «Технопарк»",
|
|
57
|
+
manager: "Ольга Смирнова",
|
|
58
|
+
city: "Санкт-Петербург",
|
|
59
|
+
status: "shipped",
|
|
60
|
+
date: "04.10.2026",
|
|
61
|
+
items: 40,
|
|
62
|
+
total: 612_000,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "№ 1043",
|
|
66
|
+
client: "ООО «Лето»",
|
|
67
|
+
manager: "Мария Орлова",
|
|
68
|
+
city: "Екатеринбург",
|
|
69
|
+
status: "paid",
|
|
70
|
+
date: "05.10.2026",
|
|
71
|
+
items: 7,
|
|
72
|
+
total: 58_350,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "№ 1044",
|
|
76
|
+
client: "ООО «Гранит»",
|
|
77
|
+
manager: "Игорь Волков",
|
|
78
|
+
city: "Новосибирск",
|
|
79
|
+
status: "shipped",
|
|
80
|
+
date: "06.10.2026",
|
|
81
|
+
items: 18,
|
|
82
|
+
total: 241_800,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const RUB = new Intl.NumberFormat("ru-RU", {
|
|
87
|
+
style: "currency",
|
|
88
|
+
currency: "RUB",
|
|
89
|
+
maximumFractionDigits: 0,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const COLUMNS: DataTableColumn<Order>[] = [
|
|
93
|
+
{ id: "id", header: "Заказ", accessor: "id", hideable: false },
|
|
94
|
+
{ id: "client", header: "Клиент", accessor: "client" },
|
|
95
|
+
{ id: "manager", header: "Менеджер", accessor: "manager" },
|
|
96
|
+
{ id: "city", header: "Город", accessor: "city" },
|
|
97
|
+
{
|
|
98
|
+
id: "status",
|
|
99
|
+
header: "Статус",
|
|
100
|
+
cell: (row) => (
|
|
101
|
+
<Badge.Root color={STATUS[row.status].color}>{STATUS[row.status].label}</Badge.Root>
|
|
102
|
+
),
|
|
103
|
+
},
|
|
104
|
+
{ id: "date", header: "Дата", accessor: "date", numeric: true },
|
|
105
|
+
{ id: "items", header: "Позиций", accessor: "items", numeric: true },
|
|
106
|
+
{
|
|
107
|
+
id: "total",
|
|
108
|
+
header: "Сумма",
|
|
109
|
+
accessor: "total",
|
|
110
|
+
sortable: true,
|
|
111
|
+
numeric: true,
|
|
112
|
+
cell: (row) => RUB.format(row.total),
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
export default function DataTableColumnsVisibilityExample() {
|
|
117
|
+
const [hidden, setHidden] = React.useState<string[]>(["city", "items"]);
|
|
118
|
+
const shown = COLUMNS.length - hidden.length;
|
|
119
|
+
|
|
120
|
+
const toggle = (id: string, visible: boolean) =>
|
|
121
|
+
setHidden((current) => (visible ? current.filter((c) => c !== id) : [...current, id]));
|
|
122
|
+
|
|
123
|
+
return (
|
|
124
|
+
<DataTable
|
|
125
|
+
columns={COLUMNS}
|
|
126
|
+
hiddenColumns={hidden}
|
|
127
|
+
rows={ORDERS}
|
|
128
|
+
getRowKey={(row) => row.id}
|
|
129
|
+
stickyFirstColumn
|
|
130
|
+
toolbar={
|
|
131
|
+
<div className={styles.toolbar}>
|
|
132
|
+
<Typography as="span" variant="body-s" tone="secondary">
|
|
133
|
+
Заказы за неделю: {ORDERS.length}
|
|
134
|
+
</Typography>
|
|
135
|
+
<Dropdown.Root>
|
|
136
|
+
<Dropdown.Trigger>
|
|
137
|
+
<Button.Root variant="outline" tone="neutral" size="s">
|
|
138
|
+
<Button.Icon>
|
|
139
|
+
<Icon name="view.preview" />
|
|
140
|
+
</Button.Icon>
|
|
141
|
+
Колонки: {shown} из {COLUMNS.length}
|
|
142
|
+
</Button.Root>
|
|
143
|
+
</Dropdown.Trigger>
|
|
144
|
+
<Dropdown.Content align="end" size="s">
|
|
145
|
+
<Dropdown.Group label="Колонки таблицы">
|
|
146
|
+
{COLUMNS.map((column) => (
|
|
147
|
+
<Dropdown.CheckboxItem
|
|
148
|
+
key={column.id}
|
|
149
|
+
checked={!hidden.includes(column.id)}
|
|
150
|
+
disabled={column.hideable === false}
|
|
151
|
+
onCheckedChange={(visible) => toggle(column.id, visible)}
|
|
152
|
+
>
|
|
153
|
+
{column.header}
|
|
154
|
+
</Dropdown.CheckboxItem>
|
|
155
|
+
))}
|
|
156
|
+
</Dropdown.Group>
|
|
157
|
+
<Dropdown.Separator />
|
|
158
|
+
<Dropdown.Item disabled={hidden.length === 0} onSelect={() => setHidden([])}>
|
|
159
|
+
Показать все
|
|
160
|
+
</Dropdown.Item>
|
|
161
|
+
</Dropdown.Content>
|
|
162
|
+
</Dropdown.Root>
|
|
163
|
+
</div>
|
|
164
|
+
}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -37,19 +37,20 @@ DigitInput field frame: label row → cells → support row (hint | e
|
|
|
37
37
|
| Prop | Type | Default | Description |
|
|
38
38
|
|---|---|---|---|
|
|
39
39
|
| `length` | `number` | `4` | Number of cells. |
|
|
40
|
-
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `host tier, else "m"` | Tier: the cell is a square
|
|
40
|
+
| `size` | `"xs" \| "s" \| "m" \| "l" \| "xl"` | `host tier, else "m"` | Tier: the cell is a square on its own scale, two steps above the control heights (36 · 40 · 48 · 56 · 64); label and hint follow the field tier. Without it the tier of its host (LoginForm, a panel), else `m`. |
|
|
41
41
|
| `label` | `ReactNode` | — | Label above the cells; names the group (`aria-labelledby`) and focuses the first cell on click. Without it the group is named by `labels.group`. |
|
|
42
42
|
| `required` | `boolean` | — | Red `*` after the label and native `required` on every cell. |
|
|
43
43
|
| `optional` | `boolean` | — | Muted marker right after the label text (`labels.optional`). |
|
|
44
44
|
| `hint` | `ReactNode` | — | Help text under the cells. Hidden while `error` is shown. |
|
|
45
45
|
| `error` | `ReactNode` | — | Error message in the hint slot; implies `invalid`. |
|
|
46
46
|
| `invalid` | `boolean` | — | Danger ring and danger digits on every cell, `aria-invalid`. A non-empty `error` implies it. |
|
|
47
|
+
| `success` | `boolean` | `false` | The code was accepted (after the server checked it): the cells turn success one after another. Ignored while invalid. Say it in words too (`hint`). |
|
|
47
48
|
| `disabled` | `boolean` | — | Disables the fieldset and every cell. |
|
|
48
49
|
| `value` | `string` | — | Controlled code; non-digits are dropped, extra digits cut to `length`. |
|
|
49
50
|
| `defaultValue` | `string` | `""` | Initial code when uncontrolled. |
|
|
50
51
|
| `onValueChange` | `(value: string) => void` | — | Called with the joined digits on every change. |
|
|
51
52
|
| `onComplete` | `(value: string) => void` | — | Called once when the last empty cell is filled (typing, paste or autofill). |
|
|
52
|
-
| `fullWidth` | `boolean` | `false` | Cells share the container width and keep the tier height; otherwise square cells and the field hugs them. |
|
|
53
|
+
| `fullWidth` | `boolean` | `false` | Cells share the container width up to 1.5 × their side (the row centres beyond that) and keep the tier height; otherwise square cells and the field hugs them. |
|
|
53
54
|
| `groupSize` | `number` | — | Splits the cells into groups of this size with a wider gap (`3` → 123 456). |
|
|
54
55
|
| `mask` | `boolean` | `false` | Hides the digits (PIN): cells are `type="password"`. |
|
|
55
56
|
| `name` | `string` | — | Name of a hidden input that carries the joined code in a native form submit. |
|
|
@@ -66,18 +67,18 @@ No `variant` or `tone`.
|
|
|
66
67
|
### size
|
|
67
68
|
| Value | Looks like | Use when | Default |
|
|
68
69
|
|---|---|---|---|
|
|
69
|
-
| `xs` |
|
|
70
|
-
| `s` |
|
|
71
|
-
| `m` |
|
|
72
|
-
| `l` |
|
|
73
|
-
| `xl` |
|
|
70
|
+
| `xs` | 36 × 36 cells, radius 8, digits 16/24, gap 8 | A code inside a popover or a dense panel | |
|
|
71
|
+
| `s` | 40 × 40 cells, radius 10, digits 18/24, gap 8 | Compact dialogs | |
|
|
72
|
+
| `m` | 48 × 48 cells, radius 12, digits 20/28, gap 12 | Regular forms, modals, LoginForm | yes |
|
|
73
|
+
| `l` | 56 × 56 cells, radius 12, digits 24/32, gap 12 | Standalone verification screens | |
|
|
74
|
+
| `xl` | 64 × 64 cells, radius 16, digits 30/36, gap 16 | Full-page sign-in, mobile-first screens | |
|
|
74
75
|
|
|
75
|
-
**Sizes:**
|
|
76
|
+
**Sizes:** cells have their own scale, two steps above the control heights: a code is the one thing on its screen and a small cell is hard to hit and to read (cell `xs` = control `m`, `s` = control `l`, `m` = control `xl`). Digits are semibold and tabular and grow with the cell; the label and the hint keep the field tier.
|
|
76
77
|
|
|
77
78
|
### Flags
|
|
78
79
|
| Value | Looks like | Use when | Default |
|
|
79
80
|
|---|---|---|---|
|
|
80
|
-
| `fullWidth` | one row, cells share the width, tier height |
|
|
81
|
+
| `fullWidth` | one row, cells share the width up to 1.5 × their side, then the row centres; tier height | narrow phone columns where a full-width button follows | `false` |
|
|
81
82
|
| `groupSize` | an extra gap before every group (`3` → 123 456) | codes of six digits and longer | — |
|
|
82
83
|
| `mask` | dots instead of digits | PIN codes | `false` |
|
|
83
84
|
| `required` / `optional` | red `*` / muted «необязательно» after the label | mark the minority of the form | — |
|
|
@@ -92,8 +93,9 @@ No `variant` or `tone`.
|
|
|
92
93
|
| State | Driven by | DOM |
|
|
93
94
|
|---|---|---|
|
|
94
95
|
| hover | — | cell fill darkens (not on focused or disabled cells) |
|
|
95
|
-
| focus | keyboard / click | focused cell: focus fill
|
|
96
|
-
| filled cell | a digit in the cell | `data-filled="true"` on the cell |
|
|
96
|
+
| focus | keyboard / click | focused cell: focus fill and its content selected so a new digit replaces it; one inset ring for the row appears on the cell and glides to the next one (`fast` · emphasized) instead of each cell drawing its own |
|
|
97
|
+
| filled cell | a digit in the cell | `data-filled="true"` on the cell; the digit fades in as it lands |
|
|
98
|
+
| success | `success` (not while invalid) | `data-success="true"` on the fieldset; the cells turn `success-soft` one after another, half a stagger step apart |
|
|
97
99
|
| full width | `fullWidth` | `data-full-width="true"` on the fieldset |
|
|
98
100
|
| group start | `groupSize` | `data-group-start="true"` on the first cell of every group |
|
|
99
101
|
| invalid | `invalid` or a non-empty `error` | `data-invalid="true"` on the fieldset and the frame, `aria-invalid="true"` on cells |
|
|
@@ -137,12 +139,12 @@ No `variant` or `tone`.
|
|
|
137
139
|
| Example | Shows |
|
|
138
140
|
|---|---|
|
|
139
141
|
| [overview.tsx](examples/overview.tsx) | A six-digit code from SMS with its label and a hint — `label`, `hint`, `length`. |
|
|
140
|
-
| [sizes.tsx](examples/sizes.tsx) | Every size; the cell is a square
|
|
142
|
+
| [sizes.tsx](examples/sizes.tsx) | Every size; the cell is a square on its own scale, two steps above the control heights — `size`. |
|
|
141
143
|
| [states.tsx](examples/states.tsx) | A default code next to a disabled one — `disabled`. |
|
|
142
144
|
| [validation.tsx](examples/validation.tsx) | Required and optional markers, a hint and an error that replaces it — `required`, `optional`, `hint`, `error`. |
|
|
143
145
|
| [grouped.tsx](examples/grouped.tsx) | A long code read in chunks with a wider gap between groups — `groupSize`. |
|
|
144
146
|
| [full-width.tsx](examples/full-width.tsx) | Cells that share the container width and keep the tier height, above a full-width button — `fullWidth`. |
|
|
145
|
-
| [on-complete.tsx](examples/on-complete.tsx) | The code is checked as soon as the last cell is filled
|
|
147
|
+
| [on-complete.tsx](examples/on-complete.tsx) | The code is checked as soon as the last cell is filled: a right one turns the cells success, a wrong one turns the hint into an error — `onComplete`, `success`, `error`. |
|
|
146
148
|
| [controlled.tsx](examples/controlled.tsx) | The parent owns the code and clears it with a button — `value`, `onValueChange`. |
|
|
147
149
|
| [in-form.tsx](examples/in-form.tsx) | A masked card PIN submitted with a form; a short PIN shows an error — `name`, `mask`, `required`, `error`. |
|
|
148
150
|
|
|
@@ -8,6 +8,14 @@
|
|
|
8
8
|
width: min(100%, var(--prime-modal-width-s));
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/* in-form.tsx: the form hugs the cells; as a grid item the button stretches to the code's width. */
|
|
12
|
+
.form {
|
|
13
|
+
display: grid;
|
|
14
|
+
gap: var(--prime-space-6);
|
|
15
|
+
width: fit-content;
|
|
16
|
+
max-width: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
/* controlled.tsx: the code and its action on one line, bottoms aligned. */
|
|
12
20
|
.row {
|
|
13
21
|
display: flex;
|
|
@@ -16,20 +16,17 @@ export default function DigitInputInFormExample() {
|
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
|
-
<form className={styles.
|
|
19
|
+
<form className={styles.form} noValidate onSubmit={submit}>
|
|
20
20
|
<DigitInput
|
|
21
21
|
label="PIN-код карты"
|
|
22
22
|
name="pin"
|
|
23
23
|
length={PIN_LENGTH}
|
|
24
24
|
mask
|
|
25
25
|
required
|
|
26
|
-
fullWidth
|
|
27
26
|
error={error}
|
|
28
27
|
onValueChange={() => setError(undefined)}
|
|
29
28
|
/>
|
|
30
|
-
<Button.Root type="submit"
|
|
31
|
-
Привязать карту
|
|
32
|
-
</Button.Root>
|
|
29
|
+
<Button.Root type="submit">Привязать карту</Button.Root>
|
|
33
30
|
</form>
|
|
34
31
|
);
|
|
35
32
|
}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
/** The code is checked as soon as the last cell is filled
|
|
1
|
+
/** The code is checked as soon as the last cell is filled: a right one turns the cells success, a wrong one turns the hint into an error — `onComplete`, `success`, `error`. */
|
|
2
2
|
import { DigitInput } from "prime-ui-kit";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
5
|
const VALID_CODE = "123456";
|
|
6
6
|
|
|
7
7
|
export default function DigitInputOnCompleteExample() {
|
|
8
|
-
const [
|
|
8
|
+
const [result, setResult] = React.useState<"accepted" | "rejected">();
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<DigitInput
|
|
12
12
|
label="Код подтверждения входа"
|
|
13
13
|
length={6}
|
|
14
|
-
hint={`Для проверки подходит ${VALID_CODE}`}
|
|
15
|
-
error={
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
hint={result === "accepted" ? "Код подтверждён" : `Для проверки подходит ${VALID_CODE}`}
|
|
15
|
+
error={result === "rejected" ? "Неверный код" : undefined}
|
|
16
|
+
success={result === "accepted"}
|
|
17
|
+
onValueChange={() => setResult(undefined)}
|
|
18
|
+
onComplete={(code) => setResult(code === VALID_CODE ? "accepted" : "rejected")}
|
|
18
19
|
/>
|
|
19
20
|
);
|
|
20
21
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Every size; the cell is a square
|
|
1
|
+
/** Every size; the cell is a square on its own scale, two steps above the control heights — `size`. */
|
|
2
2
|
import { DigitInput } from "prime-ui-kit";
|
|
3
3
|
|
|
4
4
|
const SIZES = ["xs", "s", "m", "l", "xl"] as const;
|