@seekalfred/primitives 0.5.1 → 0.6.0
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 +664 -9
- package/dist/accordion/accordion.js +10 -4
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/action-button/action-button.d.ts +50 -0
- package/dist/action-button/action-button.js +159 -0
- package/dist/action-button/action-button.js.map +1 -0
- package/dist/action-button/index.d.ts +2 -0
- package/dist/action-button/index.js +2 -0
- package/dist/alert/alert.d.ts +15 -1
- package/dist/alert/alert.js +116 -20
- package/dist/alert/alert.js.map +1 -1
- package/dist/avatar/avatar.d.ts +6 -0
- package/dist/avatar/avatar.js +47 -16
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +11 -4
- package/dist/badge/badge.js +35 -5
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +0 -27
- package/dist/breadcrumb/breadcrumb.js +20 -6
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +35 -22
- package/dist/button/button.js +177 -34
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.d.ts +76 -0
- package/dist/button-group/button-group.js +403 -0
- package/dist/button-group/button-group.js.map +1 -0
- package/dist/button-group/index.d.ts +2 -0
- package/dist/button-group/index.js +2 -0
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/calendar/calendar.js +257 -30
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +9 -2
- package/dist/card/card.js +15 -4
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +0 -7
- package/dist/checkbox/checkbox.js +24 -27
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/confirm-morph/confirm-morph.d.ts +85 -0
- package/dist/confirm-morph/confirm-morph.js +652 -0
- package/dist/confirm-morph/confirm-morph.js.map +1 -0
- package/dist/confirm-morph/index.d.ts +2 -0
- package/dist/confirm-morph/index.js +2 -0
- package/dist/copy-button/copy-button.d.ts +32 -0
- package/dist/copy-button/copy-button.js +134 -0
- package/dist/copy-button/copy-button.js.map +1 -0
- package/dist/copy-button/index.d.ts +2 -0
- package/dist/copy-button/index.js +2 -0
- package/dist/date-picker/date-picker.d.ts +51 -0
- package/dist/date-picker/date-picker.js +241 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/date-picker/date-range-picker.d.ts +78 -0
- package/dist/date-picker/date-range-picker.js +1015 -0
- package/dist/date-picker/date-range-picker.js.map +1 -0
- package/dist/date-picker/index.d.ts +4 -0
- package/dist/date-picker/index.js +4 -0
- package/dist/date-picker/picker-parts.d.ts +46 -0
- package/dist/date-picker/picker-parts.js +135 -0
- package/dist/date-picker/picker-parts.js.map +1 -0
- package/dist/dialog/dialog.d.ts +10 -14
- package/dist/dialog/dialog.js +39 -11
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/empty-state/empty-state.d.ts +44 -0
- package/dist/empty-state/empty-state.js +83 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/empty-state/index.d.ts +2 -0
- package/dist/empty-state/index.js +2 -0
- package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
- package/dist/expanding-button-group/expanding-button-group.js +350 -0
- package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
- package/dist/expanding-button-group/index.d.ts +2 -0
- package/dist/expanding-button-group/index.js +2 -0
- package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
- package/dist/file-drop-zone/file-drop-zone.js +116 -0
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
- package/dist/file-drop-zone/index.d.ts +2 -0
- package/dist/file-drop-zone/index.js +2 -0
- package/dist/filter-bar/filter-bar.d.ts +121 -0
- package/dist/filter-bar/filter-bar.js +300 -0
- package/dist/filter-bar/filter-bar.js.map +1 -0
- package/dist/filter-bar/index.d.ts +2 -0
- package/dist/filter-bar/index.js +2 -0
- package/dist/floating-button-group/floating-button-group.d.ts +61 -0
- package/dist/floating-button-group/floating-button-group.js +340 -0
- package/dist/floating-button-group/floating-button-group.js.map +1 -0
- package/dist/floating-button-group/index.d.ts +2 -0
- package/dist/floating-button-group/index.js +2 -0
- package/dist/icon-morph/icon-morph.d.ts +55 -0
- package/dist/icon-morph/icon-morph.js +331 -0
- package/dist/icon-morph/icon-morph.js.map +1 -0
- package/dist/icon-morph/index.d.ts +2 -0
- package/dist/icon-morph/index.js +2 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +29 -7
- package/dist/input/input.d.ts +22 -0
- package/dist/input/input.js +36 -12
- package/dist/input/input.js.map +1 -1
- package/dist/lib/count-to.js +113 -0
- package/dist/lib/count-to.js.map +1 -0
- package/dist/lib/eye-morph.js +62 -0
- package/dist/lib/eye-morph.js.map +1 -0
- package/dist/lib/field-message.js +336 -0
- package/dist/lib/field-message.js.map +1 -0
- package/dist/lib/field-shell.js +81 -0
- package/dist/lib/field-shell.js.map +1 -0
- package/dist/lib/field-surface.js +4 -2
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flip.js +80 -0
- package/dist/lib/flip.js.map +1 -0
- package/dist/lib/height-follow.js +59 -0
- package/dist/lib/height-follow.js.map +1 -0
- package/dist/lib/icon-swap.js +47 -0
- package/dist/lib/icon-swap.js.map +1 -0
- package/dist/lib/leaving-list.js +57 -0
- package/dist/lib/leaving-list.js.map +1 -0
- package/dist/lib/merge-refs.js +3 -3
- package/dist/lib/merge-refs.js.map +1 -1
- package/dist/lib/morph-text.js +128 -0
- package/dist/lib/morph-text.js.map +1 -0
- package/dist/lib/motion.js +162 -0
- package/dist/lib/motion.js.map +1 -0
- package/dist/lib/pausable-timeout.js +67 -0
- package/dist/lib/pausable-timeout.js.map +1 -0
- package/dist/lib/rolling-value.js +31 -0
- package/dist/lib/rolling-value.js.map +1 -0
- package/dist/lib/spring-progress.js +114 -0
- package/dist/lib/spring-progress.js.map +1 -0
- package/dist/lib/svg-id.js +10 -0
- package/dist/lib/svg-id.js.map +1 -0
- package/dist/lib/swap-text.js +77 -0
- package/dist/lib/swap-text.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +36 -0
- package/dist/lib/use-copy-feedback.js.map +1 -0
- package/dist/page-header/index.d.ts +2 -0
- package/dist/page-header/index.js +2 -0
- package/dist/page-header/page-header.d.ts +75 -0
- package/dist/page-header/page-header.js +83 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/password-field/index.d.ts +2 -0
- package/dist/password-field/index.js +2 -0
- package/dist/password-field/password-field.d.ts +37 -0
- package/dist/password-field/password-field.js +101 -0
- package/dist/password-field/password-field.js.map +1 -0
- package/dist/password-strength/index.d.ts +2 -0
- package/dist/password-strength/index.js +2 -0
- package/dist/password-strength/password-strength.d.ts +127 -0
- package/dist/password-strength/password-strength.js +455 -0
- package/dist/password-strength/password-strength.js.map +1 -0
- package/dist/popover/popover.d.ts +8 -0
- package/dist/popover/popover.js +3 -3
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +46 -5
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.js +7 -6
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +10 -0
- package/dist/scroll-area/scroll-area.js +25 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/index.d.ts +2 -0
- package/dist/search-field/index.js +2 -0
- package/dist/search-field/search-field.d.ts +26 -0
- package/dist/search-field/search-field.js +155 -0
- package/dist/search-field/search-field.js.map +1 -0
- package/dist/select/select.js +79 -21
- package/dist/select/select.js.map +1 -1
- package/dist/sheet/sheet.js +2 -1
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/side-nav/index.d.ts +2 -0
- package/dist/side-nav/index.js +2 -0
- package/dist/side-nav/side-nav.d.ts +72 -0
- package/dist/side-nav/side-nav.js +142 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +8 -2
- package/dist/skeleton/skeleton.js +6 -2
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.js +61 -7
- package/dist/slider/slider.js.map +1 -1
- package/dist/split-button/index.d.ts +2 -0
- package/dist/split-button/index.js +2 -0
- package/dist/split-button/split-button.d.ts +46 -0
- package/dist/split-button/split-button.js +122 -0
- package/dist/split-button/split-button.js.map +1 -0
- package/dist/stat-card/index.d.ts +2 -0
- package/dist/stat-card/index.js +2 -0
- package/dist/stat-card/stat-card.d.ts +81 -0
- package/dist/stat-card/stat-card.js +152 -0
- package/dist/stat-card/stat-card.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/switch/switch.js +9 -8
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +36 -5
- package/dist/table/table.js +112 -11
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +142 -6
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +23 -0
- package/dist/textarea/textarea.js +24 -2
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/theme-switch/index.d.ts +2 -0
- package/dist/theme-switch/index.js +2 -0
- package/dist/theme-switch/theme-switch.d.ts +37 -0
- package/dist/theme-switch/theme-switch.js +123 -0
- package/dist/theme-switch/theme-switch.js.map +1 -0
- package/dist/time-picker/index.d.ts +2 -0
- package/dist/time-picker/index.js +2 -0
- package/dist/time-picker/time-picker.d.ts +54 -0
- package/dist/time-picker/time-picker.js +201 -0
- package/dist/time-picker/time-picker.js.map +1 -0
- package/dist/toast/toast.js +58 -38
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +1 -1
- package/dist/tooltip/tooltip.js +63 -14
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +83 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { SwapText } from "./swap-text.js";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region src/lib/rolling-value.tsx
|
|
4
|
+
/**
|
|
5
|
+
* The value shown in a picker's trigger. The caller's own text stays, visually
|
|
6
|
+
* hidden, as what a screen reader reads (`srValue`); the copy the eye sees rolls
|
|
7
|
+
* when it changes: a later value rises in from below, an earlier one drops in from
|
|
8
|
+
* above, and the old one lifts away. Nothing animates on first mount. Long text is
|
|
9
|
+
* cut with an ellipsis. `instanceKey` remounts the swap, which is how a caller whose
|
|
10
|
+
* first value arrives a render late makes that arrival the first mount.
|
|
11
|
+
*/
|
|
12
|
+
function RollingValue({ srValue, swapKey, dir, instanceKey, children }) {
|
|
13
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
14
|
+
className: "grid min-w-0 flex-1",
|
|
15
|
+
children: [srValue, /* @__PURE__ */ jsx("span", {
|
|
16
|
+
"aria-hidden": "true",
|
|
17
|
+
className: "min-w-0 overflow-hidden whitespace-nowrap",
|
|
18
|
+
children: /* @__PURE__ */ jsx(SwapText, {
|
|
19
|
+
swapKey,
|
|
20
|
+
dir,
|
|
21
|
+
motion: "roll",
|
|
22
|
+
className: "block [&>span:not([aria-hidden])]:max-w-full [&>span:not([aria-hidden])]:truncate",
|
|
23
|
+
children
|
|
24
|
+
}, instanceKey)
|
|
25
|
+
})]
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { RollingValue };
|
|
30
|
+
|
|
31
|
+
//# sourceMappingURL=rolling-value.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rolling-value.js","names":[],"sources":["../../src/lib/rolling-value.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nimport { SwapText } from \"./swap-text.js\";\n\n/**\n * The value shown in a picker's trigger. The caller's own text stays, visually\n * hidden, as what a screen reader reads (`srValue`); the copy the eye sees rolls\n * when it changes: a later value rises in from below, an earlier one drops in from\n * above, and the old one lifts away. Nothing animates on first mount. Long text is\n * cut with an ellipsis. `instanceKey` remounts the swap, which is how a caller whose\n * first value arrives a render late makes that arrival the first mount.\n */\nexport function RollingValue({\n srValue,\n swapKey,\n dir,\n instanceKey,\n children,\n}: {\n srValue: ReactNode;\n swapKey: string;\n dir: number;\n instanceKey?: string;\n children: ReactNode;\n}) {\n return (\n <span className=\"grid min-w-0 flex-1\">\n {srValue}\n <span aria-hidden=\"true\" className=\"min-w-0 overflow-hidden whitespace-nowrap\">\n <SwapText\n key={instanceKey}\n swapKey={swapKey}\n dir={dir}\n motion=\"roll\"\n className=\"block [&>span:not([aria-hidden])]:max-w-full [&>span:not([aria-hidden])]:truncate\"\n >\n {children}\n </SwapText>\n </span>\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,aAAa,EAC3B,SACA,SACA,KACA,aACA,YAOC;CACD,OACE,qBAAC,QAAD;EAAM,WAAU;EAAhB,UAAA,CACG,SACD,oBAAC,QAAD;GAAM,eAAY;GAAO,WAAU;GACjC,UAAA,oBAAC,UAAD;IAEW;IACJ;IACL,QAAO;IACP,WAAU;IAET;GACO,GAPH,WAOG;EACN,CAAA,CACF;;AAEV"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { prefersReducedMotion } from "./motion.js";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
//#region src/lib/spring-progress.ts
|
|
4
|
+
/**
|
|
5
|
+
* The morph spring (--ease-morph, --duration-morph: visualDuration 0.42s, bounce
|
|
6
|
+
* 0.16), sampled every 16ms from Motion's solver. It is here as numbers because a
|
|
7
|
+
* script has to evaluate it: CSS cannot interpolate the `d` of a path in every
|
|
8
|
+
* browser, so an icon that morphs its outline drives the path from this.
|
|
9
|
+
*/
|
|
10
|
+
const MORPH_CURVE = [
|
|
11
|
+
0,
|
|
12
|
+
.0178,
|
|
13
|
+
.0636,
|
|
14
|
+
.1279,
|
|
15
|
+
.2033,
|
|
16
|
+
.2841,
|
|
17
|
+
.366,
|
|
18
|
+
.4459,
|
|
19
|
+
.5218,
|
|
20
|
+
.5921,
|
|
21
|
+
.6562,
|
|
22
|
+
.7136,
|
|
23
|
+
.7643,
|
|
24
|
+
.8086,
|
|
25
|
+
.8466,
|
|
26
|
+
.879,
|
|
27
|
+
.9063,
|
|
28
|
+
.9289,
|
|
29
|
+
.9475,
|
|
30
|
+
.9626,
|
|
31
|
+
.9746,
|
|
32
|
+
.9841,
|
|
33
|
+
.9914,
|
|
34
|
+
.9969,
|
|
35
|
+
1.001,
|
|
36
|
+
1.0038,
|
|
37
|
+
1.0058,
|
|
38
|
+
1.0069,
|
|
39
|
+
1.0075,
|
|
40
|
+
1.0077,
|
|
41
|
+
1.0076,
|
|
42
|
+
1.0072,
|
|
43
|
+
1.0067,
|
|
44
|
+
1.0062,
|
|
45
|
+
1.0055,
|
|
46
|
+
1.0049,
|
|
47
|
+
1.0043,
|
|
48
|
+
1.0037,
|
|
49
|
+
1.0032,
|
|
50
|
+
1.0027,
|
|
51
|
+
1.0022,
|
|
52
|
+
1.0018,
|
|
53
|
+
1.0015,
|
|
54
|
+
1.0012,
|
|
55
|
+
1.0009,
|
|
56
|
+
1.0007,
|
|
57
|
+
1
|
|
58
|
+
];
|
|
59
|
+
const MORPH_MS = 736;
|
|
60
|
+
const ease = (t) => {
|
|
61
|
+
const at = t * MORPH_MS / 16;
|
|
62
|
+
const i = Math.min(Math.floor(at), MORPH_CURVE.length - 2);
|
|
63
|
+
const a = MORPH_CURVE[i];
|
|
64
|
+
return a + (MORPH_CURVE[i + 1] - a) * (at - i);
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Follows `target` (0 or 1) on the morph spring and reports progress to
|
|
68
|
+
* `apply` every frame, mutating the DOM directly rather than re-rendering.
|
|
69
|
+
* It is interruptible: a new target mid-flight starts from where the value is.
|
|
70
|
+
* The first render, and reduced motion, jump straight to the target.
|
|
71
|
+
*/
|
|
72
|
+
function useSpringProgress(target, apply) {
|
|
73
|
+
const current = useRef(target);
|
|
74
|
+
const frame = useRef(0);
|
|
75
|
+
const applyRef = useRef(apply);
|
|
76
|
+
const first = useRef(true);
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
applyRef.current = apply;
|
|
79
|
+
});
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
cancelAnimationFrame(frame.current);
|
|
82
|
+
if (first.current || prefersReducedMotion()) {
|
|
83
|
+
first.current = false;
|
|
84
|
+
current.current = target;
|
|
85
|
+
applyRef.current(target);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const from = current.current;
|
|
89
|
+
const start = performance.now();
|
|
90
|
+
const tick = (now) => {
|
|
91
|
+
const t = Math.min(Math.max((now - start) / MORPH_MS, 0), 1);
|
|
92
|
+
current.current = from + (target - from) * ease(t);
|
|
93
|
+
applyRef.current(current.current);
|
|
94
|
+
if (t < 1) frame.current = requestAnimationFrame(tick);
|
|
95
|
+
};
|
|
96
|
+
frame.current = requestAnimationFrame(tick);
|
|
97
|
+
return () => cancelAnimationFrame(frame.current);
|
|
98
|
+
}, [target]);
|
|
99
|
+
}
|
|
100
|
+
/** Interpolates the numbers of a path whose two states have the same commands. */
|
|
101
|
+
function lerpPath(from, to, progress) {
|
|
102
|
+
const a = from.match(/-?\d*\.?\d+/g).map(Number);
|
|
103
|
+
const b = to.match(/-?\d*\.?\d+/g).map(Number);
|
|
104
|
+
let i = 0;
|
|
105
|
+
return from.replace(/-?\d*\.?\d+/g, () => {
|
|
106
|
+
const n = a[i] + (b[i] - a[i]) * progress;
|
|
107
|
+
i += 1;
|
|
108
|
+
return String(Math.round(n * 1e3) / 1e3);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { lerpPath, useSpringProgress };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=spring-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spring-progress.js","names":[],"sources":["../../src/lib/spring-progress.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\nimport { prefersReducedMotion } from \"./motion.js\";\n\n/**\n * The morph spring (--ease-morph, --duration-morph: visualDuration 0.42s, bounce\n * 0.16), sampled every 16ms from Motion's solver. It is here as numbers because a\n * script has to evaluate it: CSS cannot interpolate the `d` of a path in every\n * browser, so an icon that morphs its outline drives the path from this.\n */\nconst MORPH_CURVE: readonly number[] = [\n 0, 0.0178, 0.0636, 0.1279, 0.2033, 0.2841, 0.366, 0.4459, 0.5218, 0.5921, 0.6562, 0.7136, 0.7643,\n 0.8086, 0.8466, 0.879, 0.9063, 0.9289, 0.9475, 0.9626, 0.9746, 0.9841, 0.9914, 0.9969, 1.001,\n 1.0038, 1.0058, 1.0069, 1.0075, 1.0077, 1.0076, 1.0072, 1.0067, 1.0062, 1.0055, 1.0049, 1.0043,\n 1.0037, 1.0032, 1.0027, 1.0022, 1.0018, 1.0015, 1.0012, 1.0009, 1.0007, 1,\n];\nconst MORPH_MS = 736;\n\nconst ease = (t: number) => {\n const at = (t * MORPH_MS) / 16;\n const i = Math.min(Math.floor(at), MORPH_CURVE.length - 2);\n const a = MORPH_CURVE[i]!;\n const b = MORPH_CURVE[i + 1]!;\n return a + (b - a) * (at - i);\n};\n\n/**\n * Follows `target` (0 or 1) on the morph spring and reports progress to\n * `apply` every frame, mutating the DOM directly rather than re-rendering.\n * It is interruptible: a new target mid-flight starts from where the value is.\n * The first render, and reduced motion, jump straight to the target.\n */\nexport function useSpringProgress(target: number, apply: (progress: number) => void) {\n const current = useRef(target);\n const frame = useRef(0);\n const applyRef = useRef(apply);\n const first = useRef(true);\n\n useEffect(() => {\n applyRef.current = apply;\n });\n\n useEffect(() => {\n cancelAnimationFrame(frame.current);\n if (first.current || prefersReducedMotion()) {\n first.current = false;\n current.current = target;\n applyRef.current(target);\n return;\n }\n const from = current.current;\n const start = performance.now();\n const tick = (now: number) => {\n // A frame's timestamp can precede `start`; a negative t would index the curve at -1.\n const t = Math.min(Math.max((now - start) / MORPH_MS, 0), 1);\n current.current = from + (target - from) * ease(t);\n applyRef.current(current.current);\n if (t < 1) frame.current = requestAnimationFrame(tick);\n };\n frame.current = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(frame.current);\n }, [target]);\n}\n\n/** Interpolates the numbers of a path whose two states have the same commands. */\nexport function lerpPath(from: string, to: string, progress: number) {\n const a = from.match(/-?\\d*\\.?\\d+/g)!.map(Number);\n const b = to.match(/-?\\d*\\.?\\d+/g)!.map(Number);\n let i = 0;\n return from.replace(/-?\\d*\\.?\\d+/g, () => {\n const n = a[i]! + (b[i]! - a[i]!) * progress;\n i += 1;\n return String(Math.round(n * 1000) / 1000);\n });\n}\n"],"mappings":";;;;;;;;;AAUA,MAAM,cAAiC;CACrC;CAAG;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAO;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAC1F;CAAQ;CAAQ;CAAO;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CACvF;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CACxF;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;CAAQ;AAC1E;AACA,MAAM,WAAW;AAEjB,MAAM,QAAQ,MAAc;CAC1B,MAAM,KAAM,IAAI,WAAY;CAC5B,MAAM,IAAI,KAAK,IAAI,KAAK,MAAM,EAAE,GAAG,YAAY,SAAS,CAAC;CACzD,MAAM,IAAI,YAAY;CAEtB,OAAO,KADG,YAAY,IAAI,KACV,MAAM,KAAK;AAC7B;;;;;;;AAQA,SAAgB,kBAAkB,QAAgB,OAAmC;CACnF,MAAM,UAAU,OAAO,MAAM;CAC7B,MAAM,QAAQ,OAAO,CAAC;CACtB,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,QAAQ,OAAO,IAAI;CAEzB,gBAAgB;EACd,SAAS,UAAU;CACrB,CAAC;CAED,gBAAgB;EACd,qBAAqB,MAAM,OAAO;EAClC,IAAI,MAAM,WAAW,qBAAqB,GAAG;GAC3C,MAAM,UAAU;GAChB,QAAQ,UAAU;GAClB,SAAS,QAAQ,MAAM;GACvB;EACF;EACA,MAAM,OAAO,QAAQ;EACrB,MAAM,QAAQ,YAAY,IAAI;EAC9B,MAAM,QAAQ,QAAgB;GAE5B,MAAM,IAAI,KAAK,IAAI,KAAK,KAAK,MAAM,SAAS,UAAU,CAAC,GAAG,CAAC;GAC3D,QAAQ,UAAU,QAAQ,SAAS,QAAQ,KAAK,CAAC;GACjD,SAAS,QAAQ,QAAQ,OAAO;GAChC,IAAI,IAAI,GAAG,MAAM,UAAU,sBAAsB,IAAI;EACvD;EACA,MAAM,UAAU,sBAAsB,IAAI;EAC1C,aAAa,qBAAqB,MAAM,OAAO;CACjD,GAAG,CAAC,MAAM,CAAC;AACb;;AAGA,SAAgB,SAAS,MAAc,IAAY,UAAkB;CACnE,MAAM,IAAI,KAAK,MAAM,cAAc,CAAC,CAAE,IAAI,MAAM;CAChD,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,CAAE,IAAI,MAAM;CAC9C,IAAI,IAAI;CACR,OAAO,KAAK,QAAQ,sBAAsB;EACxC,MAAM,IAAI,EAAE,MAAO,EAAE,KAAM,EAAE,MAAO;EACpC,KAAK;EACL,OAAO,OAAO,KAAK,MAAM,IAAI,GAAI,IAAI,GAAI;CAC3C,CAAC;AACH"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useId } from "react";
|
|
2
|
+
//#region src/lib/svg-id.ts
|
|
3
|
+
/** An id safe inside url(#...), for masks and clip paths: React's own ids hold characters (colons, underscores) that break the reference. */
|
|
4
|
+
function useSvgId() {
|
|
5
|
+
return useId().replace(/[^a-zA-Z0-9-]/g, "");
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { useSvgId };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=svg-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svg-id.js","names":[],"sources":["../../src/lib/svg-id.ts"],"sourcesContent":["import { useId } from \"react\";\n\n/** An id safe inside url(#...), for masks and clip paths: React's own ids hold characters (colons, underscores) that break the reference. */\nexport function useSvgId() {\n return useId().replace(/[^a-zA-Z0-9-]/g, \"\");\n}\n"],"mappings":";;;AAGA,SAAgB,WAAW;CACzB,OAAO,MAAM,CAAC,CAAC,QAAQ,kBAAkB,EAAE;AAC7C"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { cn } from "./cn.js";
|
|
2
|
+
import { useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from "./motion.js";
|
|
3
|
+
import { useCallback, useRef } from "react";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
//#region src/lib/swap-text.tsx
|
|
6
|
+
const LEAVING = {
|
|
7
|
+
rise: "absolute motion-safe:animate-field-swap-out motion-reduce:animate-fade-out",
|
|
8
|
+
roll: "absolute motion-safe:animate-field-swap-out motion-reduce:animate-fade-out",
|
|
9
|
+
slide: "absolute motion-safe:animate-calendar-title-out motion-reduce:animate-fade-out",
|
|
10
|
+
word: "absolute motion-safe:animate-range-word-out motion-reduce:animate-range-word-fade-out",
|
|
11
|
+
glyph: "absolute motion-safe:animate-glyph-swap-out motion-reduce:animate-fade-out",
|
|
12
|
+
dot: "absolute motion-safe:animate-avatar-dot-out motion-reduce:animate-fade-out"
|
|
13
|
+
};
|
|
14
|
+
const ARRIVING = {
|
|
15
|
+
rise: "inline-block motion-safe:animate-field-swap-in motion-reduce:animate-field-swap-fade-in",
|
|
16
|
+
roll: "inline-block motion-safe:animate-select-value-in motion-reduce:animate-fade-in",
|
|
17
|
+
slide: "inline-block motion-safe:animate-calendar-title-in motion-reduce:animate-fade-in",
|
|
18
|
+
word: "inline-block motion-safe:animate-range-word-in motion-reduce:animate-fade-in",
|
|
19
|
+
glyph: "inline-block motion-safe:animate-glyph-swap-in motion-reduce:animate-fade-in",
|
|
20
|
+
dot: "inline-block motion-safe:animate-avatar-dot-in motion-reduce:animate-fade-in"
|
|
21
|
+
};
|
|
22
|
+
/** A replaced piece of content on its way out, popped out of flow where it stood and lifting away. */
|
|
23
|
+
function Leaving({ id, empty, rect, motion, onDone, children }) {
|
|
24
|
+
const remove = useCallback(() => onDone(id), [onDone, id]);
|
|
25
|
+
useExitBackstop(true, remove);
|
|
26
|
+
useIsomorphicLayoutEffect(() => {
|
|
27
|
+
if (empty) remove();
|
|
28
|
+
}, [empty, remove]);
|
|
29
|
+
if (empty) return null;
|
|
30
|
+
return /* @__PURE__ */ jsx("span", {
|
|
31
|
+
"aria-hidden": "true",
|
|
32
|
+
"data-leaving": "",
|
|
33
|
+
onAnimationEnd: (event) => {
|
|
34
|
+
if (event.target === event.currentTarget) remove();
|
|
35
|
+
},
|
|
36
|
+
style: rect,
|
|
37
|
+
className: LEAVING[motion],
|
|
38
|
+
children
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Content that is replaced as a whole when `swapKey` changes: the new content
|
|
43
|
+
* rises in from `dir` (1 from below, -1 from above; one variable shared with the
|
|
44
|
+
* leaving content so both follow the newest direction) while the old content,
|
|
45
|
+
* kept where it stood, lifts away the other way. An empty `swapKey` shows nothing
|
|
46
|
+
* (and the content before it leaves). Nothing animates on first mount, and
|
|
47
|
+
* reduced motion fades instead of moving. This is for the eye only: a screen
|
|
48
|
+
* reader reads the caller's own copy. The wrapper is `position: relative`, which
|
|
49
|
+
* the popped-out content is placed against.
|
|
50
|
+
*/
|
|
51
|
+
function SwapText({ swapKey, dir = 1, motion = "rise", className, children }) {
|
|
52
|
+
const ref = useRef(null);
|
|
53
|
+
const { leaving, changed, remove } = useLeavingItems(swapKey, {
|
|
54
|
+
key: swapKey,
|
|
55
|
+
node: children
|
|
56
|
+
}, () => ref.current);
|
|
57
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
58
|
+
style: { "--dir": dir },
|
|
59
|
+
className: cn("relative", className),
|
|
60
|
+
children: [leaving.map((item) => /* @__PURE__ */ jsx(Leaving, {
|
|
61
|
+
id: item.id,
|
|
62
|
+
empty: item.value.key === "",
|
|
63
|
+
rect: item.rect,
|
|
64
|
+
motion,
|
|
65
|
+
onDone: remove,
|
|
66
|
+
children: item.value.node
|
|
67
|
+
}, item.id)), swapKey ? /* @__PURE__ */ jsx("span", {
|
|
68
|
+
ref,
|
|
69
|
+
className: changed ? ARRIVING[motion] : "inline-block",
|
|
70
|
+
children
|
|
71
|
+
}, swapKey) : null]
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
export { SwapText };
|
|
76
|
+
|
|
77
|
+
//# sourceMappingURL=swap-text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swap-text.js","names":[],"sources":["../../src/lib/swap-text.tsx"],"sourcesContent":["import { useCallback, useRef, type CSSProperties, type ReactNode } from \"react\";\n\nimport { cn } from \"./cn.js\";\nimport { useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from \"./motion.js\";\nimport type { Rect } from \"./motion.js\";\n\n/**\n * How replaced content moves. `rise`: up or down a little (a field message). `roll`:\n * a little further, and a 120ms fade under reduced motion (the value a picker shows).\n * `slide`: sideways (a calendar's month title). `word`: a word of a rolling label.\n * `glyph`: an icon, which grows in from small and blurred. `dot`: a presence dot.\n */\nexport type SwapMotion = \"rise\" | \"roll\" | \"slide\" | \"word\" | \"glyph\" | \"dot\";\n\n// Static strings: Tailwind scans source text, so a class built at runtime would never be emitted.\nconst LEAVING: Record<SwapMotion, string> = {\n rise: \"absolute motion-safe:animate-field-swap-out motion-reduce:animate-fade-out\",\n roll: \"absolute motion-safe:animate-field-swap-out motion-reduce:animate-fade-out\",\n slide: \"absolute motion-safe:animate-calendar-title-out motion-reduce:animate-fade-out\",\n word: \"absolute motion-safe:animate-range-word-out motion-reduce:animate-range-word-fade-out\",\n glyph: \"absolute motion-safe:animate-glyph-swap-out motion-reduce:animate-fade-out\",\n dot: \"absolute motion-safe:animate-avatar-dot-out motion-reduce:animate-fade-out\",\n};\n\nconst ARRIVING: Record<SwapMotion, string> = {\n rise: \"inline-block motion-safe:animate-field-swap-in motion-reduce:animate-field-swap-fade-in\",\n roll: \"inline-block motion-safe:animate-select-value-in motion-reduce:animate-fade-in\",\n slide: \"inline-block motion-safe:animate-calendar-title-in motion-reduce:animate-fade-in\",\n word: \"inline-block motion-safe:animate-range-word-in motion-reduce:animate-fade-in\",\n glyph: \"inline-block motion-safe:animate-glyph-swap-in motion-reduce:animate-fade-in\",\n dot: \"inline-block motion-safe:animate-avatar-dot-in motion-reduce:animate-fade-in\",\n};\n\n/** A replaced piece of content on its way out, popped out of flow where it stood and lifting away. */\nfunction Leaving({\n id,\n empty,\n rect,\n motion,\n onDone,\n children,\n}: {\n id: number;\n empty: boolean;\n rect: Rect;\n motion: SwapMotion;\n onDone: (id: number) => void;\n children: ReactNode;\n}) {\n const remove = useCallback(() => onDone(id), [onDone, id]);\n useExitBackstop(true, remove);\n // The empty value of \"nothing shown\" has nothing to lift away.\n useIsomorphicLayoutEffect(() => {\n if (empty) remove();\n }, [empty, remove]);\n if (empty) return null;\n return (\n <span\n aria-hidden=\"true\"\n data-leaving=\"\"\n onAnimationEnd={(event) => {\n if (event.target === event.currentTarget) remove();\n }}\n style={rect}\n className={LEAVING[motion]}\n >\n {children}\n </span>\n );\n}\n\n/**\n * Content that is replaced as a whole when `swapKey` changes: the new content\n * rises in from `dir` (1 from below, -1 from above; one variable shared with the\n * leaving content so both follow the newest direction) while the old content,\n * kept where it stood, lifts away the other way. An empty `swapKey` shows nothing\n * (and the content before it leaves). Nothing animates on first mount, and\n * reduced motion fades instead of moving. This is for the eye only: a screen\n * reader reads the caller's own copy. The wrapper is `position: relative`, which\n * the popped-out content is placed against.\n */\nexport function SwapText({\n swapKey,\n dir = 1,\n motion = \"rise\",\n className,\n children,\n}: {\n swapKey: string;\n dir?: number;\n /** How it moves. `dir` is 1 from below or forward, -1 from above or back. @default \"rise\" */\n motion?: SwapMotion;\n className?: string;\n children: ReactNode;\n}) {\n const ref = useRef<HTMLSpanElement>(null);\n const { leaving, changed, remove } = useLeavingItems(\n swapKey,\n { key: swapKey, node: children },\n () => ref.current,\n );\n return (\n <span style={{ \"--dir\": dir } as CSSProperties} className={cn(\"relative\", className)}>\n {leaving.map((item) => (\n <Leaving\n key={item.id}\n id={item.id}\n empty={item.value.key === \"\"}\n rect={item.rect}\n motion={motion}\n onDone={remove}\n >\n {item.value.node}\n </Leaving>\n ))}\n {swapKey ? (\n <span\n key={swapKey}\n ref={ref}\n className={changed ? ARRIVING[motion] : \"inline-block\"}\n >\n {children}\n </span>\n ) : null}\n </span>\n );\n}\n"],"mappings":";;;;;AAeA,MAAM,UAAsC;CAC1C,MAAM;CACN,MAAM;CACN,OAAO;CACP,MAAM;CACN,OAAO;CACP,KAAK;AACP;AAEA,MAAM,WAAuC;CAC3C,MAAM;CACN,MAAM;CACN,OAAO;CACP,MAAM;CACN,OAAO;CACP,KAAK;AACP;;AAGA,SAAS,QAAQ,EACf,IACA,OACA,MACA,QACA,QACA,YAQC;CACD,MAAM,SAAS,kBAAkB,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC;CACzD,gBAAgB,MAAM,MAAM;CAE5B,gCAAgC;EAC9B,IAAI,OAAO,OAAO;CACpB,GAAG,CAAC,OAAO,MAAM,CAAC;CAClB,IAAI,OAAO,OAAO;CAClB,OACE,oBAAC,QAAD;EACE,eAAY;EACZ,gBAAa;EACb,iBAAiB,UAAU;GACzB,IAAI,MAAM,WAAW,MAAM,eAAe,OAAO;EACnD;EACA,OAAO;EACP,WAAW,QAAQ;EAElB;CACG,CAAA;AAEV;;;;;;;;;;;AAYA,SAAgB,SAAS,EACvB,SACA,MAAM,GACN,SAAS,QACT,WACA,YAQC;CACD,MAAM,MAAM,OAAwB,IAAI;CACxC,MAAM,EAAE,SAAS,SAAS,WAAW,gBACnC,SACA;EAAE,KAAK;EAAS,MAAM;CAAS,SACzB,IAAI,OACZ;CACA,OACE,qBAAC,QAAD;EAAM,OAAO,EAAE,SAAS,IAAI;EAAoB,WAAW,GAAG,YAAY,SAAS;EAAnF,UAAA,CACG,QAAQ,KAAK,SACZ,oBAAC,SAAD;GAEE,IAAI,KAAK;GACT,OAAO,KAAK,MAAM,QAAQ;GAC1B,MAAM,KAAK;GACH;GACR,QAAQ;GAEP,UAAA,KAAK,MAAM;EACL,GARF,KAAK,EAQH,CACV,GACA,UACC,oBAAC,QAAD;GAEO;GACL,WAAW,UAAU,SAAS,UAAU;GAEvC;EACG,GALC,OAKD,IACJ,IACA;;AAEV"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
//#region src/lib/use-copy-feedback.ts
|
|
3
|
+
/** Clipboard state for actions that render their own button: copy, show copied or failed for a while, then go back to idle. */
|
|
4
|
+
function useCopyFeedback(duration = 1900) {
|
|
5
|
+
const [state, setState] = useState("idle");
|
|
6
|
+
const timeout = useRef(null);
|
|
7
|
+
const reset = useCallback(() => {
|
|
8
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
9
|
+
timeout.current = null;
|
|
10
|
+
setState("idle");
|
|
11
|
+
}, []);
|
|
12
|
+
useEffect(() => () => {
|
|
13
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
14
|
+
}, []);
|
|
15
|
+
return {
|
|
16
|
+
state,
|
|
17
|
+
copy: useCallback(async (value) => {
|
|
18
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
19
|
+
try {
|
|
20
|
+
await navigator.clipboard.writeText(value);
|
|
21
|
+
setState("copied");
|
|
22
|
+
timeout.current = setTimeout(reset, duration);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
setState("error");
|
|
26
|
+
timeout.current = setTimeout(reset, duration);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}, [duration, reset]),
|
|
30
|
+
reset
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { useCopyFeedback };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=use-copy-feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-copy-feedback.js","names":[],"sources":["../../src/lib/use-copy-feedback.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type CopyFeedbackState = \"idle\" | \"copied\" | \"error\";\n\n/** Clipboard state for actions that render their own button: copy, show copied or failed for a while, then go back to idle. */\nexport function useCopyFeedback(duration = 1900) {\n const [state, setState] = useState<CopyFeedbackState>(\"idle\");\n const timeout = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const reset = useCallback(() => {\n if (timeout.current) clearTimeout(timeout.current);\n timeout.current = null;\n setState(\"idle\");\n }, []);\n\n useEffect(\n () => () => {\n if (timeout.current) clearTimeout(timeout.current);\n },\n [],\n );\n\n /** Resolves true if the text reached the clipboard. */\n const copy = useCallback(\n async (value: string) => {\n if (timeout.current) clearTimeout(timeout.current);\n try {\n await navigator.clipboard.writeText(value);\n setState(\"copied\");\n timeout.current = setTimeout(reset, duration);\n return true;\n } catch {\n setState(\"error\");\n timeout.current = setTimeout(reset, duration);\n return false;\n }\n },\n [duration, reset],\n );\n\n return { state, copy, reset };\n}\n"],"mappings":";;;AAKA,SAAgB,gBAAgB,WAAW,MAAM;CAC/C,MAAM,CAAC,OAAO,YAAY,SAA4B,MAAM;CAC5D,MAAM,UAAU,OAA6C,IAAI;CAEjE,MAAM,QAAQ,kBAAkB;EAC9B,IAAI,QAAQ,SAAS,aAAa,QAAQ,OAAO;EACjD,QAAQ,UAAU;EAClB,SAAS,MAAM;CACjB,GAAG,CAAC,CAAC;CAEL,sBACc;EACV,IAAI,QAAQ,SAAS,aAAa,QAAQ,OAAO;CACnD,GACA,CAAC,CACH;CAoBA,OAAO;EAAE;EAAO,MAjBH,YACX,OAAO,UAAkB;GACvB,IAAI,QAAQ,SAAS,aAAa,QAAQ,OAAO;GACjD,IAAI;IACF,MAAM,UAAU,UAAU,UAAU,KAAK;IACzC,SAAS,QAAQ;IACjB,QAAQ,UAAU,WAAW,OAAO,QAAQ;IAC5C,OAAO;GACT,QAAQ;IACN,SAAS,OAAO;IAChB,QAAQ,UAAU,WAAW,OAAO,QAAQ;IAC5C,OAAO;GACT;EACF,GACA,CAAC,UAAU,KAAK,CAGC;EAAG;CAAM;AAC9B"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { PageHeader, PageHeaderProps, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants } from "./page-header.js";
|
|
2
|
+
export { PageHeader, type PageHeaderProps, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { PageHeader, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants } from "./page-header.js";
|
|
2
|
+
export { PageHeader, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
//#region src/page-header/page-header.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* PageHeader: the top of a page. One title, an optional line saying what the
|
|
5
|
+
* page is for, the page's own actions, a short piece of metadata and an
|
|
6
|
+
* optional way back.
|
|
7
|
+
*
|
|
8
|
+
* Presentational, so there is no React Aria base. It exists because a page
|
|
9
|
+
* title is the one element every screen has, and without a shared component
|
|
10
|
+
* each screen picks its own size, weight, spacing and action placement, so the
|
|
11
|
+
* title moves as a person moves between pages.
|
|
12
|
+
*
|
|
13
|
+
* The title renders the page's single `<h1>`. A page has one, and putting it
|
|
14
|
+
* here means a consumer cannot render two by accident.
|
|
15
|
+
*
|
|
16
|
+
* Every string in the cva blocks below is a STATIC literal. Tailwind scans
|
|
17
|
+
* source text, so a class assembled at runtime is invisible to it and would
|
|
18
|
+
* simply never be emitted.
|
|
19
|
+
*/
|
|
20
|
+
export declare const pageHeaderVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Title block and actions share one row. `items-start` keeps the actions level
|
|
23
|
+
* with the title when a description wraps under it; `flex-wrap` moves the
|
|
24
|
+
* actions below the title on a narrow screen instead of squeezing it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const pageHeaderRowVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
27
|
+
export declare const pageHeaderTitleVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
28
|
+
export declare const pageHeaderMetaVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
29
|
+
/**
|
|
30
|
+
* Capped at the md breakpoint (48rem), a readable measure for a sentence or
|
|
31
|
+
* two. The scale defines no container size between `max` and nothing, and a
|
|
32
|
+
* bracket length is rejected by check:hardcoding, so the token is referenced.
|
|
33
|
+
*/
|
|
34
|
+
export declare const pageHeaderDescriptionVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
35
|
+
export declare const pageHeaderActionsVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
36
|
+
export declare const pageHeaderBackVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
37
|
+
/**
|
|
38
|
+
* The header at the top of a page.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* <PageHeader
|
|
42
|
+
* title="Projects"
|
|
43
|
+
* description="Everything your team is working on."
|
|
44
|
+
* meta="updated 14 Oct 2026"
|
|
45
|
+
* actions={<Button>New project</Button>}
|
|
46
|
+
* />
|
|
47
|
+
*/
|
|
48
|
+
export interface PageHeaderProps extends Omit<ComponentPropsWithoutRef<"header">, "title"> {
|
|
49
|
+
/** The page's title. Rendered as the page's single `<h1>`. */
|
|
50
|
+
title: ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* One or two sentences under the title saying what the page is for.
|
|
53
|
+
* Muted, and capped at a readable measure.
|
|
54
|
+
*/
|
|
55
|
+
description?: ReactNode;
|
|
56
|
+
/**
|
|
57
|
+
* The page's primary actions, right-aligned on the title row. They wrap
|
|
58
|
+
* below the title on a narrow screen. Pass buttons or links; the header
|
|
59
|
+
* spaces them and does not style them.
|
|
60
|
+
*/
|
|
61
|
+
actions?: ReactNode;
|
|
62
|
+
/**
|
|
63
|
+
* A short piece of context inline after the title, on the same baseline:
|
|
64
|
+
* a date the figures are as of, a status chip, a count.
|
|
65
|
+
*/
|
|
66
|
+
meta?: ReactNode;
|
|
67
|
+
/**
|
|
68
|
+
* A slot above the title for a way back, on a detail page. Supply the link
|
|
69
|
+
* yourself so it uses your router; the header sizes and colours the slot.
|
|
70
|
+
*/
|
|
71
|
+
back?: ReactNode;
|
|
72
|
+
}
|
|
73
|
+
export declare const PageHeader: import("react").ForwardRefExoticComponent<PageHeaderProps & import("react").RefAttributes<HTMLElement>>;
|
|
74
|
+
//#endregion
|
|
75
|
+
//# sourceMappingURL=page-header.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { cva } from "class-variance-authority";
|
|
4
|
+
import { forwardRef } from "react";
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
//#region src/page-header/page-header.tsx
|
|
7
|
+
/**
|
|
8
|
+
* PageHeader: the top of a page. One title, an optional line saying what the
|
|
9
|
+
* page is for, the page's own actions, a short piece of metadata and an
|
|
10
|
+
* optional way back.
|
|
11
|
+
*
|
|
12
|
+
* Presentational, so there is no React Aria base. It exists because a page
|
|
13
|
+
* title is the one element every screen has, and without a shared component
|
|
14
|
+
* each screen picks its own size, weight, spacing and action placement, so the
|
|
15
|
+
* title moves as a person moves between pages.
|
|
16
|
+
*
|
|
17
|
+
* The title renders the page's single `<h1>`. A page has one, and putting it
|
|
18
|
+
* here means a consumer cannot render two by accident.
|
|
19
|
+
*
|
|
20
|
+
* Every string in the cva blocks below is a STATIC literal. Tailwind scans
|
|
21
|
+
* source text, so a class assembled at runtime is invisible to it and would
|
|
22
|
+
* simply never be emitted.
|
|
23
|
+
*/
|
|
24
|
+
const pageHeaderVariants = cva(["mb-6 block", "box-border font-sans text-foreground"]);
|
|
25
|
+
/**
|
|
26
|
+
* Title block and actions share one row. `items-start` keeps the actions level
|
|
27
|
+
* with the title when a description wraps under it; `flex-wrap` moves the
|
|
28
|
+
* actions below the title on a narrow screen instead of squeezing it.
|
|
29
|
+
*/
|
|
30
|
+
const pageHeaderRowVariants = cva("flex flex-wrap items-start justify-between gap-3");
|
|
31
|
+
const pageHeaderTitleVariants = cva(["font-display text-h3 leading-(--leading-snug) font-semibold", "m-0"]);
|
|
32
|
+
const pageHeaderMetaVariants = cva("text-xs leading-(--leading-normal) text-muted-foreground");
|
|
33
|
+
/**
|
|
34
|
+
* Capped at the md breakpoint (48rem), a readable measure for a sentence or
|
|
35
|
+
* two. The scale defines no container size between `max` and nothing, and a
|
|
36
|
+
* bracket length is rejected by check:hardcoding, so the token is referenced.
|
|
37
|
+
*/
|
|
38
|
+
const pageHeaderDescriptionVariants = cva(["mt-1 mb-0 max-w-(--breakpoint-md)", "text-sm leading-(--leading-normal) text-muted-foreground"]);
|
|
39
|
+
const pageHeaderActionsVariants = cva("flex shrink-0 flex-wrap items-center gap-2");
|
|
40
|
+
const pageHeaderBackVariants = cva("mb-2 text-xs leading-(--leading-normal) text-muted-foreground");
|
|
41
|
+
const PageHeader = forwardRef(function PageHeader({ className, title, description, actions, meta, back, ...props }, ref) {
|
|
42
|
+
return /* @__PURE__ */ jsxs("header", {
|
|
43
|
+
ref,
|
|
44
|
+
...props,
|
|
45
|
+
"data-slot": "page-header",
|
|
46
|
+
className: cn(pageHeaderVariants(), className),
|
|
47
|
+
children: [back === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
48
|
+
"data-slot": "page-header-back",
|
|
49
|
+
className: pageHeaderBackVariants(),
|
|
50
|
+
children: back
|
|
51
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
52
|
+
"data-slot": "page-header-row",
|
|
53
|
+
className: pageHeaderRowVariants(),
|
|
54
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
55
|
+
className: "min-w-0 grow basis-64",
|
|
56
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
57
|
+
className: "flex flex-wrap items-baseline gap-x-3 gap-y-1",
|
|
58
|
+
children: [/* @__PURE__ */ jsx("h1", {
|
|
59
|
+
"data-slot": "page-header-title",
|
|
60
|
+
className: pageHeaderTitleVariants(),
|
|
61
|
+
children: title
|
|
62
|
+
}), meta === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
63
|
+
"data-slot": "page-header-meta",
|
|
64
|
+
className: pageHeaderMetaVariants(),
|
|
65
|
+
children: meta
|
|
66
|
+
})]
|
|
67
|
+
}), description === void 0 ? null : /* @__PURE__ */ jsx("p", {
|
|
68
|
+
"data-slot": "page-header-description",
|
|
69
|
+
className: pageHeaderDescriptionVariants(),
|
|
70
|
+
children: description
|
|
71
|
+
})]
|
|
72
|
+
}), actions === void 0 ? null : /* @__PURE__ */ jsx("div", {
|
|
73
|
+
"data-slot": "page-header-actions",
|
|
74
|
+
className: pageHeaderActionsVariants(),
|
|
75
|
+
children: actions
|
|
76
|
+
})]
|
|
77
|
+
})]
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//#endregion
|
|
81
|
+
export { PageHeader, pageHeaderActionsVariants, pageHeaderBackVariants, pageHeaderDescriptionVariants, pageHeaderMetaVariants, pageHeaderRowVariants, pageHeaderTitleVariants, pageHeaderVariants };
|
|
82
|
+
|
|
83
|
+
//# sourceMappingURL=page-header.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-header.js","names":[],"sources":["../../src/page-header/page-header.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * PageHeader: the top of a page. One title, an optional line saying what the\n * page is for, the page's own actions, a short piece of metadata and an\n * optional way back.\n *\n * Presentational, so there is no React Aria base. It exists because a page\n * title is the one element every screen has, and without a shared component\n * each screen picks its own size, weight, spacing and action placement, so the\n * title moves as a person moves between pages.\n *\n * The title renders the page's single `<h1>`. A page has one, and putting it\n * here means a consumer cannot render two by accident.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted.\n */\nexport const pageHeaderVariants = cva([\n \"mb-6 block\",\n // box-border and font-sans are not boilerplate: the reset in\n // @seekalfred/styles is opt-in, so a consumer may load none, and an\n // unstyled document renders text in a serif.\n \"box-border font-sans text-foreground\",\n]);\n\n/**\n * Title block and actions share one row. `items-start` keeps the actions level\n * with the title when a description wraps under it; `flex-wrap` moves the\n * actions below the title on a narrow screen instead of squeezing it.\n */\nexport const pageHeaderRowVariants = cva(\"flex flex-wrap items-start justify-between gap-3\");\n\nexport const pageHeaderTitleVariants = cva([\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"font-display text-h3 leading-(--leading-snug) font-semibold\",\n // The user-agent <h1> ships a 0.67em block margin and font-size 2em.\n \"m-0\",\n]);\n\nexport const pageHeaderMetaVariants = cva(\n \"text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\n/**\n * Capped at the md breakpoint (48rem), a readable measure for a sentence or\n * two. The scale defines no container size between `max` and nothing, and a\n * bracket length is rejected by check:hardcoding, so the token is referenced.\n */\nexport const pageHeaderDescriptionVariants = cva([\n \"mt-1 mb-0 max-w-(--breakpoint-md)\",\n \"text-sm leading-(--leading-normal) text-muted-foreground\",\n]);\n\nexport const pageHeaderActionsVariants = cva(\"flex shrink-0 flex-wrap items-center gap-2\");\n\nexport const pageHeaderBackVariants = cva(\n \"mb-2 text-xs leading-(--leading-normal) text-muted-foreground\",\n);\n\n/**\n * The header at the top of a page.\n *\n * @example\n * <PageHeader\n * title=\"Projects\"\n * description=\"Everything your team is working on.\"\n * meta=\"updated 14 Oct 2026\"\n * actions={<Button>New project</Button>}\n * />\n */\nexport interface PageHeaderProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. A tooltip on a page header is not keyboard\n // reachable, and silently shadowing it with a ReactNode would be a type\n // error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"header\">, \"title\"> {\n /** The page's title. Rendered as the page's single `<h1>`. */\n title: ReactNode;\n /**\n * One or two sentences under the title saying what the page is for.\n * Muted, and capped at a readable measure.\n */\n description?: ReactNode;\n /**\n * The page's primary actions, right-aligned on the title row. They wrap\n * below the title on a narrow screen. Pass buttons or links; the header\n * spaces them and does not style them.\n */\n actions?: ReactNode;\n /**\n * A short piece of context inline after the title, on the same baseline:\n * a date the figures are as of, a status chip, a count.\n */\n meta?: ReactNode;\n /**\n * A slot above the title for a way back, on a detail page. Supply the link\n * yourself so it uses your router; the header sizes and colours the slot.\n */\n back?: ReactNode;\n}\n\nexport const PageHeader = forwardRef<HTMLElement, PageHeaderProps>(function PageHeader(\n { className, title, description, actions, meta, back, ...props },\n ref,\n) {\n return (\n <header\n ref={ref}\n {...props}\n data-slot=\"page-header\"\n className={cn(pageHeaderVariants(), className)}\n >\n {back === undefined ? null : (\n <div data-slot=\"page-header-back\" className={pageHeaderBackVariants()}>\n {back}\n </div>\n )}\n <div data-slot=\"page-header-row\" className={pageHeaderRowVariants()}>\n {/*\n * grow from a 16rem basis, not flex-1. flex-1 is a 0% basis, so the\n * row never wraps: the actions keep their width and the title is\n * crushed into a one-word column beside them on a phone. A real basis\n * is the width the title needs before the actions move below it.\n */}\n <div className=\"min-w-0 grow basis-64\">\n <div className=\"flex flex-wrap items-baseline gap-x-3 gap-y-1\">\n <h1 data-slot=\"page-header-title\" className={pageHeaderTitleVariants()}>\n {title}\n </h1>\n {meta === undefined ? null : (\n <div data-slot=\"page-header-meta\" className={pageHeaderMetaVariants()}>\n {meta}\n </div>\n )}\n </div>\n {description === undefined ? null : (\n <p data-slot=\"page-header-description\" className={pageHeaderDescriptionVariants()}>\n {description}\n </p>\n )}\n </div>\n {actions === undefined ? null : (\n <div data-slot=\"page-header-actions\" className={pageHeaderActionsVariants()}>\n {actions}\n </div>\n )}\n </div>\n </header>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwBA,MAAa,qBAAqB,IAAI,CACpC,cAIA,sCACF,CAAC;;;;;;AAOD,MAAa,wBAAwB,IAAI,kDAAkD;AAE3F,MAAa,0BAA0B,IAAI,CAGzC,+DAEA,KACF,CAAC;AAED,MAAa,yBAAyB,IACpC,0DACF;;;;;;AAOA,MAAa,gCAAgC,IAAI,CAC/C,qCACA,0DACF,CAAC;AAED,MAAa,4BAA4B,IAAI,4CAA4C;AAEzF,MAAa,yBAAyB,IACpC,+DACF;AA4CA,MAAa,aAAa,WAAyC,SAAS,WAC1E,EAAE,WAAW,OAAO,aAAa,SAAS,MAAM,MAAM,GAAG,SACzD,KACA;CACA,OACE,qBAAC,UAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAJ/C,UAAA,CAMG,SAAS,KAAA,IAAY,OACpB,oBAAC,OAAD;GAAK,aAAU;GAAmB,WAAW,uBAAuB;GACjE,UAAA;EACE,CAAA,GAEP,qBAAC,OAAD;GAAK,aAAU;GAAkB,WAAW,sBAAsB;GAAlE,UAAA,CAOE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,MAAD;MAAI,aAAU;MAAoB,WAAW,wBAAwB;MAClE,UAAA;KACC,CAAA,GACH,SAAS,KAAA,IAAY,OACpB,oBAAC,OAAD;MAAK,aAAU;MAAmB,WAAW,uBAAuB;MACjE,UAAA;KACE,CAAA,CAEJ;IACJ,CAAA,GAAA,gBAAgB,KAAA,IAAY,OAC3B,oBAAC,KAAD;KAAG,aAAU;KAA0B,WAAW,8BAA8B;KAC7E,UAAA;IACA,CAAA,CAEF;GACJ,CAAA,GAAA,YAAY,KAAA,IAAY,OACvB,oBAAC,OAAD;IAAK,aAAU;IAAsB,WAAW,0BAA0B;IACvE,UAAA;GACE,CAAA,CAEJ;EACC,CAAA,CAAA;;AAEZ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { InputProps } from "react-aria-components";
|
|
2
|
+
//#region src/password-field/password-field.d.ts
|
|
3
|
+
/** The toggle's accessible names. Both have English defaults; pass the ones you translate. */
|
|
4
|
+
export interface PasswordFieldMessages {
|
|
5
|
+
showPassword: string;
|
|
6
|
+
hidePassword: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const defaultPasswordFieldMessages: PasswordFieldMessages;
|
|
9
|
+
export interface PasswordFieldProps extends Omit<InputProps, "className" | "style" | "size" | "type"> {
|
|
10
|
+
/** Extra classes for the input. Merged last, so they win conflicts with ours. */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** The visible label, tied to the input with `htmlFor`. */
|
|
13
|
+
label: string;
|
|
14
|
+
/**
|
|
15
|
+
* Helper text under the field, named by `aria-describedby`. A string, because
|
|
16
|
+
* the copy is animated word by word: it opens on a spring when it appears
|
|
17
|
+
* after mount and changed words rise in. Nothing animates on first render.
|
|
18
|
+
*/
|
|
19
|
+
description?: string;
|
|
20
|
+
/** Translated or reworded toggle names. */
|
|
21
|
+
messages?: Partial<PasswordFieldMessages>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A password input with a show and hide toggle. Switching resolves the text in
|
|
25
|
+
* place from a soft blur, and the eye is one outline that a slash draws across.
|
|
26
|
+
* Native input props apply to the input; `disabled` also disables the toggle,
|
|
27
|
+
* and `aria-invalid` gives the box its invalid border.
|
|
28
|
+
*
|
|
29
|
+
* The toggle's name changes with the state and it is also `aria-pressed`, so a
|
|
30
|
+
* screen reader may announce both ("Hide password, pressed").
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* <PasswordField label="Password" description="At least eight characters." />
|
|
34
|
+
*/
|
|
35
|
+
export declare const PasswordField: import("react").ForwardRefExoticComponent<PasswordFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
36
|
+
//#endregion
|
|
37
|
+
//# sourceMappingURL=password-field.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
4
|
+
import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
|
|
5
|
+
import { EyeMorph } from "../lib/eye-morph.js";
|
|
6
|
+
import { forwardRef, useState } from "react";
|
|
7
|
+
import { Button, Group, Input } from "react-aria-components";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
//#region src/password-field/password-field.tsx
|
|
10
|
+
const defaultPasswordFieldMessages = {
|
|
11
|
+
showPassword: "Show password",
|
|
12
|
+
hidePassword: "Hide password"
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The field's box. A React Aria `Group` rather than the input, because the
|
|
16
|
+
* toggle sits inside the same border: it carries the shared field surface (the
|
|
17
|
+
* 160ms border transition, the darker border on hover and focus, the muted
|
|
18
|
+
* disabled look, the invalid border) and the input inside is borderless.
|
|
19
|
+
*
|
|
20
|
+
* `data-focus-within` darkens the border too. A Group reports focus-visible
|
|
21
|
+
* from the input modality, so a mouse click into the input, or onto the toggle,
|
|
22
|
+
* would otherwise leave the border pale; a plain Input is different because
|
|
23
|
+
* React Aria treats a text input as always keyboard-focused.
|
|
24
|
+
*/
|
|
25
|
+
const shellClass = cn(fieldSurfaceVariants(), "flex items-center h-(--density-control-h-md) ps-(--density-control-pad-md) field-toggle-shell [&:where([data-focus-within])]:border-foreground");
|
|
26
|
+
/** The input inside the box: no border, no fill, no outline of its own. */
|
|
27
|
+
const inputClass = "min-w-0 flex-1 border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed motion-safe:data-[reveal=shown]:animate-field-reveal-shown motion-safe:data-[reveal=hidden]:animate-field-reveal-hidden";
|
|
28
|
+
const toggleClass = "field-toggle-button grid size-7.5 flex-none cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground aria-pressed:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed";
|
|
29
|
+
/**
|
|
30
|
+
* A password input with a show and hide toggle. Switching resolves the text in
|
|
31
|
+
* place from a soft blur, and the eye is one outline that a slash draws across.
|
|
32
|
+
* Native input props apply to the input; `disabled` also disables the toggle,
|
|
33
|
+
* and `aria-invalid` gives the box its invalid border.
|
|
34
|
+
*
|
|
35
|
+
* The toggle's name changes with the state and it is also `aria-pressed`, so a
|
|
36
|
+
* screen reader may announce both ("Hide password, pressed").
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* <PasswordField label="Password" description="At least eight characters." />
|
|
40
|
+
*/
|
|
41
|
+
const PasswordField = forwardRef(function PasswordField({ label, description, messages: messagesProp, className, ...props }, ref) {
|
|
42
|
+
const messages = {
|
|
43
|
+
...defaultPasswordFieldMessages,
|
|
44
|
+
...messagesProp
|
|
45
|
+
};
|
|
46
|
+
const [visible, setVisible] = useState(false);
|
|
47
|
+
const [toggled, setToggled] = useState(false);
|
|
48
|
+
const field = useFieldWiring({
|
|
49
|
+
enabled: true,
|
|
50
|
+
id: props.id,
|
|
51
|
+
describedBy: props["aria-describedby"],
|
|
52
|
+
invalid: props["aria-invalid"],
|
|
53
|
+
description,
|
|
54
|
+
error: void 0
|
|
55
|
+
});
|
|
56
|
+
const disabled = Boolean(props.disabled);
|
|
57
|
+
const ariaInvalid = props["aria-invalid"];
|
|
58
|
+
const control = /* @__PURE__ */ jsxs(Group, {
|
|
59
|
+
role: "presentation",
|
|
60
|
+
isDisabled: disabled,
|
|
61
|
+
isInvalid: ariaInvalid !== void 0 && ariaInvalid !== false && ariaInvalid !== "false",
|
|
62
|
+
"data-slot": "password-field-shell",
|
|
63
|
+
className: shellClass,
|
|
64
|
+
children: [/* @__PURE__ */ jsx(Input, {
|
|
65
|
+
ref,
|
|
66
|
+
...props,
|
|
67
|
+
...field.controlProps,
|
|
68
|
+
type: visible ? "text" : "password",
|
|
69
|
+
"data-slot": "password-field",
|
|
70
|
+
"data-reveal": toggled ? visible ? "shown" : "hidden" : void 0,
|
|
71
|
+
className: cn(inputClass, className)
|
|
72
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
73
|
+
type: "button",
|
|
74
|
+
isDisabled: disabled,
|
|
75
|
+
"aria-label": visible ? messages.hidePassword : messages.showPassword,
|
|
76
|
+
"aria-pressed": visible,
|
|
77
|
+
onPress: () => {
|
|
78
|
+
setVisible((current) => !current);
|
|
79
|
+
setToggled(true);
|
|
80
|
+
},
|
|
81
|
+
className: toggleClass,
|
|
82
|
+
children: /* @__PURE__ */ jsx(EyeMorph, { slashed: visible })
|
|
83
|
+
})]
|
|
84
|
+
});
|
|
85
|
+
if (!field.active) return control;
|
|
86
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
87
|
+
label,
|
|
88
|
+
controlId: field.controlId,
|
|
89
|
+
description,
|
|
90
|
+
hintId: field.hintId,
|
|
91
|
+
error: void 0,
|
|
92
|
+
errorId: void 0,
|
|
93
|
+
wordExit: "sharp",
|
|
94
|
+
numbers: false,
|
|
95
|
+
children: control
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//#endregion
|
|
99
|
+
export { PasswordField, defaultPasswordFieldMessages };
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=password-field.js.map
|