@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,652 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect, useLeavingItems } from "../lib/motion.js";
|
|
4
|
+
import { usePausableTimeout } from "../lib/pausable-timeout.js";
|
|
5
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
//#region src/confirm-morph/confirm-morph.tsx
|
|
8
|
+
/** A press this soon after the question appears is the tail of the press that opened it, not an answer. */
|
|
9
|
+
const ARM_MS = 280;
|
|
10
|
+
/** Whether the parts of the current face arrive with motion: not on first render, only on a change of face. */
|
|
11
|
+
const PartsAnimate = createContext(false);
|
|
12
|
+
/**
|
|
13
|
+
* One piece of a face. The parts of a new face arrive in reading order, 35ms
|
|
14
|
+
* apart after a 50ms beat, each clearing a small blur (--part-delay).
|
|
15
|
+
*/
|
|
16
|
+
function Part({ index, className, children }) {
|
|
17
|
+
const animate = useContext(PartsAnimate);
|
|
18
|
+
return /* @__PURE__ */ jsx("span", {
|
|
19
|
+
style: { "--part-delay": 50 + index * 35 + "ms" },
|
|
20
|
+
className: cn("inline-flex h-full items-center " + (animate ? "motion-safe:animate-part-in motion-safe:[animation-delay:var(--part-delay)] " : "") + (className ?? "")),
|
|
21
|
+
children
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Faces never move and never scale: each one sits at its natural width,
|
|
26
|
+
* against the pill's leading edge, so the baseline stays put and the pill's clip
|
|
27
|
+
* reveals it as the width springs. The present face reports its width so the
|
|
28
|
+
* pill can spring to it.
|
|
29
|
+
*/
|
|
30
|
+
function Face({ id, onSize, labelledBy, animated, children }) {
|
|
31
|
+
const ref = useRef(null);
|
|
32
|
+
useIsomorphicLayoutEffect(() => {
|
|
33
|
+
const node = ref.current;
|
|
34
|
+
if (!node) return;
|
|
35
|
+
const report = () => onSize(node.getBoundingClientRect().width);
|
|
36
|
+
report();
|
|
37
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
38
|
+
const observer = new ResizeObserver(report);
|
|
39
|
+
observer.observe(node);
|
|
40
|
+
return () => observer.disconnect();
|
|
41
|
+
}, [id, onSize]);
|
|
42
|
+
return /* @__PURE__ */ jsx("div", {
|
|
43
|
+
ref,
|
|
44
|
+
"data-face": id,
|
|
45
|
+
role: labelledBy ? "group" : void 0,
|
|
46
|
+
"aria-labelledby": labelledBy,
|
|
47
|
+
className: cn("flex h-full flex-none items-center gap-0.5 px-1 whitespace-nowrap data-[face=idle]:px-0" + (animated ? " motion-reduce:animate-face-in-soft" : "")),
|
|
48
|
+
children: /* @__PURE__ */ jsx(PartsAnimate.Provider, {
|
|
49
|
+
value: animated,
|
|
50
|
+
children
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** A leaving face steps out of the flow, exactly where it was, and fades while the pill springs to the next one. */
|
|
55
|
+
function LeavingFace({ id, onDone, children }) {
|
|
56
|
+
const remove = useCallback(() => onDone(id), [onDone, id]);
|
|
57
|
+
useExitBackstop(true, remove);
|
|
58
|
+
return /* @__PURE__ */ jsx("div", {
|
|
59
|
+
"aria-hidden": "true",
|
|
60
|
+
ref: (node) => node?.setAttribute("inert", ""),
|
|
61
|
+
onAnimationEnd: (event) => {
|
|
62
|
+
if (event.target === event.currentTarget) remove();
|
|
63
|
+
},
|
|
64
|
+
className: "pointer-events-none absolute start-0 top-0 flex h-full flex-none items-center gap-0.5 px-1 whitespace-nowrap motion-safe:animate-face-out motion-reduce:animate-fade-out",
|
|
65
|
+
children: /* @__PURE__ */ jsx(PartsAnimate.Provider, {
|
|
66
|
+
value: false,
|
|
67
|
+
children
|
|
68
|
+
})
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** A success disc that pops in with a tick drawing across it, the moment the action lands. */
|
|
72
|
+
function Check() {
|
|
73
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
74
|
+
className: "size-4.5 flex-none",
|
|
75
|
+
viewBox: "0 0 18 18",
|
|
76
|
+
fill: "none",
|
|
77
|
+
"aria-hidden": "true",
|
|
78
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
79
|
+
cx: "9",
|
|
80
|
+
cy: "9",
|
|
81
|
+
r: "8",
|
|
82
|
+
className: "fill-success-strong [transform-box:fill-box] [transform-origin:center] motion-safe:animate-disc-in"
|
|
83
|
+
}), /* @__PURE__ */ jsx("path", {
|
|
84
|
+
d: "M5.6 9.3 7.8 11.4 12.4 6.7",
|
|
85
|
+
pathLength: 1,
|
|
86
|
+
strokeWidth: "1.9",
|
|
87
|
+
strokeLinecap: "round",
|
|
88
|
+
strokeLinejoin: "round",
|
|
89
|
+
className: "stroke-background [stroke-dasharray:1] motion-safe:animate-tick-draw"
|
|
90
|
+
})]
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/** A ring that drains with the countdown. */
|
|
94
|
+
function Ring({ seconds, tone }) {
|
|
95
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
96
|
+
className: "size-4 flex-none",
|
|
97
|
+
viewBox: "0 0 16 16",
|
|
98
|
+
fill: "none",
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
101
|
+
cx: "8",
|
|
102
|
+
cy: "8",
|
|
103
|
+
r: "6.25",
|
|
104
|
+
strokeWidth: "1.75",
|
|
105
|
+
className: "stroke-border"
|
|
106
|
+
}), /* @__PURE__ */ jsx("circle", {
|
|
107
|
+
cx: "8",
|
|
108
|
+
cy: "8",
|
|
109
|
+
r: "6.25",
|
|
110
|
+
strokeWidth: "1.75",
|
|
111
|
+
strokeLinecap: "round",
|
|
112
|
+
pathLength: 1,
|
|
113
|
+
style: { animationDuration: seconds + "s" },
|
|
114
|
+
className: cn("[stroke-dasharray:1] [transform-box:fill-box] [transform-origin:center] -rotate-90 motion-safe:animate-ring-drain " + (tone === "neutral" ? "stroke-primary" : "stroke-destructive"))
|
|
115
|
+
})]
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/** Seconds that swap in place: the old digit fades up and out, the new one rises in, on tabular figures so the width holds. */
|
|
119
|
+
function Seconds({ value }) {
|
|
120
|
+
const ref = useRef(null);
|
|
121
|
+
const { leaving, changed, remove } = useLeavingItems(String(value), value, () => ref.current);
|
|
122
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
123
|
+
className: "inline-grid min-w-2 text-foreground tabular-nums",
|
|
124
|
+
children: [leaving.map((item) => /* @__PURE__ */ jsx(LeavingDigit, {
|
|
125
|
+
id: item.id,
|
|
126
|
+
onDone: remove,
|
|
127
|
+
children: item.value
|
|
128
|
+
}, item.id)), /* @__PURE__ */ jsx("span", {
|
|
129
|
+
ref,
|
|
130
|
+
className: cn("col-start-1 row-start-1" + (changed ? " motion-safe:animate-digit-in motion-reduce:animate-fade-in" : "")),
|
|
131
|
+
children: value
|
|
132
|
+
}, value)]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
function LeavingDigit({ id, onDone, children }) {
|
|
136
|
+
const remove = useCallback(() => onDone(id), [onDone, id]);
|
|
137
|
+
useExitBackstop(true, remove);
|
|
138
|
+
return /* @__PURE__ */ jsx("span", {
|
|
139
|
+
"aria-hidden": "true",
|
|
140
|
+
onAnimationEnd: (event) => {
|
|
141
|
+
if (event.target === event.currentTarget) remove();
|
|
142
|
+
},
|
|
143
|
+
className: "col-start-1 row-start-1 motion-safe:animate-digit-out motion-reduce:animate-fade-out",
|
|
144
|
+
children
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
function Spinner() {
|
|
148
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
149
|
+
className: "size-4 flex-none motion-safe:animate-spin-fast motion-reduce:[animation:spin_1.6s_linear_infinite]",
|
|
150
|
+
viewBox: "0 0 24 24",
|
|
151
|
+
fill: "none",
|
|
152
|
+
stroke: "currentColor",
|
|
153
|
+
strokeWidth: 1.75,
|
|
154
|
+
strokeLinecap: "round",
|
|
155
|
+
"aria-hidden": "true",
|
|
156
|
+
children: /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function AlertIcon() {
|
|
160
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
161
|
+
className: "size-4 flex-none text-destructive-strong",
|
|
162
|
+
viewBox: "0 0 24 24",
|
|
163
|
+
fill: "none",
|
|
164
|
+
stroke: "currentColor",
|
|
165
|
+
strokeWidth: 1.75,
|
|
166
|
+
strokeLinecap: "round",
|
|
167
|
+
strokeLinejoin: "round",
|
|
168
|
+
"aria-hidden": "true",
|
|
169
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
170
|
+
cx: "12",
|
|
171
|
+
cy: "12",
|
|
172
|
+
r: "10"
|
|
173
|
+
}), /* @__PURE__ */ jsx("path", { d: "M12 8v4M12 16h.01" })]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const focusRing = "outline-none focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset) focus-visible:outline-(--color-ring)";
|
|
177
|
+
const trigger = cn("inline-flex h-full cursor-pointer items-center gap-2 rounded-pill border-0 bg-transparent py-0 ps-3.5 pe-4 font-sans font-medium text-foreground motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle hover:not-active:not-disabled:bg-foreground/5 active:not-disabled:bg-foreground/10 active:not-disabled:duration-0 focus-visible:bg-foreground/6 disabled:cursor-not-allowed disabled:text-muted-foreground " + focusRing);
|
|
178
|
+
const secondary = "relative inline-flex confirm-button-h flex-none cursor-pointer touch-manipulation items-center justify-center rounded-pill border-0 bg-transparent px-3 font-sans font-medium text-muted-foreground motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle hover:not-active:bg-foreground/7 hover:not-active:text-foreground active:bg-foreground/10 active:text-foreground active:duration-0 focus-visible:bg-foreground/8 focus-visible:text-foreground " + focusRing;
|
|
179
|
+
const confirmDanger = "bg-destructive-subtle text-destructive-subtle-foreground hover:not-active:bg-[color-mix(in_oklab,var(--color-destructive)_18%,transparent)] active:bg-[color-mix(in_oklab,var(--color-destructive)_22%,transparent)] focus-visible:bg-[color-mix(in_oklab,var(--color-destructive)_20%,transparent)]";
|
|
180
|
+
const confirmNeutral = "bg-primary text-primary-foreground hover:not-active:bg-primary-hover active:bg-primary-pressed focus-visible:bg-primary-hover";
|
|
181
|
+
/**
|
|
182
|
+
* A button for an action that deserves a second thought, without a dialog. It
|
|
183
|
+
* asks in place: the pill morphs into a question with Cancel and Confirm, then
|
|
184
|
+
* a spinner, then a result with Undo, its width springing to each face while the
|
|
185
|
+
* faces crossfade at their natural width.
|
|
186
|
+
*
|
|
187
|
+
* Escape, a press outside, or the timeout answer the question with no. Focus
|
|
188
|
+
* follows the control between faces, but only when it was already inside, so a
|
|
189
|
+
* timeout never steals it. A live region announces each step.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* <ConfirmMorph label="Delete project" onConfirm={() => deleteProject()} onUndo={() => restore()} />
|
|
193
|
+
* <ConfirmMorph label="Delete project" confirmMode="hold" onConfirm={deleteProject} />
|
|
194
|
+
*/
|
|
195
|
+
const ConfirmMorph = forwardRef(function ConfirmMorph({ label, icon, prompt, confirmLabel = "Delete", cancelLabel = "Cancel", pendingLabel = "Deleting", doneLabel = "Deleted", errorLabel = "Couldn’t finish", retryLabel = "Retry", undoLabel = "Undo", undoingLabel = "Restoring", tone = "danger", confirmMode = "press", holdDuration = 1e3, countdown = 3, countdownLabel, onConfirm, onUndo, onCancel, state: stateProp, defaultState = "idle", onStateChange, confirmTimeout = 6e3, resultTimeout = 5e3, cancelOnOutsidePress = true, isDisabled = false, className }, ref) {
|
|
196
|
+
const uid = useId();
|
|
197
|
+
const promptId = uid + "-prompt";
|
|
198
|
+
const holdHintId = uid + "-hold";
|
|
199
|
+
const rootRef = useRef(null);
|
|
200
|
+
useImperativeHandle(ref, () => rootRef.current, []);
|
|
201
|
+
const [inner, setInner] = useState(defaultState);
|
|
202
|
+
const state = stateProp ?? inner;
|
|
203
|
+
const [working, setWorking] = useState("confirm");
|
|
204
|
+
const [counting, setCounting] = useState(false);
|
|
205
|
+
const [announcement, setAnnouncement] = useState("");
|
|
206
|
+
const faceKey = state === "confirming" && counting ? "counting" : state;
|
|
207
|
+
const live = useRef({
|
|
208
|
+
state,
|
|
209
|
+
onStateChange,
|
|
210
|
+
controlled: stateProp !== void 0
|
|
211
|
+
});
|
|
212
|
+
useIsomorphicLayoutEffect(() => {
|
|
213
|
+
live.current = {
|
|
214
|
+
state,
|
|
215
|
+
onStateChange,
|
|
216
|
+
controlled: stateProp !== void 0
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
const pendingFocus = useRef(false);
|
|
220
|
+
const run = useRef(0);
|
|
221
|
+
const askedAt = useRef(0);
|
|
222
|
+
const keepFocus = useCallback(() => {
|
|
223
|
+
const root = rootRef.current;
|
|
224
|
+
pendingFocus.current = !!root && (root.contains(document.activeElement) || document.activeElement === document.body);
|
|
225
|
+
}, []);
|
|
226
|
+
const go = useCallback((next) => {
|
|
227
|
+
if (next !== "confirming") setCounting(false);
|
|
228
|
+
if (next === live.current.state) return;
|
|
229
|
+
keepFocus();
|
|
230
|
+
if (next === "confirming") askedAt.current = performance.now();
|
|
231
|
+
if (!live.current.controlled) setInner(next);
|
|
232
|
+
live.current.state = next;
|
|
233
|
+
live.current.onStateChange?.(next);
|
|
234
|
+
}, [keepFocus]);
|
|
235
|
+
const [seenState, setSeenState] = useState(state);
|
|
236
|
+
if (seenState !== state) {
|
|
237
|
+
setSeenState(state);
|
|
238
|
+
if (state !== "confirming") setCounting(false);
|
|
239
|
+
}
|
|
240
|
+
const toIdle = useCallback(() => {
|
|
241
|
+
run.current++;
|
|
242
|
+
go("idle");
|
|
243
|
+
}, [go]);
|
|
244
|
+
const perform = useCallback(async (kind) => {
|
|
245
|
+
const handler = kind === "confirm" ? onConfirm : onUndo;
|
|
246
|
+
const token = ++run.current;
|
|
247
|
+
setWorking(kind);
|
|
248
|
+
setCounting(false);
|
|
249
|
+
let result;
|
|
250
|
+
try {
|
|
251
|
+
result = handler?.();
|
|
252
|
+
} catch {
|
|
253
|
+
go("error");
|
|
254
|
+
setAnnouncement(errorLabel);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (result && typeof result.then === "function") {
|
|
258
|
+
go("pending");
|
|
259
|
+
setAnnouncement(kind === "confirm" ? pendingLabel : undoingLabel);
|
|
260
|
+
try {
|
|
261
|
+
await result;
|
|
262
|
+
} catch {
|
|
263
|
+
if (token !== run.current) return;
|
|
264
|
+
go("error");
|
|
265
|
+
setAnnouncement(errorLabel);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (token !== run.current) return;
|
|
269
|
+
}
|
|
270
|
+
if (kind === "undo") {
|
|
271
|
+
go("idle");
|
|
272
|
+
setAnnouncement("Undone");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
go("done");
|
|
276
|
+
setAnnouncement(onUndo ? doneLabel + ". " + undoLabel + " is available." : doneLabel);
|
|
277
|
+
}, [
|
|
278
|
+
doneLabel,
|
|
279
|
+
errorLabel,
|
|
280
|
+
go,
|
|
281
|
+
onConfirm,
|
|
282
|
+
onUndo,
|
|
283
|
+
pendingLabel,
|
|
284
|
+
undoLabel,
|
|
285
|
+
undoingLabel
|
|
286
|
+
]);
|
|
287
|
+
const cancel = useCallback(() => {
|
|
288
|
+
onCancel?.();
|
|
289
|
+
toIdle();
|
|
290
|
+
setAnnouncement("Cancelled");
|
|
291
|
+
}, [onCancel, toIdle]);
|
|
292
|
+
const expire = useRef(() => {});
|
|
293
|
+
useIsomorphicLayoutEffect(() => {
|
|
294
|
+
expire.current = () => {
|
|
295
|
+
if (live.current.state === "confirming") cancel();
|
|
296
|
+
else toIdle();
|
|
297
|
+
};
|
|
298
|
+
});
|
|
299
|
+
const surfaceRef = useRef(null);
|
|
300
|
+
const target = useRef(0);
|
|
301
|
+
const flight = useRef(0);
|
|
302
|
+
const onFaceSize = useCallback((width) => {
|
|
303
|
+
if (Math.abs(width - target.current) < .25) return;
|
|
304
|
+
const from = target.current;
|
|
305
|
+
target.current = width;
|
|
306
|
+
const el = surfaceRef.current;
|
|
307
|
+
if (!el) return;
|
|
308
|
+
if (!from || prefersReducedMotion()) {
|
|
309
|
+
flight.current++;
|
|
310
|
+
el.style.width = "";
|
|
311
|
+
el.style.transition = "";
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const start = el.style.width ? el.getBoundingClientRect().width : from;
|
|
315
|
+
const token = ++flight.current;
|
|
316
|
+
const curve = width > start ? "morph" : "smooth";
|
|
317
|
+
el.style.transition = "none";
|
|
318
|
+
el.style.width = start + "px";
|
|
319
|
+
el.offsetWidth;
|
|
320
|
+
el.style.transition = "width var(--duration-" + curve + ") var(--ease-" + curve + "), background-color var(--duration-standard) var(--ease-settle), box-shadow var(--duration-standard) var(--ease-settle)";
|
|
321
|
+
el.style.width = width + "px";
|
|
322
|
+
const done = (event) => {
|
|
323
|
+
if (event.target !== el || event.propertyName !== "width") return;
|
|
324
|
+
el.removeEventListener("transitionend", done);
|
|
325
|
+
if (token !== flight.current) return;
|
|
326
|
+
el.style.width = "";
|
|
327
|
+
el.style.transition = "";
|
|
328
|
+
};
|
|
329
|
+
el.addEventListener("transitionend", done);
|
|
330
|
+
}, []);
|
|
331
|
+
const setHover = usePausableTimeout(state === "confirming" && !counting ? confirmTimeout : state === "done" || state === "error" ? resultTimeout : 0, state, () => expire.current());
|
|
332
|
+
const [seconds, setSeconds] = useState(countdown);
|
|
333
|
+
const performRef = useRef(perform);
|
|
334
|
+
useIsomorphicLayoutEffect(() => {
|
|
335
|
+
performRef.current = perform;
|
|
336
|
+
});
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (!counting) return;
|
|
339
|
+
const startedAt = performance.now();
|
|
340
|
+
const timer = setInterval(() => {
|
|
341
|
+
const elapsed = (performance.now() - startedAt) / 1e3;
|
|
342
|
+
if (elapsed >= countdown) {
|
|
343
|
+
clearInterval(timer);
|
|
344
|
+
performRef.current("confirm");
|
|
345
|
+
} else setSeconds(Math.max(1, Math.ceil(countdown - elapsed)));
|
|
346
|
+
}, 100);
|
|
347
|
+
return () => {
|
|
348
|
+
clearInterval(timer);
|
|
349
|
+
setSeconds(countdown);
|
|
350
|
+
};
|
|
351
|
+
}, [counting, countdown]);
|
|
352
|
+
const fillRef = useRef(null);
|
|
353
|
+
const holdRef = useRef(null);
|
|
354
|
+
const progress = useRef(0);
|
|
355
|
+
const setFill = (value, transition) => {
|
|
356
|
+
const el = fillRef.current;
|
|
357
|
+
if (!el) return;
|
|
358
|
+
el.style.transition = transition;
|
|
359
|
+
el.style.clipPath = "inset(0 " + (1 - value) * 100 + "% 0 0 round 999px)";
|
|
360
|
+
};
|
|
361
|
+
const startHold = () => {
|
|
362
|
+
if (holdRef.current) return;
|
|
363
|
+
const from = progress.current;
|
|
364
|
+
const remaining = holdDuration * (1 - from);
|
|
365
|
+
setFill(from, "none");
|
|
366
|
+
fillRef.current?.offsetWidth;
|
|
367
|
+
setFill(1, "clip-path " + remaining + "ms linear");
|
|
368
|
+
const timer = setTimeout(() => {
|
|
369
|
+
holdRef.current = null;
|
|
370
|
+
progress.current = 1;
|
|
371
|
+
perform("confirm");
|
|
372
|
+
}, remaining);
|
|
373
|
+
holdRef.current = {
|
|
374
|
+
from,
|
|
375
|
+
at: performance.now(),
|
|
376
|
+
timer
|
|
377
|
+
};
|
|
378
|
+
};
|
|
379
|
+
const releaseHold = () => {
|
|
380
|
+
const held = holdRef.current;
|
|
381
|
+
if (!held) return;
|
|
382
|
+
holdRef.current = null;
|
|
383
|
+
clearTimeout(held.timer);
|
|
384
|
+
const value = Math.min(1, held.from + (performance.now() - held.at) / holdDuration);
|
|
385
|
+
setFill(value, "none");
|
|
386
|
+
fillRef.current?.offsetWidth;
|
|
387
|
+
progress.current = 0;
|
|
388
|
+
setFill(0, prefersReducedMotion() ? "none" : "clip-path 540ms var(--ease-smooth)");
|
|
389
|
+
setAnnouncement("Keep holding to " + confirmLabel.toLowerCase());
|
|
390
|
+
};
|
|
391
|
+
useEffect(() => () => {
|
|
392
|
+
if (holdRef.current) clearTimeout(holdRef.current.timer);
|
|
393
|
+
}, []);
|
|
394
|
+
useEffect(() => {
|
|
395
|
+
if (state !== "confirming") {
|
|
396
|
+
if (holdRef.current) clearTimeout(holdRef.current.timer);
|
|
397
|
+
holdRef.current = null;
|
|
398
|
+
progress.current = 0;
|
|
399
|
+
setFill(0, "none");
|
|
400
|
+
}
|
|
401
|
+
}, [state]);
|
|
402
|
+
const answer = () => {
|
|
403
|
+
if (performance.now() - askedAt.current < ARM_MS) return;
|
|
404
|
+
if (confirmMode === "countdown") {
|
|
405
|
+
keepFocus();
|
|
406
|
+
setCounting(true);
|
|
407
|
+
setAnnouncement((countdownLabel ?? pendingLabel + " in") + " " + countdown + " seconds");
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
perform("confirm");
|
|
411
|
+
};
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
if (state !== "confirming" || !cancelOnOutsidePress) return;
|
|
414
|
+
const down = (event) => {
|
|
415
|
+
if (!rootRef.current?.contains(event.target)) cancel();
|
|
416
|
+
};
|
|
417
|
+
document.addEventListener("pointerdown", down);
|
|
418
|
+
return () => document.removeEventListener("pointerdown", down);
|
|
419
|
+
}, [
|
|
420
|
+
cancel,
|
|
421
|
+
cancelOnOutsidePress,
|
|
422
|
+
state
|
|
423
|
+
]);
|
|
424
|
+
useIsomorphicLayoutEffect(() => {
|
|
425
|
+
if (!pendingFocus.current) return;
|
|
426
|
+
pendingFocus.current = false;
|
|
427
|
+
const root = rootRef.current;
|
|
428
|
+
if (!root) return;
|
|
429
|
+
(root.querySelector("[data-face=\"" + faceKey + "\"]")?.querySelector("[data-autofocus]:not(:disabled)") ?? root).focus({ preventScroll: true });
|
|
430
|
+
}, [faceKey]);
|
|
431
|
+
const onKeyDown = (event) => {
|
|
432
|
+
if (event.key === "Escape") {
|
|
433
|
+
if (state === "confirming") {
|
|
434
|
+
event.preventDefault();
|
|
435
|
+
event.stopPropagation();
|
|
436
|
+
cancel();
|
|
437
|
+
} else if (state === "done" || state === "error") {
|
|
438
|
+
event.preventDefault();
|
|
439
|
+
event.stopPropagation();
|
|
440
|
+
toIdle();
|
|
441
|
+
}
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (event.key === "Enter" && state === "confirming" && !event.repeat && !(event.target instanceof HTMLButtonElement)) {
|
|
445
|
+
event.preventDefault();
|
|
446
|
+
if (counting) perform("confirm");
|
|
447
|
+
else if (confirmMode !== "hold") answer();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const holdKeys = (event, down) => {
|
|
451
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
452
|
+
event.preventDefault();
|
|
453
|
+
if (down) {
|
|
454
|
+
if (!event.repeat) startHold();
|
|
455
|
+
} else releaseHold();
|
|
456
|
+
};
|
|
457
|
+
const holdPointer = (event) => {
|
|
458
|
+
if (event.button !== 0) return;
|
|
459
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
460
|
+
startHold();
|
|
461
|
+
};
|
|
462
|
+
const shownPrompt = prompt ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [label, "?"] });
|
|
463
|
+
const shownCountdown = countdownLabel ?? pendingLabel + " in";
|
|
464
|
+
const confirmClasses = cn("relative inline-flex confirm-button-h flex-none cursor-pointer touch-manipulation items-center justify-center overflow-clip rounded-pill border-0 px-3 font-sans font-medium motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle active:duration-0 outline-none focus-visible:outline-solid focus-visible:outline-(length:--focus-ring-width) focus-visible:outline-offset-(--focus-ring-offset) focus-visible:outline-(--color-ring) " + (tone === "neutral" ? confirmNeutral : confirmDanger));
|
|
465
|
+
const confirmButton = confirmMode === "hold" ? /* @__PURE__ */ jsxs("button", {
|
|
466
|
+
type: "button",
|
|
467
|
+
className: cn(confirmClasses + " select-none [-webkit-touch-callout:none]"),
|
|
468
|
+
"data-autofocus": true,
|
|
469
|
+
"aria-describedby": holdHintId,
|
|
470
|
+
onPointerDown: holdPointer,
|
|
471
|
+
onPointerUp: releaseHold,
|
|
472
|
+
onPointerCancel: releaseHold,
|
|
473
|
+
onLostPointerCapture: releaseHold,
|
|
474
|
+
onKeyDown: (event) => holdKeys(event, true),
|
|
475
|
+
onKeyUp: (event) => holdKeys(event, false),
|
|
476
|
+
onBlur: releaseHold,
|
|
477
|
+
onContextMenu: (event) => event.preventDefault(),
|
|
478
|
+
children: [/* @__PURE__ */ jsx("span", { children: confirmLabel }), /* @__PURE__ */ jsx("span", {
|
|
479
|
+
ref: fillRef,
|
|
480
|
+
"aria-hidden": "true",
|
|
481
|
+
style: { clipPath: "inset(0 100% 0 0 round 999px)" },
|
|
482
|
+
className: cn("pointer-events-none absolute inset-0 inline-flex items-center justify-center " + (tone === "neutral" ? "bg-primary-strong text-primary-foreground" : "bg-destructive text-destructive-foreground")),
|
|
483
|
+
children: /* @__PURE__ */ jsx("span", { children: confirmLabel })
|
|
484
|
+
})]
|
|
485
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
486
|
+
type: "button",
|
|
487
|
+
className: confirmClasses,
|
|
488
|
+
"data-autofocus": true,
|
|
489
|
+
onClick: answer,
|
|
490
|
+
onKeyDown: (event) => {
|
|
491
|
+
if (event.repeat) event.preventDefault();
|
|
492
|
+
},
|
|
493
|
+
children: confirmLabel
|
|
494
|
+
});
|
|
495
|
+
const statusPart = "gap-2 ps-3 pe-2.5 font-medium text-foreground tabular-nums";
|
|
496
|
+
const face = (() => {
|
|
497
|
+
switch (faceKey) {
|
|
498
|
+
case "confirming": return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
499
|
+
/* @__PURE__ */ jsx(Part, {
|
|
500
|
+
index: 0,
|
|
501
|
+
className: "max-w-64 overflow-hidden ps-3 pe-2 font-medium text-foreground tabular-nums",
|
|
502
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
503
|
+
id: promptId,
|
|
504
|
+
className: "overflow-hidden text-ellipsis",
|
|
505
|
+
children: shownPrompt
|
|
506
|
+
})
|
|
507
|
+
}),
|
|
508
|
+
/* @__PURE__ */ jsx(Part, {
|
|
509
|
+
index: 1,
|
|
510
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
511
|
+
type: "button",
|
|
512
|
+
className: secondary,
|
|
513
|
+
onClick: cancel,
|
|
514
|
+
children: cancelLabel
|
|
515
|
+
})
|
|
516
|
+
}),
|
|
517
|
+
/* @__PURE__ */ jsx(Part, {
|
|
518
|
+
index: 2,
|
|
519
|
+
children: confirmButton
|
|
520
|
+
}),
|
|
521
|
+
confirmMode === "hold" ? /* @__PURE__ */ jsx("span", {
|
|
522
|
+
id: holdHintId,
|
|
523
|
+
className: "sr-only",
|
|
524
|
+
children: "Press and hold"
|
|
525
|
+
}) : null
|
|
526
|
+
] });
|
|
527
|
+
case "counting": return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Part, {
|
|
528
|
+
index: 0,
|
|
529
|
+
className: cn(statusPart + " pe-1.5 text-muted-foreground"),
|
|
530
|
+
children: [/* @__PURE__ */ jsx(Ring, {
|
|
531
|
+
seconds: countdown,
|
|
532
|
+
tone
|
|
533
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
534
|
+
className: "inline-flex items-baseline gap-1",
|
|
535
|
+
children: [
|
|
536
|
+
shownCountdown,
|
|
537
|
+
" ",
|
|
538
|
+
/* @__PURE__ */ jsx(Seconds, { value: seconds })
|
|
539
|
+
]
|
|
540
|
+
})]
|
|
541
|
+
}), /* @__PURE__ */ jsx(Part, {
|
|
542
|
+
index: 1,
|
|
543
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
544
|
+
type: "button",
|
|
545
|
+
className: secondary,
|
|
546
|
+
onClick: cancel,
|
|
547
|
+
children: cancelLabel
|
|
548
|
+
})
|
|
549
|
+
})] });
|
|
550
|
+
case "pending": return /* @__PURE__ */ jsxs(Part, {
|
|
551
|
+
index: 0,
|
|
552
|
+
className: cn(statusPart + " ps-3 pe-4 text-muted-foreground"),
|
|
553
|
+
children: [/* @__PURE__ */ jsx(Spinner, {}), /* @__PURE__ */ jsx("span", {
|
|
554
|
+
className: "inline-flex items-baseline gap-1",
|
|
555
|
+
children: working === "undo" ? undoingLabel : pendingLabel
|
|
556
|
+
})]
|
|
557
|
+
});
|
|
558
|
+
case "done": return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Part, {
|
|
559
|
+
index: 0,
|
|
560
|
+
className: cn(statusPart + (onUndo ? "" : " pe-3.5")),
|
|
561
|
+
children: [/* @__PURE__ */ jsx(Check, {}), /* @__PURE__ */ jsx("span", {
|
|
562
|
+
className: "inline-flex items-baseline gap-1",
|
|
563
|
+
children: doneLabel
|
|
564
|
+
})]
|
|
565
|
+
}), onUndo ? /* @__PURE__ */ jsx(Part, {
|
|
566
|
+
index: 1,
|
|
567
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
568
|
+
type: "button",
|
|
569
|
+
className: cn(secondary + " text-foreground"),
|
|
570
|
+
"data-autofocus": true,
|
|
571
|
+
onClick: () => void perform("undo"),
|
|
572
|
+
children: undoLabel
|
|
573
|
+
})
|
|
574
|
+
}) : null] });
|
|
575
|
+
case "error": return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Part, {
|
|
576
|
+
index: 0,
|
|
577
|
+
className: statusPart,
|
|
578
|
+
children: [/* @__PURE__ */ jsx(AlertIcon, {}), /* @__PURE__ */ jsx("span", {
|
|
579
|
+
className: "inline-flex items-baseline gap-1",
|
|
580
|
+
children: errorLabel
|
|
581
|
+
})]
|
|
582
|
+
}), /* @__PURE__ */ jsx(Part, {
|
|
583
|
+
index: 1,
|
|
584
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
585
|
+
type: "button",
|
|
586
|
+
className: cn(secondary + " text-foreground"),
|
|
587
|
+
"data-autofocus": true,
|
|
588
|
+
onClick: () => void perform(working),
|
|
589
|
+
children: retryLabel
|
|
590
|
+
})
|
|
591
|
+
})] });
|
|
592
|
+
default: return /* @__PURE__ */ jsx(Part, {
|
|
593
|
+
index: 0,
|
|
594
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
595
|
+
type: "button",
|
|
596
|
+
className: trigger,
|
|
597
|
+
"data-autofocus": true,
|
|
598
|
+
disabled: isDisabled,
|
|
599
|
+
onClick: () => {
|
|
600
|
+
setAnnouncement(typeof shownPrompt === "string" ? shownPrompt : "");
|
|
601
|
+
go("confirming");
|
|
602
|
+
},
|
|
603
|
+
children: [icon ? /* @__PURE__ */ jsx("span", {
|
|
604
|
+
className: "grid size-4 place-items-center text-muted-foreground [&>svg]:size-4",
|
|
605
|
+
"aria-hidden": "true",
|
|
606
|
+
children: icon
|
|
607
|
+
}) : null, /* @__PURE__ */ jsx("span", { children: label })]
|
|
608
|
+
})
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
})();
|
|
612
|
+
const { leaving, changed, remove } = useLeavingItems(faceKey, face, () => null);
|
|
613
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
614
|
+
ref: rootRef,
|
|
615
|
+
"data-state": state,
|
|
616
|
+
"data-face": faceKey,
|
|
617
|
+
"data-tone": tone,
|
|
618
|
+
"data-mode": confirmMode,
|
|
619
|
+
"data-disabled": isDisabled || void 0,
|
|
620
|
+
tabIndex: -1,
|
|
621
|
+
"aria-busy": state === "pending" || void 0,
|
|
622
|
+
onKeyDown,
|
|
623
|
+
onPointerEnter: () => setHover(true),
|
|
624
|
+
onPointerLeave: () => setHover(false),
|
|
625
|
+
onPointerCancel: () => setHover(false),
|
|
626
|
+
className: cn("relative inline-flex max-w-full min-w-0 align-middle font-sans text-sm leading-5 text-foreground outline-none [-webkit-tap-highlight-color:transparent]", className),
|
|
627
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
628
|
+
ref: surfaceRef,
|
|
629
|
+
className: cn("relative flex h-(--density-control-h-sm) max-w-full justify-start overflow-clip rounded-pill bg-card shadow-resting after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:border-(length:--border-width) after:border-border after:content-[''] motion-safe:transition-[background-color,box-shadow] motion-safe:duration-(--duration-standard) motion-safe:ease-settle " + (isDisabled && state === "idle" ? "shadow-none" : "")),
|
|
630
|
+
children: [leaving.map((item) => /* @__PURE__ */ jsx(LeavingFace, {
|
|
631
|
+
id: item.id,
|
|
632
|
+
onDone: remove,
|
|
633
|
+
children: item.value
|
|
634
|
+
}, item.id)), /* @__PURE__ */ jsx(Face, {
|
|
635
|
+
id: faceKey,
|
|
636
|
+
onSize: onFaceSize,
|
|
637
|
+
labelledBy: faceKey === "confirming" ? promptId : void 0,
|
|
638
|
+
animated: changed,
|
|
639
|
+
children: face
|
|
640
|
+
}, faceKey)]
|
|
641
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
642
|
+
className: "sr-only",
|
|
643
|
+
role: "status",
|
|
644
|
+
"aria-live": "polite",
|
|
645
|
+
children: announcement
|
|
646
|
+
})]
|
|
647
|
+
});
|
|
648
|
+
});
|
|
649
|
+
//#endregion
|
|
650
|
+
export { ConfirmMorph };
|
|
651
|
+
|
|
652
|
+
//# sourceMappingURL=confirm-morph.js.map
|