prime-ui-kit 0.9.2 → 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 +9 -5
- package/dist/chunks/{chunk-ZEFCDEEQ.js → chunk-6JTZOA5I.js} +878 -331
- package/dist/chunks/{chunk-LXGRKN34.js → chunk-OAE4IQX2.js} +769 -479
- package/dist/chunks/{chunk-IQELQNMR.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/Row.d.ts +3 -1
- package/dist/components/data-table/rows.d.ts +4 -0
- package/dist/components/digit-input/DigitInput.d.ts +6 -1
- package/dist/components/index.d.ts +4 -1
- package/dist/components/input/Input.d.ts +16 -1
- package/dist/components/input/passwordStrength.d.ts +9 -0
- package/dist/components/progress-bar/ProgressBar.d.ts +16 -1
- package/dist/components/sparkline/Sparkline.d.ts +26 -0
- package/dist/hooks/useEdgeOverflow.d.ts +4 -0
- package/dist/icons/registry.d.ts +2 -0
- package/dist/index.css +830 -267
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -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/surfaceDepth.d.ts +25 -0
- package/dist/internal/swatch.d.ts +4 -1
- package/dist/layout/app-shell/AppShell.d.ts +2 -2
- 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/semantic.d.ts +66 -56
- package/dist/tokens/themes/dark.d.ts +6 -14
- package/package.json +1 -1
- package/src/components/accordion/COMPONENT.md +4 -4
- package/src/components/avatar/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 +1 -1
- 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/examples/overview.tsx +3 -3
- package/src/components/data-table/COMPONENT.md +5 -4
- package/src/components/digit-input/COMPONENT.md +5 -3
- package/src/components/digit-input/examples/on-complete.tsx +7 -6
- package/src/components/dnd/COMPONENT.md +1 -1
- package/src/components/dnd/examples/examples.module.css +4 -4
- package/src/components/dropdown/COMPONENT.md +1 -1
- package/src/components/dropdown/examples/examples.module.css +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 +4 -4
- package/src/components/login-form/COMPONENT.md +4 -4
- 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 +2 -2
- package/src/components/modal/examples/examples.module.css +2 -2
- package/src/components/page-content/examples/examples.module.css +1 -1
- package/src/components/popover/COMPONENT.md +1 -1
- package/src/components/progress-bar/COMPONENT.md +5 -1
- 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/skeleton/examples/with-crossfade.tsx +3 -3
- 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/tabs/COMPONENT.md +15 -16
- 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 +1 -1
- package/src/layout/app-header/examples/examples.module.css +4 -4
- package/src/layout/app-shell/COMPONENT.md +8 -8
- package/src/layout/app-shell/examples/examples.module.css +3 -3
- package/src/layout/bottom-nav/COMPONENT.md +1 -1
- package/src/layout/bottom-nav/examples/examples.module.css +1 -1
- package/src/layout/sidebar/COMPONENT.md +4 -4
- package/src/layout/sidebar/examples/examples.module.css +5 -5
- package/src/styles/globals.css +125 -2
- package/src/styles/theme-dark.css +36 -13
- package/src/styles/theme-light.css +60 -16
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -34,7 +34,6 @@ import {
|
|
|
34
34
|
PageToolbar,
|
|
35
35
|
Pagination,
|
|
36
36
|
Popover,
|
|
37
|
-
ProgressBar,
|
|
38
37
|
ProgressCircle,
|
|
39
38
|
Radio,
|
|
40
39
|
SegmentedControl,
|
|
@@ -42,6 +41,7 @@ import {
|
|
|
42
41
|
Skeleton,
|
|
43
42
|
Slider,
|
|
44
43
|
SmartFilter,
|
|
44
|
+
Sparkline,
|
|
45
45
|
Stepper,
|
|
46
46
|
Switch,
|
|
47
47
|
THEME_SWITCHING_ATTRIBUTE,
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
Typography,
|
|
54
54
|
YEARLESS_YEAR,
|
|
55
55
|
applyTheme,
|
|
56
|
+
celebrate,
|
|
56
57
|
datepickerPresets,
|
|
57
58
|
formatDatepickerValue,
|
|
58
59
|
matchesSmartFilter,
|
|
@@ -61,7 +62,7 @@ import {
|
|
|
61
62
|
suspendTransitions,
|
|
62
63
|
useNotifications,
|
|
63
64
|
useSidebar
|
|
64
|
-
} from "./chunks/chunk-
|
|
65
|
+
} from "./chunks/chunk-OAE4IQX2.js";
|
|
65
66
|
import {
|
|
66
67
|
Button,
|
|
67
68
|
COLOR_PRESETS,
|
|
@@ -74,6 +75,7 @@ import {
|
|
|
74
75
|
Icon,
|
|
75
76
|
Input,
|
|
76
77
|
Label,
|
|
78
|
+
ProgressBar,
|
|
77
79
|
ScrollContainer,
|
|
78
80
|
Select,
|
|
79
81
|
Spinner,
|
|
@@ -81,13 +83,14 @@ import {
|
|
|
81
83
|
controlSizes,
|
|
82
84
|
createIcon,
|
|
83
85
|
dataStates,
|
|
86
|
+
getPasswordStrength,
|
|
84
87
|
paletteColors,
|
|
85
88
|
stepDown,
|
|
86
89
|
textTones,
|
|
87
90
|
tones,
|
|
88
91
|
useOptionalControlSize,
|
|
89
92
|
variants
|
|
90
|
-
} from "./chunks/chunk-
|
|
93
|
+
} from "./chunks/chunk-6JTZOA5I.js";
|
|
91
94
|
export {
|
|
92
95
|
Accordion,
|
|
93
96
|
AppHeader,
|
|
@@ -145,6 +148,7 @@ export {
|
|
|
145
148
|
Skeleton,
|
|
146
149
|
Slider,
|
|
147
150
|
SmartFilter,
|
|
151
|
+
Sparkline,
|
|
148
152
|
Spinner,
|
|
149
153
|
Stepper,
|
|
150
154
|
Switch,
|
|
@@ -158,11 +162,13 @@ export {
|
|
|
158
162
|
Typography,
|
|
159
163
|
YEARLESS_YEAR,
|
|
160
164
|
applyTheme,
|
|
165
|
+
celebrate,
|
|
161
166
|
controlSizes,
|
|
162
167
|
createIcon,
|
|
163
168
|
dataStates,
|
|
164
169
|
datepickerPresets,
|
|
165
170
|
formatDatepickerValue,
|
|
171
|
+
getPasswordStrength,
|
|
166
172
|
matchesSmartFilter,
|
|
167
173
|
moveBefore,
|
|
168
174
|
paletteColors,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CheckMarkState = "checked" | "unchecked" | "indeterminate";
|
|
2
|
+
/**
|
|
3
|
+
* The kit's check and indeterminate bar, drawn in with a stroke reveal (foundation §7 rule 8: a
|
|
4
|
+
* check draws in, it does not pop). Both paths stay in the DOM so the mark animates both ways;
|
|
5
|
+
* `state` picks the one that shows. Color is `currentColor`; size comes from the host box.
|
|
6
|
+
* Shared by Checkbox and the color swatches. Own SVG instead of `Icon`: the motion needs
|
|
7
|
+
* `pathLength` on the paths.
|
|
8
|
+
*/
|
|
9
|
+
export declare function CheckMark({ state, className }: {
|
|
10
|
+
state: CheckMarkState;
|
|
11
|
+
className?: string;
|
|
12
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type MorphTextProps = {
|
|
2
|
+
children: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* A label that changes in place (foundation §7 rule 8): the old letters leave upward, the new ones
|
|
7
|
+
* come from below one short step apart, and the box glides between the two widths. At rest it is
|
|
8
|
+
* one plain text span; the letter layers exist only while a change plays. Still on the first
|
|
9
|
+
* render, under reduced motion, and when only digits change (a counter or a percentage in a label
|
|
10
|
+
* updates in place).
|
|
11
|
+
*/
|
|
12
|
+
export declare function MorphText({ children: text, className }: MorphTextProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type RollingNumberProps = {
|
|
2
|
+
/** The formatted value («4 812 350 ₽», «12», «−3,4%»); digits roll, other characters stay put. */
|
|
3
|
+
children: string | number;
|
|
4
|
+
className?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* A number whose digits roll to their new values column by column, like an odometer (foundation §7
|
|
8
|
+
* rule 8: counters ease, they do not flip). The DOM holds the real digits (text, copy, assistive
|
|
9
|
+
* tech and a page without CSS read the value); each digit column draws the 0–9 strip in a
|
|
10
|
+
* pseudo-element. Columns are keyed from the right, so an unchanged digit stays still and a new
|
|
11
|
+
* leading digit appears without rolling. A value that changes by itself every frame (a running
|
|
12
|
+
* percentage) stays plain tabular text: rolling there is noise.
|
|
13
|
+
*/
|
|
14
|
+
export declare function RollingNumber({ children, className }: RollingNumberProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Depth of a surface on the ladder (foundation §4): `0` the page … `4` the deepest nested layer,
|
|
4
|
+
* `floating` for menus, popovers, modals and drawers, `floating-1`/`floating-2` for layers nested in
|
|
5
|
+
* one, `tinted` for a host off the ladder (a tinted banner, an accent wash). A surface renders
|
|
6
|
+
* `data-depth` with it; `globals.css` maps each depth to the context variables
|
|
7
|
+
* (`--prime-color-layer-current`, `--prime-color-field-bg`…).
|
|
8
|
+
*/
|
|
9
|
+
export type SurfaceDepth = 0 | 1 | 2 | 3 | 4 | "floating" | "floating-1" | "floating-2" | "tinted";
|
|
10
|
+
/**
|
|
11
|
+
* The depth of a surface nested in `depth`; the deepest layer and `floating-2` repeat, a card on a
|
|
12
|
+
* tinted host is a card on the page.
|
|
13
|
+
*/
|
|
14
|
+
export declare function nextSurfaceDepth(depth: SurfaceDepth): SurfaceDepth;
|
|
15
|
+
/** Depth of the surface the caller renders: one above the nearest surface around it. */
|
|
16
|
+
export declare function useNestedSurfaceDepth(): SurfaceDepth;
|
|
17
|
+
/** Surfaces inside `children` count their depth from `value`. */
|
|
18
|
+
export declare const SurfaceDepthProvider: React.Provider<SurfaceDepth>;
|
|
19
|
+
/**
|
|
20
|
+
* The layer an ExampleFrame stage stands for (the docs' "preview background"). The stage element
|
|
21
|
+
* itself takes it as `data-depth`: no wrapper may sit between the stage and the example, because
|
|
22
|
+
* the stage lays out its children with child selectors (`> *`).
|
|
23
|
+
*/
|
|
24
|
+
export declare const StageDepthProvider: React.Provider<SurfaceDepth>;
|
|
25
|
+
export declare function useStageDepth(): SurfaceDepth;
|
|
@@ -31,7 +31,10 @@ export declare const swatchTierClass: string;
|
|
|
31
31
|
export declare function SwatchFill({ value }: {
|
|
32
32
|
value: string | null;
|
|
33
33
|
}): import("react/jsx-runtime").JSX.Element;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Inside a selectable swatch: the color layer and the contrasting check, which draws in when the
|
|
36
|
+
* swatch is selected and retracts when it is not (`CheckMark`, the same mark as Checkbox).
|
|
37
|
+
*/
|
|
35
38
|
export declare function SwatchContent({ value, selected }: {
|
|
36
39
|
value: string | null;
|
|
37
40
|
selected: boolean;
|
|
@@ -7,14 +7,14 @@ export type AppShellRootProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
|
7
7
|
export type AppShellNavProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
8
8
|
ref?: React.Ref<HTMLDivElement>;
|
|
9
9
|
};
|
|
10
|
-
/** Navigation column (usually `Sidebar.Root
|
|
10
|
+
/** Navigation column (usually `Sidebar.Root`, a surface one layer above the page). */
|
|
11
11
|
declare function AppShellNav({ className, ...rest }: AppShellNavProps): import("react/jsx-runtime").JSX.Element;
|
|
12
12
|
declare namespace AppShellNav {
|
|
13
13
|
var displayName: string;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Grid: navigation column | content panel. Everything that is not `AppShell.Nav` goes into the
|
|
17
|
-
* content panel (
|
|
17
|
+
* content panel, which is the page itself (the layer around the shell): cards on it are the next layer.
|
|
18
18
|
*/
|
|
19
19
|
declare function AppShellRoot({ fillViewport, className, children, ...rest }: AppShellRootProps): import("react/jsx-runtime").JSX.Element;
|
|
20
20
|
declare namespace AppShellRoot {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type CelebrateOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Where the burst starts: an element (from its center) or a point in the viewport. Default: the
|
|
4
|
+
* middle of the screen, a third from the top.
|
|
5
|
+
*/
|
|
6
|
+
origin?: Element | {
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* A short confetti burst for a rare milestone: a quarter closed, the first invoice paid, an
|
|
13
|
+
* onboarding finished (foundation §7 rule 1 — only rare moments carry delight; never for routine
|
|
14
|
+
* success, that is a Notification). Draws on a canvas above every layer, ignores the pointer and
|
|
15
|
+
* assistive tech, and removes itself after 1.4 s. Under reduced motion it draws nothing. Say the
|
|
16
|
+
* achievement in words too: the burst is decoration. Resolves when the canvas is gone.
|
|
17
|
+
*/
|
|
18
|
+
export declare function celebrate(options?: CelebrateOptions): Promise<void>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** ΔL in OKLab between neighbouring layers. */
|
|
2
|
+
export declare const LAYER_STEP = 0.03;
|
|
3
|
+
/** Layers 0…4: the page and four nested surfaces. */
|
|
4
|
+
export declare const LAYER_DEPTH = 4;
|
|
5
|
+
export type LayerTheme = {
|
|
6
|
+
/** The page color (layer 0). */
|
|
7
|
+
page: string;
|
|
8
|
+
/** Lightness window: no color goes outside it. */
|
|
9
|
+
lo: number;
|
|
10
|
+
hi: number;
|
|
11
|
+
/** Direction of control fills from their host: −1 darker, +1 lighter. */
|
|
12
|
+
control: 1 | -1;
|
|
13
|
+
};
|
|
14
|
+
export declare const LIGHT_LAYERS: LayerTheme;
|
|
15
|
+
export declare const DARK_LAYERS: LayerTheme;
|
|
16
|
+
export declare const lightnessOf: (hex: string) => number;
|
|
17
|
+
/**
|
|
18
|
+
* `steps` steps from L towards `dir`. Reflects when less than half a step of room is left (unless
|
|
19
|
+
* `reflect` is off), snaps to the edge when less than half a step would remain after the move.
|
|
20
|
+
*/
|
|
21
|
+
export declare function move(theme: LayerTheme, L: number, dir: 1 | -1, steps?: number, reflect?: boolean): number;
|
|
22
|
+
export type LayerColors = {
|
|
23
|
+
/** The layer itself. */
|
|
24
|
+
bg: string;
|
|
25
|
+
/** Fields, chips, segmented tracks, neutral buttons on this layer. */
|
|
26
|
+
fill: string;
|
|
27
|
+
/** Hover of that fill. */
|
|
28
|
+
fillHover: string;
|
|
29
|
+
/** The selected segment (thumb) on a track of this layer. */
|
|
30
|
+
selected: string;
|
|
31
|
+
};
|
|
32
|
+
/** Layers 0…LAYER_DEPTH of a theme, each one step from its parent. */
|
|
33
|
+
export declare function layerLadder(theme: LayerTheme): LayerColors[];
|
|
34
|
+
/** Floating layers (popovers, menus, modals, drawers) sit one step above a card and never reflect. */
|
|
35
|
+
export declare function floatingLayer(theme: LayerTheme): LayerColors;
|
|
36
|
+
/** Token tree for `color.layer`: `0`…`4` plus `floating` and the two layers nested in it. */
|
|
37
|
+
export declare function layerTokens(theme: LayerTheme): Record<string, LayerColors>;
|
package/dist/tokens/naming.d.ts
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
export declare const REF_PREFIX = "--prime-ref";
|
|
3
3
|
/** Semantic variables: `--prime-*`. */
|
|
4
4
|
export declare const SYS_PREFIX = "--prime";
|
|
5
|
-
/** Token path → CSS variable: `color.
|
|
5
|
+
/** Token path → CSS variable: `color.text.primary` → `--prime-color-text-primary`, camelCase → kebab-case. */
|
|
6
6
|
export declare function toVarName(path: string, prefix?: string): string;
|
|
@@ -1,25 +1,13 @@
|
|
|
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)";
|
|
25
13
|
/** Soft shade over an edge that hides scrolled content (DataTable columns). */
|
|
@@ -32,22 +20,18 @@ export declare const semanticTokens: {
|
|
|
32
20
|
/** The specular top edge of a glass surface: the line that makes it read as glass. */
|
|
33
21
|
readonly glassEdge: "rgba(255, 255, 255, 0.85)";
|
|
34
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
|
+
*/
|
|
35
28
|
readonly fill: {
|
|
36
|
-
/**
|
|
29
|
+
/** Ghost hover, row hover. */
|
|
37
30
|
readonly subtle: "rgba(17, 19, 24, 0.04)";
|
|
38
31
|
readonly subtleActive: "rgba(17, 19, 24, 0.07)";
|
|
39
|
-
/**
|
|
40
|
-
* The faintest wash: hover of a tab in the sunken strip (Tabs). Stays below the
|
|
41
|
-
* sunken → surface step in both themes, so a hovered tab never reads as the selected one.
|
|
42
|
-
*/
|
|
32
|
+
/** The faintest wash: hover of a tab in the tab strip, below the selected tab's step. */
|
|
43
33
|
readonly faint: "rgba(17, 19, 24, 0.04)";
|
|
44
|
-
/**
|
|
45
|
-
* Neutral buttons, chips, segmented track. Translucent ink wash: always one step off whatever
|
|
46
|
-
* it sits on (canvas, card, sunken tile, a custom container), in both themes.
|
|
47
|
-
*/
|
|
48
|
-
readonly muted: "color-mix(in srgb, var(--prime-ref-color-gray-925) 7%, transparent)";
|
|
49
|
-
readonly mutedHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 10%, transparent)";
|
|
50
|
-
/** Unchecked checkbox, switch track, slider track. Same wash, stronger. */
|
|
34
|
+
/** Unchecked checkbox, switch track, slider track. */
|
|
51
35
|
readonly strong: "color-mix(in srgb, var(--prime-ref-color-gray-925) 15%, transparent)";
|
|
52
36
|
readonly strongHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 22%, transparent)";
|
|
53
37
|
};
|
|
@@ -105,39 +89,29 @@ export declare const semanticTokens: {
|
|
|
105
89
|
readonly soft: "{color.sky.100}";
|
|
106
90
|
readonly text: "{color.sky.800}";
|
|
107
91
|
};
|
|
108
|
-
/**
|
|
109
|
-
* Field fill depends on what it sits on. `bg` is the canvas value; every surface component
|
|
110
|
-
* (Card, Modal, Drawer, Popover, Sidebar…) sets `--prime-color-field-bg: var(--prime-color-field-bg-surface)`.
|
|
111
|
-
*/
|
|
92
|
+
/** The field fill at rest, hover and focus comes from the ladder (`--prime-color-field-bg*`). */
|
|
112
93
|
readonly field: {
|
|
113
|
-
/**
|
|
114
|
-
readonly bg: "color-mix(in srgb, var(--prime-ref-color-gray-925) 6%, transparent)";
|
|
115
|
-
readonly bgSurface: "color-mix(in srgb, var(--prime-ref-color-gray-925) 6%, transparent)";
|
|
116
|
-
/** Hover wash of a field: the `bg` wash, twice as dense (≈ `bg` mixed 94% with the text color). */
|
|
117
|
-
readonly bgHover: "color-mix(in srgb, var(--prime-ref-color-gray-925) 12%, transparent)";
|
|
118
|
-
readonly bgFocus: "{color.gray.0}";
|
|
94
|
+
/** A disabled field: a faint wash on whatever it sits on. */
|
|
119
95
|
readonly bgDisabled: "color-mix(in srgb, var(--prime-ref-color-gray-925) 4%, transparent)";
|
|
120
96
|
};
|
|
121
|
-
/**
|
|
122
|
-
* Card fill depends on what it sits on (same idea as `field`). On the canvas a card is white and raised;
|
|
123
|
-
* every surface plane (AppShell content, Card, Modal, Drawer, Popover) sets
|
|
124
|
-
* `--prime-color-card-bg: var(--prime-color-bg-sunken)`, and a card on a sunken fill drops its shadow.
|
|
125
|
-
*/
|
|
126
|
-
readonly card: {
|
|
127
|
-
readonly bg: "{color.bg.surface}";
|
|
128
|
-
};
|
|
129
97
|
readonly focus: {
|
|
130
98
|
readonly ring: "{color.cobalt.700}";
|
|
131
99
|
};
|
|
132
100
|
readonly control: {
|
|
133
101
|
/** Switch thumb, slider thumb. */
|
|
134
102
|
readonly thumb: "{color.white}";
|
|
135
|
-
/** Selected segment in a segmented track: one step lighter than the track. */
|
|
136
|
-
readonly selected: "{color.gray.0}";
|
|
137
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
|
+
*/
|
|
138
108
|
readonly tooltip: {
|
|
139
109
|
readonly bg: "{color.gray.925}";
|
|
140
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)";
|
|
141
115
|
};
|
|
142
116
|
readonly palette: Record<string, {
|
|
143
117
|
soft: string;
|
|
@@ -326,6 +300,8 @@ export declare const semanticTokens: {
|
|
|
326
300
|
};
|
|
327
301
|
/** Step between items of a staggered first render. */
|
|
328
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";
|
|
329
305
|
/** `:active` scale of pressable controls; `compact` for icon-only and small targets. */
|
|
330
306
|
readonly press: {
|
|
331
307
|
readonly scale: "0.98";
|
|
@@ -561,8 +537,6 @@ export declare const semanticTokens: {
|
|
|
561
537
|
readonly minWidth: "12rem";
|
|
562
538
|
};
|
|
563
539
|
readonly card: {
|
|
564
|
-
/** Context: surfaces set it to `none` together with `--prime-color-card-bg` (see color.card). */
|
|
565
|
-
readonly shadow: "{shadow.raised}";
|
|
566
540
|
readonly radius: "{radius.12}";
|
|
567
541
|
readonly paddingS: "{space.4}";
|
|
568
542
|
readonly paddingM: "{space.5}";
|
|
@@ -625,10 +599,46 @@ export declare const semanticTokens: {
|
|
|
625
599
|
readonly floatingInset: "{space.3}";
|
|
626
600
|
readonly blur: "0.5rem";
|
|
627
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
|
+
*/
|
|
628
606
|
readonly tooltip: {
|
|
629
|
-
readonly
|
|
630
|
-
|
|
631
|
-
|
|
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
|
+
};
|
|
632
642
|
readonly maxWidth: "17.5rem";
|
|
633
643
|
/** Arrow base and depth; the arrow points at the trigger's centre. */
|
|
634
644
|
readonly arrowWidth: "0.625rem";
|
|
@@ -1,11 +1,7 @@
|
|
|
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)";
|
|
11
7
|
/** Darker than the scrim's share: a shade on a 900 surface needs more ink to read. */
|
|
@@ -17,8 +13,6 @@ export declare const darkThemeOverrides: {
|
|
|
17
13
|
readonly subtle: "rgba(233, 235, 240, 0.05)";
|
|
18
14
|
readonly subtleActive: "rgba(233, 235, 240, 0.09)";
|
|
19
15
|
readonly faint: "rgba(233, 235, 240, 0.02)";
|
|
20
|
-
readonly muted: "color-mix(in srgb, var(--prime-ref-color-gray-100) 7%, transparent)";
|
|
21
|
-
readonly mutedHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 10%, transparent)";
|
|
22
16
|
readonly strong: "color-mix(in srgb, var(--prime-ref-color-gray-100) 16%, transparent)";
|
|
23
17
|
readonly strongHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 22%, transparent)";
|
|
24
18
|
};
|
|
@@ -72,10 +66,6 @@ export declare const darkThemeOverrides: {
|
|
|
72
66
|
readonly text: "{color.sky.300}";
|
|
73
67
|
};
|
|
74
68
|
readonly field: {
|
|
75
|
-
readonly bg: "color-mix(in srgb, var(--prime-ref-color-gray-100) 6%, transparent)";
|
|
76
|
-
readonly bgSurface: "color-mix(in srgb, var(--prime-ref-color-gray-100) 6%, transparent)";
|
|
77
|
-
readonly bgHover: "color-mix(in srgb, var(--prime-ref-color-gray-100) 12%, transparent)";
|
|
78
|
-
readonly bgFocus: "{color.gray.925}";
|
|
79
69
|
readonly bgDisabled: "color-mix(in srgb, var(--prime-ref-color-gray-100) 4%, transparent)";
|
|
80
70
|
};
|
|
81
71
|
readonly focus: {
|
|
@@ -83,11 +73,13 @@ export declare const darkThemeOverrides: {
|
|
|
83
73
|
};
|
|
84
74
|
readonly control: {
|
|
85
75
|
readonly thumb: "{color.gray.100}";
|
|
86
|
-
readonly selected: "{color.gray.750}";
|
|
87
76
|
};
|
|
77
|
+
/** A light chip over the dark UI: inverse like in light, so it reads over any layer. */
|
|
88
78
|
readonly tooltip: {
|
|
89
|
-
readonly bg: "{color.gray.
|
|
90
|
-
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)";
|
|
91
83
|
};
|
|
92
84
|
readonly palette: Record<string, {
|
|
93
85
|
soft: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prime-ui-kit",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"packageManager": "bun@1.3.13",
|
|
5
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.",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -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
|
|
@@ -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)
|