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