react-ui-suite 0.1.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/LICENSE +21 -0
- package/README.md +71 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +4055 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,4055 @@
|
|
|
1
|
+
// components/Alert/Alert.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
var variantStyles = {
|
|
6
|
+
info: {
|
|
7
|
+
container: "bg-sky-50 text-sky-900 ring-1 ring-sky-100 dark:bg-sky-500/10 dark:text-sky-100",
|
|
8
|
+
accent: "bg-sky-400",
|
|
9
|
+
icon: "\u2139\uFE0F"
|
|
10
|
+
},
|
|
11
|
+
success: {
|
|
12
|
+
container: "bg-emerald-50 text-emerald-900 ring-1 ring-emerald-100 dark:bg-emerald-500/10 dark:text-emerald-100",
|
|
13
|
+
accent: "bg-emerald-400",
|
|
14
|
+
icon: "\u2714\uFE0F"
|
|
15
|
+
},
|
|
16
|
+
warning: {
|
|
17
|
+
container: "bg-amber-50 text-amber-900 ring-1 ring-amber-100 dark:bg-amber-400/10 dark:text-amber-100",
|
|
18
|
+
accent: "bg-amber-400",
|
|
19
|
+
icon: "\u26A0\uFE0F"
|
|
20
|
+
},
|
|
21
|
+
danger: {
|
|
22
|
+
container: "bg-rose-50 text-rose-900 ring-1 ring-rose-100 dark:bg-rose-500/10 dark:text-rose-100",
|
|
23
|
+
accent: "bg-rose-400",
|
|
24
|
+
icon: "\u26D4"
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var Alert = React.forwardRef(function Alert2({ title, description, variant = "info", onDismiss, className, ...rest }, ref) {
|
|
28
|
+
const style = variantStyles[variant];
|
|
29
|
+
return /* @__PURE__ */ jsxs(
|
|
30
|
+
"div",
|
|
31
|
+
{
|
|
32
|
+
...rest,
|
|
33
|
+
ref,
|
|
34
|
+
role: "alert",
|
|
35
|
+
className: twMerge(
|
|
36
|
+
"relative flex items-start gap-3 rounded-2xl px-4 py-3 text-sm shadow-sm",
|
|
37
|
+
style.container,
|
|
38
|
+
className
|
|
39
|
+
),
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ jsx("span", { className: "mt-[2px] text-base", "aria-hidden": "true", children: style.icon }),
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("p", { className: "font-semibold", children: title }),
|
|
44
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-sm/relaxed opacity-90", children: description }) : null
|
|
45
|
+
] }),
|
|
46
|
+
onDismiss ? /* @__PURE__ */ jsx(
|
|
47
|
+
"button",
|
|
48
|
+
{
|
|
49
|
+
type: "button",
|
|
50
|
+
onClick: onDismiss,
|
|
51
|
+
className: "rounded-full p-1 text-xs font-semibold uppercase tracking-wide text-current/70 hover:text-current focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent",
|
|
52
|
+
"aria-label": "Dismiss alert",
|
|
53
|
+
children: "\xD7"
|
|
54
|
+
}
|
|
55
|
+
) : null,
|
|
56
|
+
/* @__PURE__ */ jsx(
|
|
57
|
+
"span",
|
|
58
|
+
{
|
|
59
|
+
className: twMerge("absolute inset-y-2 left-1 w-1 rounded-full", style.accent),
|
|
60
|
+
"aria-hidden": "true"
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// components/Badge/Badge.tsx
|
|
69
|
+
import * as React2 from "react";
|
|
70
|
+
import { twMerge as twMerge2 } from "tailwind-merge";
|
|
71
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
72
|
+
var variantClasses = {
|
|
73
|
+
neutral: "bg-slate-100 text-slate-700 ring-1 ring-slate-200 dark:bg-zinc-900/60 dark:text-zinc-100 dark:ring-zinc-700/60",
|
|
74
|
+
info: "bg-sky-50 text-sky-700 ring-1 ring-sky-200 dark:bg-sky-500/10 dark:text-sky-300",
|
|
75
|
+
success: "bg-emerald-50 text-emerald-700 ring-1 ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300",
|
|
76
|
+
warning: "bg-amber-50 text-amber-700 ring-1 ring-amber-200 dark:bg-amber-500/10 dark:text-amber-200",
|
|
77
|
+
danger: "bg-rose-50 text-rose-700 ring-1 ring-rose-200 dark:bg-rose-500/10 dark:text-rose-300"
|
|
78
|
+
};
|
|
79
|
+
var Badge = React2.forwardRef(function Badge2({ variant = "neutral", icon, className, children, ...rest }, ref) {
|
|
80
|
+
return /* @__PURE__ */ jsxs2(
|
|
81
|
+
"span",
|
|
82
|
+
{
|
|
83
|
+
...rest,
|
|
84
|
+
ref,
|
|
85
|
+
className: twMerge2(
|
|
86
|
+
"inline-flex items-center gap-1 rounded-full px-3 py-1 text-[0.7rem] font-semibold uppercase tracking-wide",
|
|
87
|
+
variantClasses[variant],
|
|
88
|
+
className
|
|
89
|
+
),
|
|
90
|
+
children: [
|
|
91
|
+
icon ? /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", children: icon }) : null,
|
|
92
|
+
children
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// components/Button/Button.tsx
|
|
99
|
+
import { twMerge as twMerge3 } from "tailwind-merge";
|
|
100
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
101
|
+
function Button({
|
|
102
|
+
children,
|
|
103
|
+
onClick,
|
|
104
|
+
disabled = false,
|
|
105
|
+
className = "",
|
|
106
|
+
type = "button",
|
|
107
|
+
style,
|
|
108
|
+
...rest
|
|
109
|
+
}) {
|
|
110
|
+
const hasCustomBackground = typeof className === "string" && /bg-/.test(className);
|
|
111
|
+
const hasCustomText = typeof className === "string" && /text-/.test(className);
|
|
112
|
+
const backgroundClasses = hasCustomBackground ? "" : "bg-white hover:bg-slate-100 dark:bg-zinc-900/70 dark:hover:bg-zinc-800";
|
|
113
|
+
const textClasses = hasCustomText ? "" : "text-slate-900 dark:text-zinc-100";
|
|
114
|
+
return /* @__PURE__ */ jsx3(
|
|
115
|
+
"button",
|
|
116
|
+
{
|
|
117
|
+
type,
|
|
118
|
+
onClick,
|
|
119
|
+
disabled,
|
|
120
|
+
style,
|
|
121
|
+
...rest,
|
|
122
|
+
className: twMerge3(
|
|
123
|
+
"inline-flex items-center justify-center gap-2 rounded-xl border border-slate-300 px-4 py-2 text-sm font-semibold shadow-sm transition",
|
|
124
|
+
backgroundClasses,
|
|
125
|
+
textClasses,
|
|
126
|
+
"hover:border-slate-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500",
|
|
127
|
+
"disabled:cursor-not-allowed disabled:opacity-60",
|
|
128
|
+
"dark:border-zinc-700/60 dark:hover:border-zinc-500 dark:shadow-slate-950/20",
|
|
129
|
+
className
|
|
130
|
+
),
|
|
131
|
+
children
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// components/Card/Card.tsx
|
|
137
|
+
import * as React3 from "react";
|
|
138
|
+
import { twMerge as twMerge4 } from "tailwind-merge";
|
|
139
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
140
|
+
var Card = React3.forwardRef(function Card2({ eyebrow, title, actions, children, footer, muted, className, ...rest }, ref) {
|
|
141
|
+
const hasHeader = eyebrow || actions;
|
|
142
|
+
return /* @__PURE__ */ jsxs3(
|
|
143
|
+
"div",
|
|
144
|
+
{
|
|
145
|
+
...rest,
|
|
146
|
+
ref,
|
|
147
|
+
className: twMerge4(
|
|
148
|
+
"flex h-full flex-col rounded-3xl border border-slate-200 bg-white p-5 shadow-lg shadow-slate-200/40 dark:border-zinc-700/60 dark:bg-zinc-900/70 dark:shadow-none",
|
|
149
|
+
muted && "bg-slate-50/70 dark:bg-zinc-900/40",
|
|
150
|
+
className
|
|
151
|
+
),
|
|
152
|
+
children: [
|
|
153
|
+
hasHeader && /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between gap-4", children: [
|
|
154
|
+
eyebrow ? /* @__PURE__ */ jsx4("p", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-400", children: eyebrow }) : /* @__PURE__ */ jsx4("span", {}),
|
|
155
|
+
actions ? /* @__PURE__ */ jsx4("div", { className: "text-sm text-slate-500 dark:text-zinc-400", children: actions }) : null
|
|
156
|
+
] }),
|
|
157
|
+
title ? /* @__PURE__ */ jsx4(
|
|
158
|
+
"h3",
|
|
159
|
+
{
|
|
160
|
+
className: twMerge4(
|
|
161
|
+
"text-lg font-semibold text-slate-900 dark:text-zinc-100",
|
|
162
|
+
hasHeader ? "mt-3" : "mt-0"
|
|
163
|
+
),
|
|
164
|
+
children: title
|
|
165
|
+
}
|
|
166
|
+
) : null,
|
|
167
|
+
children ? /* @__PURE__ */ jsx4("div", { className: "mt-3 flex-1 text-sm text-slate-600 dark:text-zinc-300", children }) : null,
|
|
168
|
+
footer ? /* @__PURE__ */ jsx4("div", { className: "mt-4 border-t border-slate-100 pt-3 text-xs text-slate-500 dark:border-zinc-800 dark:text-zinc-400", children: footer }) : null
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// components/Checkbox/Checkbox.tsx
|
|
175
|
+
import * as React4 from "react";
|
|
176
|
+
import { twMerge as twMerge5 } from "tailwind-merge";
|
|
177
|
+
|
|
178
|
+
// components/Combobox/icons.tsx
|
|
179
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
180
|
+
function ChevronDown(props) {
|
|
181
|
+
return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", ...props, children: /* @__PURE__ */ jsx5(
|
|
182
|
+
"path",
|
|
183
|
+
{
|
|
184
|
+
fillRule: "evenodd",
|
|
185
|
+
d: "M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.172l3.71-3.94a.75.75 0 1 1 1.08 1.04l-4.24 4.5a.75.75 0 0 1-1.08 0l-4.24-4.5a.75.75 0 0 1 .02-1.06z",
|
|
186
|
+
clipRule: "evenodd"
|
|
187
|
+
}
|
|
188
|
+
) });
|
|
189
|
+
}
|
|
190
|
+
function Check(props) {
|
|
191
|
+
return /* @__PURE__ */ jsx5("svg", { viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", ...props, children: /* @__PURE__ */ jsx5("path", { d: "M16.704 5.29a1 1 0 0 1 0 1.415l-7.2 7.2a1 1 0 0 1-1.415 0l-3.2-3.2a1 1 0 1 1 1.414-1.414l2.493 2.493 6.493-6.493a1 1 0 0 1 1.415 0z" }) });
|
|
192
|
+
}
|
|
193
|
+
function Calendar(props) {
|
|
194
|
+
return /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true", ...props, children: [
|
|
195
|
+
/* @__PURE__ */ jsx5("rect", { x: "3.5", y: "5", width: "17", height: "15", rx: "2", strokeWidth: "1.5" }),
|
|
196
|
+
/* @__PURE__ */ jsx5("path", { d: "M8 4v3M16 4v3", strokeWidth: "1.5", strokeLinecap: "round" }),
|
|
197
|
+
/* @__PURE__ */ jsx5("path", { d: "M3.5 9h17", strokeWidth: "1.5" }),
|
|
198
|
+
/* @__PURE__ */ jsx5("rect", { x: "7.75", y: "12.25", width: "4.5", height: "4.5", rx: "1", strokeWidth: "1.5" })
|
|
199
|
+
] });
|
|
200
|
+
}
|
|
201
|
+
function Clock(props) {
|
|
202
|
+
return /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "aria-hidden": "true", ...props, children: [
|
|
203
|
+
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "8.5", strokeWidth: "1.5" }),
|
|
204
|
+
/* @__PURE__ */ jsx5(
|
|
205
|
+
"path",
|
|
206
|
+
{
|
|
207
|
+
d: "M12 7.5v4.25l3 1.75",
|
|
208
|
+
strokeWidth: "1.5",
|
|
209
|
+
strokeLinecap: "round",
|
|
210
|
+
strokeLinejoin: "round"
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// utils/ref.ts
|
|
217
|
+
function assignRef(ref, value) {
|
|
218
|
+
if (typeof ref === "function") {
|
|
219
|
+
ref(value);
|
|
220
|
+
} else if (ref) {
|
|
221
|
+
ref.current = value;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// components/Checkbox/Checkbox.tsx
|
|
226
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
227
|
+
var Checkbox = React4.forwardRef(function Checkbox2({
|
|
228
|
+
label,
|
|
229
|
+
description,
|
|
230
|
+
checked,
|
|
231
|
+
defaultChecked = false,
|
|
232
|
+
onChange,
|
|
233
|
+
disabled,
|
|
234
|
+
className,
|
|
235
|
+
indeterminate,
|
|
236
|
+
id,
|
|
237
|
+
...rest
|
|
238
|
+
}, forwardedRef) {
|
|
239
|
+
const internalRef = React4.useRef(null);
|
|
240
|
+
const mergedRef = React4.useCallback(
|
|
241
|
+
(node) => {
|
|
242
|
+
internalRef.current = node;
|
|
243
|
+
assignRef(forwardedRef, node);
|
|
244
|
+
},
|
|
245
|
+
[forwardedRef]
|
|
246
|
+
);
|
|
247
|
+
const isControlled = typeof checked === "boolean";
|
|
248
|
+
const [internalChecked, setInternalChecked] = React4.useState(defaultChecked);
|
|
249
|
+
const resolvedChecked = isControlled ? !!checked : internalChecked;
|
|
250
|
+
const generatedId = React4.useId();
|
|
251
|
+
const checkboxId = id != null ? id : generatedId;
|
|
252
|
+
React4.useEffect(() => {
|
|
253
|
+
if (internalRef.current) {
|
|
254
|
+
internalRef.current.indeterminate = !!indeterminate && !resolvedChecked;
|
|
255
|
+
}
|
|
256
|
+
}, [indeterminate, resolvedChecked]);
|
|
257
|
+
const handleChange = (event) => {
|
|
258
|
+
const next = event.target.checked;
|
|
259
|
+
if (!isControlled) {
|
|
260
|
+
setInternalChecked(next);
|
|
261
|
+
}
|
|
262
|
+
onChange == null ? void 0 : onChange(next);
|
|
263
|
+
};
|
|
264
|
+
return /* @__PURE__ */ jsxs5(
|
|
265
|
+
"label",
|
|
266
|
+
{
|
|
267
|
+
htmlFor: checkboxId,
|
|
268
|
+
className: twMerge5(
|
|
269
|
+
"flex cursor-pointer gap-3 rounded-2xl border border-transparent px-2 py-2 text-left transition hover:bg-slate-50 dark:hover:bg-zinc-900/60",
|
|
270
|
+
"items-center",
|
|
271
|
+
disabled && "cursor-not-allowed opacity-60",
|
|
272
|
+
className
|
|
273
|
+
),
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsxs5("span", { className: "flex h-6 w-6 items-center justify-center", children: [
|
|
276
|
+
/* @__PURE__ */ jsx6(
|
|
277
|
+
"input",
|
|
278
|
+
{
|
|
279
|
+
...rest,
|
|
280
|
+
ref: mergedRef,
|
|
281
|
+
id: checkboxId,
|
|
282
|
+
type: "checkbox",
|
|
283
|
+
className: "peer sr-only",
|
|
284
|
+
checked: resolvedChecked,
|
|
285
|
+
onChange: handleChange,
|
|
286
|
+
disabled
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
/* @__PURE__ */ jsx6(
|
|
290
|
+
"span",
|
|
291
|
+
{
|
|
292
|
+
className: twMerge5(
|
|
293
|
+
"grid size-5 place-items-center rounded-lg border border-slate-300 bg-white text-[0.65rem] font-semibold text-slate-600 transition drop-shadow-sm peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-slate-400 dark:border-zinc-600 dark:bg-zinc-950/70 dark:text-zinc-200",
|
|
294
|
+
resolvedChecked && "border-slate-400 bg-slate-100 text-slate-900 shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-500 dark:bg-zinc-500 dark:text-white"
|
|
295
|
+
),
|
|
296
|
+
"aria-hidden": "true",
|
|
297
|
+
children: resolvedChecked ? /* @__PURE__ */ jsx6(Check, { className: "h-3 w-3" }) : indeterminate ? /* @__PURE__ */ jsx6("span", { className: "h-[2px] w-2 rounded-full bg-current" }) : null
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
] }),
|
|
301
|
+
/* @__PURE__ */ jsxs5("span", { className: "flex flex-1 flex-col", children: [
|
|
302
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm font-semibold text-slate-900 dark:text-zinc-100", children: label }),
|
|
303
|
+
description ? /* @__PURE__ */ jsx6("span", { className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null
|
|
304
|
+
] })
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// components/ColorPicker/ColorPicker.tsx
|
|
311
|
+
import * as React5 from "react";
|
|
312
|
+
import { twMerge as twMerge6 } from "tailwind-merge";
|
|
313
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
314
|
+
var defaultSwatches = ["#0ea5e9", "#22c55e", "#f97316", "#f43f5e", "#6366f1", "#facc15"];
|
|
315
|
+
var formatOrder = ["hex", "rgb", "hsl"];
|
|
316
|
+
function clamp(value, min, max) {
|
|
317
|
+
return Math.min(max, Math.max(min, value));
|
|
318
|
+
}
|
|
319
|
+
function normalizeHex(input) {
|
|
320
|
+
if (!input) return null;
|
|
321
|
+
let hex = input.trim().replace(/^#/, "");
|
|
322
|
+
if (hex.length === 3) {
|
|
323
|
+
hex = hex.split("").map((char) => char + char).join("");
|
|
324
|
+
}
|
|
325
|
+
if (!/^[0-9a-fA-F]{6}$/.test(hex)) return null;
|
|
326
|
+
return `#${hex.toUpperCase()}`;
|
|
327
|
+
}
|
|
328
|
+
function hexToRgb(hex) {
|
|
329
|
+
const normalized = normalizeHex(hex);
|
|
330
|
+
if (!normalized) return null;
|
|
331
|
+
const value = normalized.replace("#", "");
|
|
332
|
+
return {
|
|
333
|
+
r: parseInt(value.slice(0, 2), 16),
|
|
334
|
+
g: parseInt(value.slice(2, 4), 16),
|
|
335
|
+
b: parseInt(value.slice(4, 6), 16)
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function rgbToHex(r, g, b) {
|
|
339
|
+
const parts = [r, g, b].map((component) => clamp(Math.round(component), 0, 255)).map((component) => component.toString(16).padStart(2, "0"));
|
|
340
|
+
return `#${parts.join("").toUpperCase()}`;
|
|
341
|
+
}
|
|
342
|
+
function hexToHsl(hex) {
|
|
343
|
+
const rgb = hexToRgb(hex);
|
|
344
|
+
if (!rgb) return null;
|
|
345
|
+
const r = rgb.r / 255;
|
|
346
|
+
const g = rgb.g / 255;
|
|
347
|
+
const b = rgb.b / 255;
|
|
348
|
+
const max = Math.max(r, g, b);
|
|
349
|
+
const min = Math.min(r, g, b);
|
|
350
|
+
let h = 0;
|
|
351
|
+
let s = 0;
|
|
352
|
+
const l = (max + min) / 2;
|
|
353
|
+
if (max !== min) {
|
|
354
|
+
const d = max - min;
|
|
355
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
356
|
+
switch (max) {
|
|
357
|
+
case r:
|
|
358
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
359
|
+
break;
|
|
360
|
+
case g:
|
|
361
|
+
h = (b - r) / d + 2;
|
|
362
|
+
break;
|
|
363
|
+
case b:
|
|
364
|
+
h = (r - g) / d + 4;
|
|
365
|
+
break;
|
|
366
|
+
}
|
|
367
|
+
h /= 6;
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
h: Math.round(h * 360),
|
|
371
|
+
s: Math.round(s * 100),
|
|
372
|
+
l: Math.round(l * 100)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function hslToHex(h, s, l) {
|
|
376
|
+
const hue = (h % 360 + 360) % 360;
|
|
377
|
+
const sat = clamp(s / 100, 0, 1);
|
|
378
|
+
const light = clamp(l / 100, 0, 1);
|
|
379
|
+
if (sat === 0) {
|
|
380
|
+
const gray = Math.round(light * 255);
|
|
381
|
+
return rgbToHex(gray, gray, gray);
|
|
382
|
+
}
|
|
383
|
+
const q = light < 0.5 ? light * (1 + sat) : light + sat - light * sat;
|
|
384
|
+
const p = 2 * light - q;
|
|
385
|
+
function hueToRgb(t) {
|
|
386
|
+
if (t < 0) t += 1;
|
|
387
|
+
if (t > 1) t -= 1;
|
|
388
|
+
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
|
389
|
+
if (t < 1 / 2) return q;
|
|
390
|
+
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
|
391
|
+
return p;
|
|
392
|
+
}
|
|
393
|
+
const r = hueToRgb(hue / 360 + 1 / 3);
|
|
394
|
+
const g = hueToRgb(hue / 360);
|
|
395
|
+
const b = hueToRgb(hue / 360 - 1 / 3);
|
|
396
|
+
return rgbToHex(r * 255, g * 255, b * 255);
|
|
397
|
+
}
|
|
398
|
+
var channelLabels = {
|
|
399
|
+
hex: ["HEX"],
|
|
400
|
+
rgb: ["R", "G", "B"],
|
|
401
|
+
hsl: ["H", "S", "L"]
|
|
402
|
+
};
|
|
403
|
+
function hexToHsv(hex) {
|
|
404
|
+
const rgb = hexToRgb(hex);
|
|
405
|
+
if (!rgb) return null;
|
|
406
|
+
const r = rgb.r / 255;
|
|
407
|
+
const g = rgb.g / 255;
|
|
408
|
+
const b = rgb.b / 255;
|
|
409
|
+
const max = Math.max(r, g, b);
|
|
410
|
+
const min = Math.min(r, g, b);
|
|
411
|
+
const delta = max - min;
|
|
412
|
+
let h = 0;
|
|
413
|
+
if (delta !== 0) {
|
|
414
|
+
if (max === r) {
|
|
415
|
+
h = (g - b) / delta % 6;
|
|
416
|
+
} else if (max === g) {
|
|
417
|
+
h = (b - r) / delta + 2;
|
|
418
|
+
} else {
|
|
419
|
+
h = (r - g) / delta + 4;
|
|
420
|
+
}
|
|
421
|
+
h *= 60;
|
|
422
|
+
if (h < 0) h += 360;
|
|
423
|
+
}
|
|
424
|
+
const s = max === 0 ? 0 : delta / max;
|
|
425
|
+
const v = max;
|
|
426
|
+
return {
|
|
427
|
+
h: Math.round(h),
|
|
428
|
+
s: Math.round(s * 100),
|
|
429
|
+
v: Math.round(v * 100)
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function hsvToHex(h, s, v) {
|
|
433
|
+
const hue = (h % 360 + 360) % 360;
|
|
434
|
+
const sat = clamp(s / 100, 0, 1);
|
|
435
|
+
const val = clamp(v / 100, 0, 1);
|
|
436
|
+
const c = val * sat;
|
|
437
|
+
const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
438
|
+
const m = val - c;
|
|
439
|
+
let rPrime = 0;
|
|
440
|
+
let gPrime = 0;
|
|
441
|
+
let bPrime = 0;
|
|
442
|
+
if (hue < 60) {
|
|
443
|
+
rPrime = c;
|
|
444
|
+
gPrime = x;
|
|
445
|
+
} else if (hue < 120) {
|
|
446
|
+
rPrime = x;
|
|
447
|
+
gPrime = c;
|
|
448
|
+
} else if (hue < 180) {
|
|
449
|
+
gPrime = c;
|
|
450
|
+
bPrime = x;
|
|
451
|
+
} else if (hue < 240) {
|
|
452
|
+
gPrime = x;
|
|
453
|
+
bPrime = c;
|
|
454
|
+
} else if (hue < 300) {
|
|
455
|
+
rPrime = x;
|
|
456
|
+
bPrime = c;
|
|
457
|
+
} else {
|
|
458
|
+
rPrime = c;
|
|
459
|
+
bPrime = x;
|
|
460
|
+
}
|
|
461
|
+
const r = Math.round((rPrime + m) * 255);
|
|
462
|
+
const g = Math.round((gPrime + m) * 255);
|
|
463
|
+
const b = Math.round((bPrime + m) * 255);
|
|
464
|
+
return rgbToHex(r, g, b);
|
|
465
|
+
}
|
|
466
|
+
function getChannelValues(hex, format) {
|
|
467
|
+
var _a;
|
|
468
|
+
const normalized = (_a = normalizeHex(hex)) != null ? _a : "#000000";
|
|
469
|
+
switch (format) {
|
|
470
|
+
case "rgb": {
|
|
471
|
+
const rgb = hexToRgb(normalized);
|
|
472
|
+
if (!rgb) return ["0", "0", "0"];
|
|
473
|
+
return [rgb.r, rgb.g, rgb.b].map((value) => String(value));
|
|
474
|
+
}
|
|
475
|
+
case "hsl": {
|
|
476
|
+
const hsl = hexToHsl(normalized);
|
|
477
|
+
if (!hsl) return ["0", "0", "0"];
|
|
478
|
+
return [hsl.h, hsl.s, hsl.l].map((value) => String(value));
|
|
479
|
+
}
|
|
480
|
+
default: {
|
|
481
|
+
const value = normalized.replace("#", "");
|
|
482
|
+
return [value.toUpperCase()];
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function channelsToHex(values, format) {
|
|
487
|
+
var _a, _b;
|
|
488
|
+
if (values.some((value) => value.trim() === "")) return null;
|
|
489
|
+
switch (format) {
|
|
490
|
+
case "hex": {
|
|
491
|
+
const hex = (_b = (_a = values[0]) == null ? void 0 : _a.trim().replace(/^#/, "")) != null ? _b : "";
|
|
492
|
+
if (!/^[0-9a-fA-F]{3}$/.test(hex) && !/^[0-9a-fA-F]{6}$/.test(hex)) return null;
|
|
493
|
+
return normalizeHex(`#${hex}`);
|
|
494
|
+
}
|
|
495
|
+
case "rgb": {
|
|
496
|
+
const numbers = values.map((value) => Number(value.trim()));
|
|
497
|
+
if (numbers.some((num) => Number.isNaN(num))) return null;
|
|
498
|
+
return rgbToHex(numbers[0], numbers[1], numbers[2]);
|
|
499
|
+
}
|
|
500
|
+
case "hsl": {
|
|
501
|
+
const numbers = values.map((value) => Number(value.trim()));
|
|
502
|
+
if (numbers.some((num) => Number.isNaN(num))) return null;
|
|
503
|
+
return hslToHex(numbers[0], numbers[1], numbers[2]);
|
|
504
|
+
}
|
|
505
|
+
default:
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
var ColorPicker = React5.forwardRef(
|
|
510
|
+
function ColorPicker2({
|
|
511
|
+
label,
|
|
512
|
+
value,
|
|
513
|
+
defaultValue = "#0ea5e9",
|
|
514
|
+
onChange,
|
|
515
|
+
swatches = defaultSwatches,
|
|
516
|
+
id,
|
|
517
|
+
className,
|
|
518
|
+
...rest
|
|
519
|
+
}, forwardedRef) {
|
|
520
|
+
var _a;
|
|
521
|
+
const normalizedDefault = React5.useMemo(
|
|
522
|
+
() => {
|
|
523
|
+
var _a2;
|
|
524
|
+
return (_a2 = normalizeHex(defaultValue)) != null ? _a2 : "#0EA5E9";
|
|
525
|
+
},
|
|
526
|
+
[defaultValue]
|
|
527
|
+
);
|
|
528
|
+
const normalizedSwatches = React5.useMemo(
|
|
529
|
+
() => swatches.map((color) => normalizeHex(color)).filter(Boolean),
|
|
530
|
+
[swatches]
|
|
531
|
+
);
|
|
532
|
+
const isControlled = typeof value === "string";
|
|
533
|
+
const [internalValue, setInternalValue] = React5.useState(normalizedDefault);
|
|
534
|
+
const [format, setFormat] = React5.useState("hex");
|
|
535
|
+
const resolvedValue = React5.useMemo(() => {
|
|
536
|
+
var _a2, _b;
|
|
537
|
+
if (isControlled) {
|
|
538
|
+
return (_a2 = normalizeHex(value)) != null ? _a2 : normalizedDefault;
|
|
539
|
+
}
|
|
540
|
+
return (_b = normalizeHex(internalValue)) != null ? _b : normalizedDefault;
|
|
541
|
+
}, [isControlled, value, internalValue, normalizedDefault]);
|
|
542
|
+
const [channelValues, setChannelValues] = React5.useState(
|
|
543
|
+
() => getChannelValues(resolvedValue, format)
|
|
544
|
+
);
|
|
545
|
+
const [swatchList, setSwatchList] = React5.useState(normalizedSwatches);
|
|
546
|
+
const resolvedHsv = React5.useMemo(
|
|
547
|
+
() => {
|
|
548
|
+
var _a2;
|
|
549
|
+
return (_a2 = hexToHsv(resolvedValue)) != null ? _a2 : { h: 199, s: 100, v: 100 };
|
|
550
|
+
},
|
|
551
|
+
[resolvedValue]
|
|
552
|
+
);
|
|
553
|
+
const [hue, setHue] = React5.useState(resolvedHsv.h);
|
|
554
|
+
const [saturation, setSaturation] = React5.useState(resolvedHsv.s);
|
|
555
|
+
const [valueLevel, setValueLevel] = React5.useState(resolvedHsv.v);
|
|
556
|
+
const generatedId = React5.useId();
|
|
557
|
+
const inputId = id != null ? id : generatedId;
|
|
558
|
+
const inputRef = React5.useRef(null);
|
|
559
|
+
const triggerRef = React5.useRef(null);
|
|
560
|
+
const panelRef = React5.useRef(null);
|
|
561
|
+
const gradientRef = React5.useRef(null);
|
|
562
|
+
const hueRef = React5.useRef(null);
|
|
563
|
+
const gradientDragCleanupRef = React5.useRef(null);
|
|
564
|
+
const hueDragCleanupRef = React5.useRef(null);
|
|
565
|
+
const [isOpen, setIsOpen] = React5.useState(false);
|
|
566
|
+
const mergedRef = React5.useCallback(
|
|
567
|
+
(node) => {
|
|
568
|
+
inputRef.current = node;
|
|
569
|
+
assignRef(forwardedRef, node);
|
|
570
|
+
},
|
|
571
|
+
[forwardedRef]
|
|
572
|
+
);
|
|
573
|
+
const shouldLockHue = (s, v) => s <= 1 || v <= 1;
|
|
574
|
+
React5.useEffect(() => {
|
|
575
|
+
setHue((prev) => shouldLockHue(resolvedHsv.s, resolvedHsv.v) ? prev : resolvedHsv.h);
|
|
576
|
+
setSaturation(resolvedHsv.s);
|
|
577
|
+
setValueLevel(resolvedHsv.v);
|
|
578
|
+
}, [resolvedHsv.h, resolvedHsv.s, resolvedHsv.v]);
|
|
579
|
+
React5.useEffect(() => {
|
|
580
|
+
setSwatchList(normalizedSwatches);
|
|
581
|
+
}, [normalizedSwatches]);
|
|
582
|
+
const applyColor = React5.useCallback(
|
|
583
|
+
(next) => {
|
|
584
|
+
const normalized = normalizeHex(next);
|
|
585
|
+
if (!normalized) return;
|
|
586
|
+
if (!isControlled) {
|
|
587
|
+
setInternalValue(normalized);
|
|
588
|
+
}
|
|
589
|
+
onChange == null ? void 0 : onChange(normalized);
|
|
590
|
+
},
|
|
591
|
+
[isControlled, onChange]
|
|
592
|
+
);
|
|
593
|
+
const handleSwatchClick = (color) => {
|
|
594
|
+
const normalized = normalizeHex(color);
|
|
595
|
+
if (!normalized) return;
|
|
596
|
+
const hsv = hexToHsv(normalized);
|
|
597
|
+
if (hsv) {
|
|
598
|
+
setHue((prev) => shouldLockHue(hsv.s, hsv.v) ? prev : hsv.h);
|
|
599
|
+
setSaturation(hsv.s);
|
|
600
|
+
setValueLevel(hsv.v);
|
|
601
|
+
}
|
|
602
|
+
applyColor(normalized);
|
|
603
|
+
};
|
|
604
|
+
const handleAddSwatch = React5.useCallback(() => {
|
|
605
|
+
const normalized = normalizeHex(resolvedValue);
|
|
606
|
+
if (!normalized) return;
|
|
607
|
+
setSwatchList((prev) => {
|
|
608
|
+
if (prev.includes(normalized)) return prev;
|
|
609
|
+
return [...prev, normalized];
|
|
610
|
+
});
|
|
611
|
+
}, [resolvedValue]);
|
|
612
|
+
const handleRemoveSwatch = React5.useCallback((color) => {
|
|
613
|
+
setSwatchList((prev) => prev.filter((value2) => value2.toLowerCase() !== color.toLowerCase()));
|
|
614
|
+
}, []);
|
|
615
|
+
React5.useEffect(() => {
|
|
616
|
+
setChannelValues(getChannelValues(resolvedValue, format));
|
|
617
|
+
}, [resolvedValue, format]);
|
|
618
|
+
const handleChannelChange = React5.useCallback(
|
|
619
|
+
(index, nextValue) => {
|
|
620
|
+
setChannelValues((prev) => {
|
|
621
|
+
const updated = [...prev];
|
|
622
|
+
if (format === "hex") {
|
|
623
|
+
updated[index] = nextValue.replace(/[^0-9a-fA-F]/g, "").toUpperCase();
|
|
624
|
+
} else {
|
|
625
|
+
updated[index] = nextValue;
|
|
626
|
+
}
|
|
627
|
+
const parsed = channelsToHex(updated, format);
|
|
628
|
+
if (parsed) {
|
|
629
|
+
applyColor(parsed);
|
|
630
|
+
}
|
|
631
|
+
return updated;
|
|
632
|
+
});
|
|
633
|
+
},
|
|
634
|
+
[applyColor, format]
|
|
635
|
+
);
|
|
636
|
+
const resetChannels = React5.useCallback(() => {
|
|
637
|
+
setChannelValues(getChannelValues(resolvedValue, format));
|
|
638
|
+
}, [resolvedValue, format]);
|
|
639
|
+
const closePanel = React5.useCallback(() => {
|
|
640
|
+
setIsOpen(false);
|
|
641
|
+
}, []);
|
|
642
|
+
React5.useEffect(() => {
|
|
643
|
+
if (!isOpen) return;
|
|
644
|
+
const handlePointer = (event) => {
|
|
645
|
+
var _a2, _b;
|
|
646
|
+
const target = event.target;
|
|
647
|
+
if (((_a2 = panelRef.current) == null ? void 0 : _a2.contains(target)) || ((_b = triggerRef.current) == null ? void 0 : _b.contains(target))) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
closePanel();
|
|
651
|
+
};
|
|
652
|
+
const handleKeydown = (event) => {
|
|
653
|
+
var _a2;
|
|
654
|
+
if (event.key === "Escape") {
|
|
655
|
+
event.preventDefault();
|
|
656
|
+
closePanel();
|
|
657
|
+
(_a2 = triggerRef.current) == null ? void 0 : _a2.focus();
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
window.addEventListener("pointerdown", handlePointer);
|
|
661
|
+
window.addEventListener("keydown", handleKeydown);
|
|
662
|
+
return () => {
|
|
663
|
+
window.removeEventListener("pointerdown", handlePointer);
|
|
664
|
+
window.removeEventListener("keydown", handleKeydown);
|
|
665
|
+
};
|
|
666
|
+
}, [closePanel, isOpen]);
|
|
667
|
+
const updateGradientColor = React5.useCallback(
|
|
668
|
+
(clientX, clientY) => {
|
|
669
|
+
var _a2;
|
|
670
|
+
const rect = (_a2 = gradientRef.current) == null ? void 0 : _a2.getBoundingClientRect();
|
|
671
|
+
if (!rect) return;
|
|
672
|
+
const xRatio = clamp((clientX - rect.left) / rect.width, 0, 1);
|
|
673
|
+
const yRatio = clamp((clientY - rect.top) / rect.height, 0, 1);
|
|
674
|
+
const nextS = Math.round(xRatio * 100);
|
|
675
|
+
const nextV = Math.round((1 - yRatio) * 100);
|
|
676
|
+
setSaturation(nextS);
|
|
677
|
+
setValueLevel(nextV);
|
|
678
|
+
applyColor(hsvToHex(hue, nextS, nextV));
|
|
679
|
+
},
|
|
680
|
+
[applyColor, hue]
|
|
681
|
+
);
|
|
682
|
+
const handleGradientPointerDown = React5.useCallback(
|
|
683
|
+
(event) => {
|
|
684
|
+
var _a2;
|
|
685
|
+
event.preventDefault();
|
|
686
|
+
(_a2 = gradientDragCleanupRef.current) == null ? void 0 : _a2.call(gradientDragCleanupRef);
|
|
687
|
+
updateGradientColor(event.clientX, event.clientY);
|
|
688
|
+
const handleMove = (moveEvent) => {
|
|
689
|
+
moveEvent.preventDefault();
|
|
690
|
+
updateGradientColor(moveEvent.clientX, moveEvent.clientY);
|
|
691
|
+
};
|
|
692
|
+
const stop = () => {
|
|
693
|
+
window.removeEventListener("pointermove", handleMove);
|
|
694
|
+
window.removeEventListener("pointerup", stop);
|
|
695
|
+
window.removeEventListener("pointercancel", stop);
|
|
696
|
+
gradientDragCleanupRef.current = null;
|
|
697
|
+
};
|
|
698
|
+
window.addEventListener("pointermove", handleMove);
|
|
699
|
+
window.addEventListener("pointerup", stop);
|
|
700
|
+
window.addEventListener("pointercancel", stop);
|
|
701
|
+
gradientDragCleanupRef.current = stop;
|
|
702
|
+
},
|
|
703
|
+
[updateGradientColor]
|
|
704
|
+
);
|
|
705
|
+
const updateHueFromPointer = React5.useCallback(
|
|
706
|
+
(clientX) => {
|
|
707
|
+
var _a2;
|
|
708
|
+
const rect = (_a2 = hueRef.current) == null ? void 0 : _a2.getBoundingClientRect();
|
|
709
|
+
if (!rect) return;
|
|
710
|
+
const ratio = clamp((clientX - rect.left) / rect.width, 0, 1);
|
|
711
|
+
const nextHue = Math.round(ratio * 360);
|
|
712
|
+
setHue(nextHue);
|
|
713
|
+
applyColor(hsvToHex(nextHue, saturation, valueLevel));
|
|
714
|
+
},
|
|
715
|
+
[applyColor, saturation, valueLevel]
|
|
716
|
+
);
|
|
717
|
+
const handleHuePointerDown = React5.useCallback(
|
|
718
|
+
(event) => {
|
|
719
|
+
var _a2;
|
|
720
|
+
event.preventDefault();
|
|
721
|
+
(_a2 = hueDragCleanupRef.current) == null ? void 0 : _a2.call(hueDragCleanupRef);
|
|
722
|
+
updateHueFromPointer(event.clientX);
|
|
723
|
+
const handleMove = (moveEvent) => {
|
|
724
|
+
moveEvent.preventDefault();
|
|
725
|
+
updateHueFromPointer(moveEvent.clientX);
|
|
726
|
+
};
|
|
727
|
+
const stop = () => {
|
|
728
|
+
window.removeEventListener("pointermove", handleMove);
|
|
729
|
+
window.removeEventListener("pointerup", stop);
|
|
730
|
+
window.removeEventListener("pointercancel", stop);
|
|
731
|
+
hueDragCleanupRef.current = null;
|
|
732
|
+
};
|
|
733
|
+
window.addEventListener("pointermove", handleMove);
|
|
734
|
+
window.addEventListener("pointerup", stop);
|
|
735
|
+
window.addEventListener("pointercancel", stop);
|
|
736
|
+
hueDragCleanupRef.current = stop;
|
|
737
|
+
},
|
|
738
|
+
[updateHueFromPointer]
|
|
739
|
+
);
|
|
740
|
+
React5.useEffect(() => {
|
|
741
|
+
return () => {
|
|
742
|
+
var _a2, _b;
|
|
743
|
+
(_a2 = gradientDragCleanupRef.current) == null ? void 0 : _a2.call(gradientDragCleanupRef);
|
|
744
|
+
(_b = hueDragCleanupRef.current) == null ? void 0 : _b.call(hueDragCleanupRef);
|
|
745
|
+
};
|
|
746
|
+
}, []);
|
|
747
|
+
const activeLabels = channelLabels[format];
|
|
748
|
+
return /* @__PURE__ */ jsxs6("div", { className: "relative inline-flex flex-col items-center gap-2", children: [
|
|
749
|
+
label ? /* @__PURE__ */ jsx7("p", { className: "text-[10px] font-semibold uppercase tracking-[0.3em] text-slate-400 dark:text-zinc-500", children: label }) : null,
|
|
750
|
+
/* @__PURE__ */ jsxs6(
|
|
751
|
+
"button",
|
|
752
|
+
{
|
|
753
|
+
type: "button",
|
|
754
|
+
ref: triggerRef,
|
|
755
|
+
onClick: () => setIsOpen((prev) => !prev),
|
|
756
|
+
"aria-haspopup": "dialog",
|
|
757
|
+
"aria-expanded": isOpen,
|
|
758
|
+
"aria-label": label ? `Adjust ${label}` : "Choose color",
|
|
759
|
+
className: "relative inline-flex h-12 w-12 items-center justify-center rounded-full border border-slate-200 bg-white shadow-sm transition hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:border-zinc-700 dark:bg-zinc-900",
|
|
760
|
+
children: [
|
|
761
|
+
/* @__PURE__ */ jsx7(
|
|
762
|
+
"span",
|
|
763
|
+
{
|
|
764
|
+
className: "absolute inset-1 rounded-full border border-white/30 shadow-inner dark:border-zinc-900/60",
|
|
765
|
+
style: { background: resolvedValue },
|
|
766
|
+
"aria-hidden": "true"
|
|
767
|
+
}
|
|
768
|
+
),
|
|
769
|
+
/* @__PURE__ */ jsx7(
|
|
770
|
+
"span",
|
|
771
|
+
{
|
|
772
|
+
className: "relative text-xl drop-shadow-[0_1px_1px_rgba(15,23,42,0.55)]",
|
|
773
|
+
style: { transform: "translateX(0.5px) translateY(-1.5px)" },
|
|
774
|
+
"aria-hidden": "true",
|
|
775
|
+
children: "\u{1F3A8}"
|
|
776
|
+
}
|
|
777
|
+
)
|
|
778
|
+
]
|
|
779
|
+
}
|
|
780
|
+
),
|
|
781
|
+
isOpen ? /* @__PURE__ */ jsx7(
|
|
782
|
+
"div",
|
|
783
|
+
{
|
|
784
|
+
ref: panelRef,
|
|
785
|
+
role: "dialog",
|
|
786
|
+
"aria-label": label ? `${label} color picker` : "Color picker dialog",
|
|
787
|
+
className: "absolute left-1/2 top-full z-20 mt-3 w-[340px] max-w-[90vw] -translate-x-1/2 rounded-2xl border border-slate-200/80 bg-white/95 p-4 shadow-2xl backdrop-blur-sm dark:border-zinc-700/60 dark:bg-zinc-900/95",
|
|
788
|
+
children: /* @__PURE__ */ jsxs6("div", { className: "space-y-4", children: [
|
|
789
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
|
|
790
|
+
/* @__PURE__ */ jsx7(
|
|
791
|
+
"div",
|
|
792
|
+
{
|
|
793
|
+
ref: gradientRef,
|
|
794
|
+
onPointerDown: handleGradientPointerDown,
|
|
795
|
+
className: "relative h-40 w-full cursor-crosshair overflow-hidden rounded-xl shadow-[inset_0_1px_2px_rgba(15,23,42,.25)] ring-1 ring-black/5 dark:ring-white/10",
|
|
796
|
+
style: {
|
|
797
|
+
backgroundColor: `hsl(${hue}, 100%, 50%)`,
|
|
798
|
+
backgroundImage: "linear-gradient(to top, rgba(0,0,0,1), rgba(0,0,0,0)), linear-gradient(to right, #fff, rgba(255,255,255,0))"
|
|
799
|
+
},
|
|
800
|
+
"aria-label": "Color area",
|
|
801
|
+
children: /* @__PURE__ */ jsx7(
|
|
802
|
+
"span",
|
|
803
|
+
{
|
|
804
|
+
className: "pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-[0_1px_2px_rgba(15,23,42,.4)]",
|
|
805
|
+
style: {
|
|
806
|
+
left: `${saturation}%`,
|
|
807
|
+
top: `${100 - valueLevel}%`,
|
|
808
|
+
background: resolvedValue
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
)
|
|
812
|
+
}
|
|
813
|
+
),
|
|
814
|
+
/* @__PURE__ */ jsx7(
|
|
815
|
+
"div",
|
|
816
|
+
{
|
|
817
|
+
ref: hueRef,
|
|
818
|
+
onPointerDown: handleHuePointerDown,
|
|
819
|
+
className: "relative h-3 w-full cursor-pointer rounded-full border border-white/40 shadow-inner dark:border-zinc-800/60",
|
|
820
|
+
style: {
|
|
821
|
+
background: "linear-gradient(90deg, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
|
|
822
|
+
},
|
|
823
|
+
"aria-label": "Hue slider",
|
|
824
|
+
children: /* @__PURE__ */ jsx7(
|
|
825
|
+
"span",
|
|
826
|
+
{
|
|
827
|
+
className: "pointer-events-none absolute top-1/2 h-5 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full border border-slate-900/40 bg-white shadow",
|
|
828
|
+
style: { left: `${hue / 360 * 100}%` }
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
}
|
|
832
|
+
)
|
|
833
|
+
] }),
|
|
834
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
835
|
+
swatchList.map((color) => /* @__PURE__ */ jsxs6("div", { className: "relative group", children: [
|
|
836
|
+
/* @__PURE__ */ jsx7(
|
|
837
|
+
"button",
|
|
838
|
+
{
|
|
839
|
+
type: "button",
|
|
840
|
+
onClick: () => handleSwatchClick(color),
|
|
841
|
+
className: twMerge6(
|
|
842
|
+
"h-7 w-7 rounded-full border border-white/30 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:border-zinc-900/70",
|
|
843
|
+
color.toLowerCase() === resolvedValue.toLowerCase() && "ring-2 ring-offset-2 ring-slate-500 dark:ring-offset-slate-900"
|
|
844
|
+
),
|
|
845
|
+
style: { background: color },
|
|
846
|
+
"aria-label": `Select ${color}`
|
|
847
|
+
}
|
|
848
|
+
),
|
|
849
|
+
/* @__PURE__ */ jsx7(
|
|
850
|
+
"button",
|
|
851
|
+
{
|
|
852
|
+
type: "button",
|
|
853
|
+
onClick: () => handleRemoveSwatch(color),
|
|
854
|
+
className: "absolute -top-1.5 -right-1.5 hidden h-5 w-5 items-center justify-center rounded-full bg-white text-[10px] font-bold text-slate-600 shadow-sm ring-1 ring-slate-200 transition group-hover:flex group-focus-within:flex dark:bg-zinc-900 dark:text-zinc-200 dark:ring-zinc-700",
|
|
855
|
+
"aria-label": `Remove ${color} swatch`,
|
|
856
|
+
children: "\xD7"
|
|
857
|
+
}
|
|
858
|
+
)
|
|
859
|
+
] }, color)),
|
|
860
|
+
/* @__PURE__ */ jsx7(
|
|
861
|
+
"button",
|
|
862
|
+
{
|
|
863
|
+
type: "button",
|
|
864
|
+
onClick: handleAddSwatch,
|
|
865
|
+
className: "inline-flex h-7 items-center gap-1 rounded-full border border-dashed border-slate-300 px-3 text-xs font-semibold uppercase tracking-wide text-slate-500 transition hover:border-slate-400 hover:text-slate-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 dark:border-zinc-600 dark:text-zinc-300 dark:hover:border-zinc-500",
|
|
866
|
+
children: "+ Add swatch"
|
|
867
|
+
}
|
|
868
|
+
)
|
|
869
|
+
] }),
|
|
870
|
+
/* @__PURE__ */ jsxs6("div", { className: "space-y-2", children: [
|
|
871
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
872
|
+
/* @__PURE__ */ jsx7("p", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-500", children: "Channels" }),
|
|
873
|
+
/* @__PURE__ */ jsx7("div", { className: "inline-flex overflow-hidden rounded-xl border border-slate-200 bg-white/80 text-[11px] font-semibold uppercase text-slate-500 dark:border-zinc-700 dark:bg-zinc-900/80 dark:text-zinc-300", children: formatOrder.map((option) => /* @__PURE__ */ jsx7(
|
|
874
|
+
"button",
|
|
875
|
+
{
|
|
876
|
+
type: "button",
|
|
877
|
+
onClick: () => setFormat(option),
|
|
878
|
+
"aria-pressed": format === option,
|
|
879
|
+
className: twMerge6(
|
|
880
|
+
"px-3 py-1.5 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/60",
|
|
881
|
+
format === option ? "bg-white text-slate-900 shadow-[inset_0_0_0_1px_rgba(15,23,42,0.08)] dark:bg-white/15 dark:text-white" : "text-slate-500 dark:text-zinc-400"
|
|
882
|
+
),
|
|
883
|
+
children: option.toUpperCase()
|
|
884
|
+
},
|
|
885
|
+
option
|
|
886
|
+
)) })
|
|
887
|
+
] }),
|
|
888
|
+
/* @__PURE__ */ jsx7("div", { className: "flex flex-wrap gap-2", children: format === "hex" ? /* @__PURE__ */ jsxs6("label", { className: "flex min-w-[180px] flex-1 items-center gap-2 rounded-xl border border-slate-200 bg-white/60 px-3 py-1.5 font-mono text-sm text-slate-900 focus-within:border-slate-400 dark:border-zinc-700 dark:bg-zinc-900/70 dark:text-zinc-100", children: [
|
|
889
|
+
/* @__PURE__ */ jsx7("span", { className: "text-base font-semibold text-slate-400 dark:text-zinc-500", children: "#" }),
|
|
890
|
+
/* @__PURE__ */ jsx7(
|
|
891
|
+
"input",
|
|
892
|
+
{
|
|
893
|
+
type: "text",
|
|
894
|
+
value: (_a = channelValues[0]) != null ? _a : "",
|
|
895
|
+
onChange: (event) => handleChannelChange(0, event.target.value),
|
|
896
|
+
onBlur: resetChannels,
|
|
897
|
+
inputMode: "text",
|
|
898
|
+
maxLength: 6,
|
|
899
|
+
className: "h-6 min-w-0 flex-1 border-none bg-transparent p-0 text-left text-sm uppercase tracking-[0.2em] text-slate-900 placeholder:text-slate-400 focus:outline-none dark:text-zinc-50"
|
|
900
|
+
}
|
|
901
|
+
)
|
|
902
|
+
] }) : channelValues.map((value2, index) => /* @__PURE__ */ jsxs6(
|
|
903
|
+
"label",
|
|
904
|
+
{
|
|
905
|
+
className: "flex min-w-[90px] flex-1 items-center gap-2 rounded-xl border border-slate-200 bg-white/60 px-3 py-1.5 font-mono text-sm text-slate-900 focus-within:border-slate-400 dark:border-zinc-700 dark:bg-zinc-900/70 dark:text-zinc-100",
|
|
906
|
+
children: [
|
|
907
|
+
/* @__PURE__ */ jsx7("span", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-500", children: activeLabels[index] }),
|
|
908
|
+
/* @__PURE__ */ jsx7(
|
|
909
|
+
"input",
|
|
910
|
+
{
|
|
911
|
+
type: "text",
|
|
912
|
+
value: value2,
|
|
913
|
+
onChange: (event) => handleChannelChange(index, event.target.value),
|
|
914
|
+
onBlur: resetChannels,
|
|
915
|
+
inputMode: "numeric",
|
|
916
|
+
className: "h-6 min-w-0 flex-1 border-none bg-transparent p-0 text-right text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none dark:text-zinc-50"
|
|
917
|
+
}
|
|
918
|
+
)
|
|
919
|
+
]
|
|
920
|
+
},
|
|
921
|
+
activeLabels[index]
|
|
922
|
+
)) })
|
|
923
|
+
] })
|
|
924
|
+
] })
|
|
925
|
+
}
|
|
926
|
+
) : null,
|
|
927
|
+
/* @__PURE__ */ jsx7(
|
|
928
|
+
"input",
|
|
929
|
+
{
|
|
930
|
+
...rest,
|
|
931
|
+
ref: mergedRef,
|
|
932
|
+
id: inputId,
|
|
933
|
+
type: "hidden",
|
|
934
|
+
className,
|
|
935
|
+
value: resolvedValue,
|
|
936
|
+
readOnly: true
|
|
937
|
+
}
|
|
938
|
+
)
|
|
939
|
+
] });
|
|
940
|
+
}
|
|
941
|
+
);
|
|
942
|
+
|
|
943
|
+
// components/Combobox/Combobox.tsx
|
|
944
|
+
import * as React9 from "react";
|
|
945
|
+
import { twMerge as twMerge10 } from "tailwind-merge";
|
|
946
|
+
|
|
947
|
+
// components/Dropdown/Dropdown.tsx
|
|
948
|
+
import * as React6 from "react";
|
|
949
|
+
import { twMerge as twMerge7 } from "tailwind-merge";
|
|
950
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
951
|
+
var Dropdown = React6.forwardRef(
|
|
952
|
+
({
|
|
953
|
+
isOpen,
|
|
954
|
+
disabled,
|
|
955
|
+
placeholder,
|
|
956
|
+
displayValue,
|
|
957
|
+
query,
|
|
958
|
+
className,
|
|
959
|
+
inputClassName,
|
|
960
|
+
highlightClass,
|
|
961
|
+
ariaControls,
|
|
962
|
+
ariaActiveDescendant,
|
|
963
|
+
ariaLabel,
|
|
964
|
+
shellClassName,
|
|
965
|
+
leadingContent,
|
|
966
|
+
inlineContent,
|
|
967
|
+
inputProps,
|
|
968
|
+
inputRef,
|
|
969
|
+
chevronRef,
|
|
970
|
+
showChevron = true,
|
|
971
|
+
onShellFocusCapture,
|
|
972
|
+
onShellBlurCapture,
|
|
973
|
+
onKeyDownCapture,
|
|
974
|
+
onShellMouseDown,
|
|
975
|
+
onInputFocus,
|
|
976
|
+
onInputMouseDown,
|
|
977
|
+
onInputChange,
|
|
978
|
+
onChevronClick,
|
|
979
|
+
children
|
|
980
|
+
}, ref) => {
|
|
981
|
+
const focusClasses = !isOpen ? "focus-within:border-slate-400 focus-within:shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:focus-within:border-slate-500" : "";
|
|
982
|
+
const buttonClasses = "inline-flex h-9 w-10 items-center justify-center rounded-xl bg-transparent text-sm text-slate-600 transition focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 dark:text-zinc-200";
|
|
983
|
+
const inputClasses = twMerge7(
|
|
984
|
+
"w-full bg-transparent text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none dark:text-zinc-100 dark:placeholder:text-zinc-500",
|
|
985
|
+
inputClassName,
|
|
986
|
+
inputProps == null ? void 0 : inputProps.className
|
|
987
|
+
);
|
|
988
|
+
const mergedPlaceholder = placeholder != null ? placeholder : inputProps == null ? void 0 : inputProps.placeholder;
|
|
989
|
+
const mergedAriaLabel = ariaLabel != null ? ariaLabel : inputProps == null ? void 0 : inputProps["aria-label"];
|
|
990
|
+
return /* @__PURE__ */ jsxs7("div", { ref, className: twMerge7("relative w-full", className), children: [
|
|
991
|
+
/* @__PURE__ */ jsxs7(
|
|
992
|
+
"div",
|
|
993
|
+
{
|
|
994
|
+
onKeyDownCapture,
|
|
995
|
+
onMouseDown: onShellMouseDown,
|
|
996
|
+
onFocusCapture: onShellFocusCapture,
|
|
997
|
+
onBlurCapture: onShellBlurCapture,
|
|
998
|
+
className: twMerge7(
|
|
999
|
+
"flex h-11 items-center gap-1 rounded-2xl border border-slate-300 bg-white pl-3 pr-1 text-slate-900 shadow-sm transition dark:border-zinc-700/60 dark:bg-zinc-900/70 dark:text-zinc-100",
|
|
1000
|
+
focusClasses,
|
|
1001
|
+
isOpen && twMerge7("rounded-b-none border-b-0 dark:border-b-0", highlightClass),
|
|
1002
|
+
disabled && "opacity-60",
|
|
1003
|
+
shellClassName
|
|
1004
|
+
),
|
|
1005
|
+
children: [
|
|
1006
|
+
leadingContent ? /* @__PURE__ */ jsx8("div", { className: "flex items-center", children: leadingContent }) : null,
|
|
1007
|
+
/* @__PURE__ */ jsx8(
|
|
1008
|
+
"input",
|
|
1009
|
+
{
|
|
1010
|
+
...inputProps,
|
|
1011
|
+
ref: inputRef,
|
|
1012
|
+
role: "combobox",
|
|
1013
|
+
"aria-expanded": isOpen,
|
|
1014
|
+
"aria-autocomplete": "list",
|
|
1015
|
+
"aria-controls": ariaControls,
|
|
1016
|
+
"aria-activedescendant": ariaActiveDescendant,
|
|
1017
|
+
"aria-label": mergedAriaLabel,
|
|
1018
|
+
disabled: !!disabled || (inputProps == null ? void 0 : inputProps.disabled),
|
|
1019
|
+
readOnly: !isOpen,
|
|
1020
|
+
onMouseDown: onInputMouseDown,
|
|
1021
|
+
value: isOpen ? query : displayValue,
|
|
1022
|
+
onFocus: onInputFocus,
|
|
1023
|
+
onChange: onInputChange,
|
|
1024
|
+
placeholder: mergedPlaceholder,
|
|
1025
|
+
className: inputClasses
|
|
1026
|
+
}
|
|
1027
|
+
),
|
|
1028
|
+
inlineContent ? /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-2", children: inlineContent }) : null,
|
|
1029
|
+
showChevron ? /* @__PURE__ */ jsx8(
|
|
1030
|
+
"button",
|
|
1031
|
+
{
|
|
1032
|
+
ref: chevronRef,
|
|
1033
|
+
type: "button",
|
|
1034
|
+
"aria-label": isOpen ? "Close" : "Open",
|
|
1035
|
+
onClick: onChevronClick,
|
|
1036
|
+
className: buttonClasses,
|
|
1037
|
+
disabled: !!disabled,
|
|
1038
|
+
children: /* @__PURE__ */ jsx8(
|
|
1039
|
+
ChevronDown,
|
|
1040
|
+
{
|
|
1041
|
+
className: twMerge7("size-4 transition-transform", isOpen && "rotate-180")
|
|
1042
|
+
}
|
|
1043
|
+
)
|
|
1044
|
+
}
|
|
1045
|
+
) : null
|
|
1046
|
+
]
|
|
1047
|
+
}
|
|
1048
|
+
),
|
|
1049
|
+
children
|
|
1050
|
+
] });
|
|
1051
|
+
}
|
|
1052
|
+
);
|
|
1053
|
+
Dropdown.displayName = "Dropdown";
|
|
1054
|
+
|
|
1055
|
+
// components/Popover/Popover.tsx
|
|
1056
|
+
import * as React7 from "react";
|
|
1057
|
+
import { twMerge as twMerge8 } from "tailwind-merge";
|
|
1058
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1059
|
+
var MIN_THUMB_SIZE = 28;
|
|
1060
|
+
var TRACK_PADDING = 6;
|
|
1061
|
+
function Scrollbar({ scrollRef }) {
|
|
1062
|
+
const [{ visible, size, offset }, setThumbState] = React7.useState({
|
|
1063
|
+
visible: false,
|
|
1064
|
+
size: MIN_THUMB_SIZE,
|
|
1065
|
+
offset: 0
|
|
1066
|
+
});
|
|
1067
|
+
const dragState = React7.useRef(null);
|
|
1068
|
+
React7.useLayoutEffect(() => {
|
|
1069
|
+
const target = scrollRef.current;
|
|
1070
|
+
if (!target) return;
|
|
1071
|
+
let raf = 0;
|
|
1072
|
+
const update = () => {
|
|
1073
|
+
const el = scrollRef.current;
|
|
1074
|
+
if (!el) return;
|
|
1075
|
+
const { scrollTop, scrollHeight, clientHeight } = el;
|
|
1076
|
+
const scrollRange = scrollHeight - clientHeight;
|
|
1077
|
+
const trackHeight = Math.max(0, clientHeight - TRACK_PADDING * 2);
|
|
1078
|
+
if (scrollRange <= 0 || trackHeight <= 0) {
|
|
1079
|
+
setThumbState((prev) => prev.visible ? { ...prev, visible: false } : prev);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
const ratio = clientHeight / scrollHeight;
|
|
1083
|
+
const thumbSize = Math.max(trackHeight * ratio, MIN_THUMB_SIZE);
|
|
1084
|
+
const maxOffset = Math.max(0, trackHeight - thumbSize);
|
|
1085
|
+
const thumbOffset = maxOffset > 0 ? scrollTop / scrollRange * maxOffset : 0;
|
|
1086
|
+
setThumbState({ visible: true, size: thumbSize, offset: thumbOffset });
|
|
1087
|
+
};
|
|
1088
|
+
const handleScroll = () => {
|
|
1089
|
+
cancelAnimationFrame(raf);
|
|
1090
|
+
raf = window.requestAnimationFrame(update);
|
|
1091
|
+
};
|
|
1092
|
+
target.addEventListener("scroll", handleScroll, { passive: true });
|
|
1093
|
+
update();
|
|
1094
|
+
const resizeObserver = typeof window !== "undefined" && "ResizeObserver" in window ? new ResizeObserver(() => update()) : null;
|
|
1095
|
+
resizeObserver == null ? void 0 : resizeObserver.observe(target);
|
|
1096
|
+
return () => {
|
|
1097
|
+
target.removeEventListener("scroll", handleScroll);
|
|
1098
|
+
resizeObserver == null ? void 0 : resizeObserver.disconnect();
|
|
1099
|
+
cancelAnimationFrame(raf);
|
|
1100
|
+
};
|
|
1101
|
+
}, [scrollRef]);
|
|
1102
|
+
React7.useEffect(() => {
|
|
1103
|
+
const handlePointerMove = (event) => {
|
|
1104
|
+
const drag = dragState.current;
|
|
1105
|
+
if (!drag) return;
|
|
1106
|
+
const el = scrollRef.current;
|
|
1107
|
+
if (!el) return;
|
|
1108
|
+
const trackHeight = Math.max(0, el.clientHeight - TRACK_PADDING * 2);
|
|
1109
|
+
const scrollRange = el.scrollHeight - el.clientHeight;
|
|
1110
|
+
if (scrollRange <= 0 || trackHeight <= drag.thumbSize) return;
|
|
1111
|
+
event.preventDefault();
|
|
1112
|
+
const delta = event.clientY - drag.startY;
|
|
1113
|
+
const ratio = scrollRange / (trackHeight - drag.thumbSize);
|
|
1114
|
+
const nextScroll = drag.startScrollTop + delta * ratio;
|
|
1115
|
+
el.scrollTop = Math.min(scrollRange, Math.max(0, nextScroll));
|
|
1116
|
+
};
|
|
1117
|
+
const handlePointerUp = () => {
|
|
1118
|
+
dragState.current = null;
|
|
1119
|
+
};
|
|
1120
|
+
window.addEventListener("pointermove", handlePointerMove);
|
|
1121
|
+
window.addEventListener("pointerup", handlePointerUp);
|
|
1122
|
+
return () => {
|
|
1123
|
+
window.removeEventListener("pointermove", handlePointerMove);
|
|
1124
|
+
window.removeEventListener("pointerup", handlePointerUp);
|
|
1125
|
+
};
|
|
1126
|
+
}, [scrollRef]);
|
|
1127
|
+
if (!visible) return null;
|
|
1128
|
+
const handleThumbPointerDown = (event) => {
|
|
1129
|
+
var _a, _b;
|
|
1130
|
+
const el = scrollRef.current;
|
|
1131
|
+
if (!el) return;
|
|
1132
|
+
event.preventDefault();
|
|
1133
|
+
event.stopPropagation();
|
|
1134
|
+
dragState.current = {
|
|
1135
|
+
startY: event.clientY,
|
|
1136
|
+
startScrollTop: el.scrollTop,
|
|
1137
|
+
thumbSize: size
|
|
1138
|
+
};
|
|
1139
|
+
(_b = (_a = event.currentTarget).setPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId);
|
|
1140
|
+
};
|
|
1141
|
+
const handleTrackPointerDown = (event) => {
|
|
1142
|
+
var _a, _b;
|
|
1143
|
+
const el = scrollRef.current;
|
|
1144
|
+
if (!el) return;
|
|
1145
|
+
event.preventDefault();
|
|
1146
|
+
event.stopPropagation();
|
|
1147
|
+
const trackRect = event.currentTarget.getBoundingClientRect();
|
|
1148
|
+
const clickOffset = event.clientY - trackRect.top - TRACK_PADDING;
|
|
1149
|
+
const trackLength = trackRect.height - TRACK_PADDING * 2;
|
|
1150
|
+
const scrollRange = el.scrollHeight - el.clientHeight;
|
|
1151
|
+
if (scrollRange <= 0 || trackLength <= 0) return;
|
|
1152
|
+
const effective = Math.max(1, trackLength - size);
|
|
1153
|
+
const ratio = Math.max(0, Math.min(effective, clickOffset - size / 2)) / effective;
|
|
1154
|
+
el.scrollTop = ratio * scrollRange;
|
|
1155
|
+
dragState.current = {
|
|
1156
|
+
startY: event.clientY,
|
|
1157
|
+
startScrollTop: el.scrollTop,
|
|
1158
|
+
thumbSize: size
|
|
1159
|
+
};
|
|
1160
|
+
(_b = (_a = event.currentTarget).setPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId);
|
|
1161
|
+
};
|
|
1162
|
+
return /* @__PURE__ */ jsx9(
|
|
1163
|
+
"div",
|
|
1164
|
+
{
|
|
1165
|
+
"aria-hidden": "true",
|
|
1166
|
+
className: "pointer-events-none absolute inset-y-[6px] right-[2px] flex w-3 justify-center",
|
|
1167
|
+
children: /* @__PURE__ */ jsx9(
|
|
1168
|
+
"div",
|
|
1169
|
+
{
|
|
1170
|
+
className: "pointer-events-auto relative h-full w-1 rounded-full bg-slate-900/5 shadow-inner dark:bg-white/10",
|
|
1171
|
+
onPointerDown: handleTrackPointerDown,
|
|
1172
|
+
children: /* @__PURE__ */ jsx9(
|
|
1173
|
+
"div",
|
|
1174
|
+
{
|
|
1175
|
+
className: "pointer-events-auto absolute left-1/2 w-1.5 -translate-x-1/2 rounded-full bg-slate-400/80 shadow-sm transition-colors dark:bg-zinc-200/70",
|
|
1176
|
+
style: { height: `${size}px`, top: `${offset}px` },
|
|
1177
|
+
onPointerDown: handleThumbPointerDown
|
|
1178
|
+
}
|
|
1179
|
+
)
|
|
1180
|
+
}
|
|
1181
|
+
)
|
|
1182
|
+
}
|
|
1183
|
+
);
|
|
1184
|
+
}
|
|
1185
|
+
function Popover({ className, children }) {
|
|
1186
|
+
const scrollRef = React7.useRef(null);
|
|
1187
|
+
return /* @__PURE__ */ jsx9(
|
|
1188
|
+
"div",
|
|
1189
|
+
{
|
|
1190
|
+
className: twMerge8(
|
|
1191
|
+
"absolute left-0 right-0 top-full z-[999] -mt-px rounded-b-2xl rounded-t-none border border-slate-300 bg-white/95 py-1 shadow-xl shadow-slate-200/60 backdrop-blur dark:border-zinc-600 dark:bg-zinc-900/95 dark:shadow-zinc-900/40",
|
|
1192
|
+
className
|
|
1193
|
+
),
|
|
1194
|
+
children: /* @__PURE__ */ jsxs8("div", { className: "relative", children: [
|
|
1195
|
+
children({ scrollRef }),
|
|
1196
|
+
/* @__PURE__ */ jsx9(Scrollbar, { scrollRef })
|
|
1197
|
+
] })
|
|
1198
|
+
}
|
|
1199
|
+
);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// components/Combobox/hooks.ts
|
|
1203
|
+
import { useCallback as useCallback3, useEffect as useEffect4, useState as useState4 } from "react";
|
|
1204
|
+
function useControlledState(controlled, defaultValue) {
|
|
1205
|
+
const [internal, setInternal] = useState4(defaultValue);
|
|
1206
|
+
const isControlled = controlled !== void 0;
|
|
1207
|
+
const value = isControlled ? controlled : internal;
|
|
1208
|
+
const setValue = useCallback3(
|
|
1209
|
+
(next) => {
|
|
1210
|
+
if (!isControlled) {
|
|
1211
|
+
setInternal(next);
|
|
1212
|
+
}
|
|
1213
|
+
},
|
|
1214
|
+
[isControlled]
|
|
1215
|
+
);
|
|
1216
|
+
return [value, setValue];
|
|
1217
|
+
}
|
|
1218
|
+
function useOutsideClick(refs, handler) {
|
|
1219
|
+
useEffect4(() => {
|
|
1220
|
+
function onDoc(e) {
|
|
1221
|
+
const target = e.target;
|
|
1222
|
+
const inside = refs.some((r) => r.current && r.current.contains(target));
|
|
1223
|
+
if (!inside) handler();
|
|
1224
|
+
}
|
|
1225
|
+
document.addEventListener("mousedown", onDoc);
|
|
1226
|
+
return () => document.removeEventListener("mousedown", onDoc);
|
|
1227
|
+
}, [refs, handler]);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// components/Combobox/Listbox.tsx
|
|
1231
|
+
import * as React8 from "react";
|
|
1232
|
+
import { twMerge as twMerge9 } from "tailwind-merge";
|
|
1233
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1234
|
+
function Listbox({
|
|
1235
|
+
id,
|
|
1236
|
+
options,
|
|
1237
|
+
activeIndex,
|
|
1238
|
+
selectedId,
|
|
1239
|
+
onHoverIndex,
|
|
1240
|
+
onSelectIndex,
|
|
1241
|
+
listRef,
|
|
1242
|
+
emptyState,
|
|
1243
|
+
renderOption
|
|
1244
|
+
}) {
|
|
1245
|
+
React8.useEffect(() => {
|
|
1246
|
+
var _a;
|
|
1247
|
+
if (activeIndex < 0) return;
|
|
1248
|
+
const el = (_a = listRef.current) == null ? void 0 : _a.querySelector(`[data-index="${activeIndex}"]`);
|
|
1249
|
+
el == null ? void 0 : el.scrollIntoView({ block: "nearest" });
|
|
1250
|
+
}, [activeIndex, listRef]);
|
|
1251
|
+
return /* @__PURE__ */ jsxs9(
|
|
1252
|
+
"ul",
|
|
1253
|
+
{
|
|
1254
|
+
ref: listRef,
|
|
1255
|
+
id,
|
|
1256
|
+
role: "listbox",
|
|
1257
|
+
className: twMerge9("combobox-scrollbar max-h-64 overflow-auto px-1 pr-4"),
|
|
1258
|
+
children: [
|
|
1259
|
+
options.length === 0 && /* @__PURE__ */ jsx10("li", { "aria-disabled": true, className: "select-none", children: /* @__PURE__ */ jsx10("div", { className: "px-3 py-2 text-sm text-slate-500 dark:text-zinc-500", children: emptyState != null ? emptyState : "No results" }) }),
|
|
1260
|
+
options.map((opt, i) => {
|
|
1261
|
+
const selected = opt.id === selectedId;
|
|
1262
|
+
const active = i === activeIndex;
|
|
1263
|
+
const optionState = { active, selected };
|
|
1264
|
+
return /* @__PURE__ */ jsx10(
|
|
1265
|
+
"li",
|
|
1266
|
+
{
|
|
1267
|
+
id: `${id}-option-${i}`,
|
|
1268
|
+
role: "option",
|
|
1269
|
+
"aria-selected": selected,
|
|
1270
|
+
"aria-disabled": opt.disabled,
|
|
1271
|
+
"data-index": i,
|
|
1272
|
+
className: "list-none",
|
|
1273
|
+
onMouseEnter: () => !opt.disabled && onHoverIndex(i),
|
|
1274
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
1275
|
+
onClick: () => !opt.disabled && onSelectIndex(i),
|
|
1276
|
+
children: renderOption ? renderOption(opt, optionState) : /* @__PURE__ */ jsxs9(
|
|
1277
|
+
"div",
|
|
1278
|
+
{
|
|
1279
|
+
className: twMerge9(
|
|
1280
|
+
"flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2 text-sm",
|
|
1281
|
+
active ? "bg-slate-100 text-slate-900 dark:bg-zinc-800/70 dark:text-zinc-100" : "text-slate-700 hover:bg-slate-100 dark:text-zinc-200 dark:hover:bg-zinc-800/70",
|
|
1282
|
+
opt.disabled && "cursor-not-allowed opacity-50"
|
|
1283
|
+
),
|
|
1284
|
+
children: [
|
|
1285
|
+
/* @__PURE__ */ jsx10("span", { className: "truncate", children: opt.label }),
|
|
1286
|
+
selected ? /* @__PURE__ */ jsx10(Check, { className: "ml-auto h-3 w-3 text-slate-600 dark:text-zinc-300" }) : /* @__PURE__ */ jsx10("span", { className: "ml-auto inline-flex h-3 w-3" })
|
|
1287
|
+
]
|
|
1288
|
+
}
|
|
1289
|
+
)
|
|
1290
|
+
},
|
|
1291
|
+
opt.id
|
|
1292
|
+
);
|
|
1293
|
+
})
|
|
1294
|
+
]
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// components/Combobox/Combobox.tsx
|
|
1300
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1301
|
+
function InnerCombobox({
|
|
1302
|
+
options,
|
|
1303
|
+
value: valueProp,
|
|
1304
|
+
defaultValue,
|
|
1305
|
+
onChange,
|
|
1306
|
+
placeholder = "Select.",
|
|
1307
|
+
disabled,
|
|
1308
|
+
className,
|
|
1309
|
+
listClassName,
|
|
1310
|
+
inputClassName,
|
|
1311
|
+
emptyState,
|
|
1312
|
+
renderOption,
|
|
1313
|
+
filter,
|
|
1314
|
+
openOnFocus = true,
|
|
1315
|
+
ariaLabel
|
|
1316
|
+
}, forwardedRef) {
|
|
1317
|
+
var _a, _b;
|
|
1318
|
+
const inputRef = React9.useRef(null);
|
|
1319
|
+
const containerRef = React9.useRef(null);
|
|
1320
|
+
const chevronRef = React9.useRef(null);
|
|
1321
|
+
const outsideClickRefs = React9.useMemo(
|
|
1322
|
+
() => [containerRef],
|
|
1323
|
+
[containerRef]
|
|
1324
|
+
);
|
|
1325
|
+
const mergedInputRef = (node) => {
|
|
1326
|
+
inputRef.current = node;
|
|
1327
|
+
assignRef(forwardedRef, node);
|
|
1328
|
+
};
|
|
1329
|
+
const [open, setOpen] = React9.useState(false);
|
|
1330
|
+
const closeOnOutsideClick = React9.useCallback(() => setOpen(false), []);
|
|
1331
|
+
const [query, setQuery] = React9.useState("");
|
|
1332
|
+
const [activeIndex, setActiveIndex] = React9.useState(-1);
|
|
1333
|
+
const suppressNextOpenRef = React9.useRef(false);
|
|
1334
|
+
const suppressToggleRef = React9.useRef(false);
|
|
1335
|
+
const prevOpenRef = React9.useRef(open);
|
|
1336
|
+
const [selected, setSelected] = useControlledState(
|
|
1337
|
+
valueProp,
|
|
1338
|
+
defaultValue != null ? defaultValue : null
|
|
1339
|
+
);
|
|
1340
|
+
const isEffectivelyOpen = open && !suppressNextOpenRef.current;
|
|
1341
|
+
const id = React9.useId();
|
|
1342
|
+
const listboxId = `${id}-listbox`;
|
|
1343
|
+
const normalizedFilter = React9.useMemo(() => {
|
|
1344
|
+
if (filter) return filter;
|
|
1345
|
+
return (opt, q) => opt.label.toLowerCase().includes(q.trim().toLowerCase());
|
|
1346
|
+
}, [filter]);
|
|
1347
|
+
const visibleOptions = React9.useMemo(() => {
|
|
1348
|
+
const base = options != null ? options : [];
|
|
1349
|
+
return query ? base.filter((o) => normalizedFilter(o, query)) : base;
|
|
1350
|
+
}, [options, query, normalizedFilter]);
|
|
1351
|
+
const selectedOptionIndex = React9.useMemo(() => {
|
|
1352
|
+
if (!selected) return -1;
|
|
1353
|
+
const indexFromOptions = (options != null ? options : []).findIndex((option) => option.id === selected.id);
|
|
1354
|
+
if (indexFromOptions !== -1) return indexFromOptions;
|
|
1355
|
+
return visibleOptions.findIndex((option) => option.id === selected.id);
|
|
1356
|
+
}, [options, visibleOptions, selected]);
|
|
1357
|
+
const firstEnabledOptionIndex = React9.useMemo(() => {
|
|
1358
|
+
if (!options) return -1;
|
|
1359
|
+
return options.findIndex((option) => !option.disabled);
|
|
1360
|
+
}, [options]);
|
|
1361
|
+
const lastEnabledOptionIndex = React9.useMemo(() => {
|
|
1362
|
+
if (!options) return -1;
|
|
1363
|
+
for (let i = options.length - 1; i >= 0; i -= 1) {
|
|
1364
|
+
if (!options[i].disabled) {
|
|
1365
|
+
return i;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return -1;
|
|
1369
|
+
}, [options]);
|
|
1370
|
+
const cycleEnabledOptionIndex = React9.useCallback(
|
|
1371
|
+
(startIndex, direction) => {
|
|
1372
|
+
if (!options || options.length === 0) return -1;
|
|
1373
|
+
let next = startIndex;
|
|
1374
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
1375
|
+
next = (next + direction + options.length) % options.length;
|
|
1376
|
+
if (!options[next].disabled) return next;
|
|
1377
|
+
}
|
|
1378
|
+
return -1;
|
|
1379
|
+
},
|
|
1380
|
+
[options]
|
|
1381
|
+
);
|
|
1382
|
+
const selectedIndex = React9.useMemo(() => {
|
|
1383
|
+
if (!selected) return -1;
|
|
1384
|
+
return visibleOptions.findIndex((option) => option.id === selected.id);
|
|
1385
|
+
}, [visibleOptions, selected]);
|
|
1386
|
+
const firstEnabledIndex = React9.useMemo(() => {
|
|
1387
|
+
return visibleOptions.findIndex((option) => !option.disabled);
|
|
1388
|
+
}, [visibleOptions]);
|
|
1389
|
+
const lastEnabledIndex = React9.useMemo(() => {
|
|
1390
|
+
for (let i = visibleOptions.length - 1; i >= 0; i -= 1) {
|
|
1391
|
+
if (!visibleOptions[i].disabled) {
|
|
1392
|
+
return i;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
return -1;
|
|
1396
|
+
}, [visibleOptions]);
|
|
1397
|
+
const cycleEnabledIndex = React9.useCallback(
|
|
1398
|
+
(startIndex, direction) => {
|
|
1399
|
+
if (visibleOptions.length === 0) return -1;
|
|
1400
|
+
let next = startIndex;
|
|
1401
|
+
for (let i = 0; i < visibleOptions.length; i += 1) {
|
|
1402
|
+
next = (next + direction + visibleOptions.length) % visibleOptions.length;
|
|
1403
|
+
if (!visibleOptions[next].disabled) return next;
|
|
1404
|
+
}
|
|
1405
|
+
return -1;
|
|
1406
|
+
},
|
|
1407
|
+
[visibleOptions]
|
|
1408
|
+
);
|
|
1409
|
+
React9.useEffect(() => {
|
|
1410
|
+
if (visibleOptions.length === 0) {
|
|
1411
|
+
setActiveIndex(-1);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (activeIndex >= visibleOptions.length) setActiveIndex(visibleOptions.length - 1);
|
|
1415
|
+
}, [visibleOptions, activeIndex]);
|
|
1416
|
+
React9.useEffect(() => {
|
|
1417
|
+
const wasOpen = prevOpenRef.current;
|
|
1418
|
+
prevOpenRef.current = open;
|
|
1419
|
+
if (open && !wasOpen) {
|
|
1420
|
+
const initialIndex = selectedIndex !== -1 ? selectedIndex : firstEnabledIndex;
|
|
1421
|
+
setActiveIndex(initialIndex);
|
|
1422
|
+
}
|
|
1423
|
+
if (!open && wasOpen) {
|
|
1424
|
+
setActiveIndex(-1);
|
|
1425
|
+
}
|
|
1426
|
+
}, [open, selectedIndex, firstEnabledIndex]);
|
|
1427
|
+
React9.useLayoutEffect(() => {
|
|
1428
|
+
if (open && suppressNextOpenRef.current) {
|
|
1429
|
+
suppressNextOpenRef.current = false;
|
|
1430
|
+
setOpen(false);
|
|
1431
|
+
}
|
|
1432
|
+
}, [open]);
|
|
1433
|
+
useOutsideClick(outsideClickRefs, closeOnOutsideClick);
|
|
1434
|
+
function commitSelection(opt, opts = {}) {
|
|
1435
|
+
const { refocus = true } = opts;
|
|
1436
|
+
setSelected(opt);
|
|
1437
|
+
onChange == null ? void 0 : onChange(opt);
|
|
1438
|
+
if (opt) setQuery("");
|
|
1439
|
+
setOpen(false);
|
|
1440
|
+
if (refocus) {
|
|
1441
|
+
requestAnimationFrame(() => {
|
|
1442
|
+
var _a2;
|
|
1443
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
function onKeyDown(e) {
|
|
1448
|
+
const isArrowDown = e.key === "ArrowDown" || e.code === "ArrowDown" || e.key === "Down";
|
|
1449
|
+
const isArrowUp = e.key === "ArrowUp" || e.code === "ArrowUp" || e.key === "Up";
|
|
1450
|
+
if (isArrowDown || isArrowUp) {
|
|
1451
|
+
e.preventDefault();
|
|
1452
|
+
const direction = isArrowDown ? 1 : -1;
|
|
1453
|
+
if (!open) {
|
|
1454
|
+
suppressNextOpenRef.current = true;
|
|
1455
|
+
const source = options != null ? options : [];
|
|
1456
|
+
if (source.length === 0) return;
|
|
1457
|
+
if (selectedOptionIndex === -1) {
|
|
1458
|
+
const fallbackIndex = direction === 1 ? firstEnabledOptionIndex : lastEnabledOptionIndex;
|
|
1459
|
+
if (fallbackIndex !== -1) {
|
|
1460
|
+
commitSelection(source[fallbackIndex], { refocus: false });
|
|
1461
|
+
}
|
|
1462
|
+
} else {
|
|
1463
|
+
const nextIndex = cycleEnabledOptionIndex(selectedOptionIndex, direction);
|
|
1464
|
+
if (nextIndex !== -1 && nextIndex !== selectedOptionIndex) {
|
|
1465
|
+
commitSelection(source[nextIndex], { refocus: false });
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const baseIndex = activeIndex === -1 ? direction === 1 ? firstEnabledIndex : lastEnabledIndex : activeIndex;
|
|
1471
|
+
if (baseIndex === -1) return;
|
|
1472
|
+
const next = cycleEnabledIndex(baseIndex, direction);
|
|
1473
|
+
if (next !== -1) {
|
|
1474
|
+
setActiveIndex(next);
|
|
1475
|
+
}
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
if (!open && (e.key === "Enter" || e.key === " ")) {
|
|
1479
|
+
e.preventDefault();
|
|
1480
|
+
suppressNextOpenRef.current = false;
|
|
1481
|
+
setOpen(true);
|
|
1482
|
+
return;
|
|
1483
|
+
}
|
|
1484
|
+
if (!open) return;
|
|
1485
|
+
switch (e.key) {
|
|
1486
|
+
case "Home": {
|
|
1487
|
+
if (firstEnabledIndex !== -1) {
|
|
1488
|
+
e.preventDefault();
|
|
1489
|
+
setActiveIndex(firstEnabledIndex);
|
|
1490
|
+
}
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
case "End": {
|
|
1494
|
+
if (lastEnabledIndex !== -1) {
|
|
1495
|
+
e.preventDefault();
|
|
1496
|
+
setActiveIndex(lastEnabledIndex);
|
|
1497
|
+
}
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
case "Enter": {
|
|
1501
|
+
e.preventDefault();
|
|
1502
|
+
if (activeIndex >= 0 && !visibleOptions[activeIndex].disabled) {
|
|
1503
|
+
commitSelection(visibleOptions[activeIndex]);
|
|
1504
|
+
}
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
case "Escape": {
|
|
1508
|
+
e.preventDefault();
|
|
1509
|
+
setOpen(false);
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
const selectedLabel = (_a = selected == null ? void 0 : selected.label) != null ? _a : "";
|
|
1515
|
+
const selectedId = (_b = selected == null ? void 0 : selected.id) != null ? _b : null;
|
|
1516
|
+
const highlightBorder = "border-slate-400 dark:border-slate-500";
|
|
1517
|
+
const listboxHighlight = isEffectivelyOpen ? highlightBorder : "";
|
|
1518
|
+
function openList() {
|
|
1519
|
+
if (disabled) return;
|
|
1520
|
+
suppressNextOpenRef.current = false;
|
|
1521
|
+
setOpen(true);
|
|
1522
|
+
setQuery("");
|
|
1523
|
+
const initialIndex = selectedIndex !== -1 ? selectedIndex : firstEnabledIndex;
|
|
1524
|
+
setActiveIndex(initialIndex);
|
|
1525
|
+
requestAnimationFrame(() => {
|
|
1526
|
+
var _a2;
|
|
1527
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1528
|
+
});
|
|
1529
|
+
}
|
|
1530
|
+
return /* @__PURE__ */ jsx11(
|
|
1531
|
+
Dropdown,
|
|
1532
|
+
{
|
|
1533
|
+
ref: containerRef,
|
|
1534
|
+
isOpen: isEffectivelyOpen,
|
|
1535
|
+
disabled,
|
|
1536
|
+
placeholder,
|
|
1537
|
+
displayValue: selectedLabel,
|
|
1538
|
+
query,
|
|
1539
|
+
className,
|
|
1540
|
+
inputClassName,
|
|
1541
|
+
highlightClass: highlightBorder,
|
|
1542
|
+
ariaControls: listboxId,
|
|
1543
|
+
ariaActiveDescendant: activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0,
|
|
1544
|
+
ariaLabel,
|
|
1545
|
+
inputRef: mergedInputRef,
|
|
1546
|
+
chevronRef,
|
|
1547
|
+
onKeyDownCapture: onKeyDown,
|
|
1548
|
+
onShellMouseDown: (e) => {
|
|
1549
|
+
var _a2;
|
|
1550
|
+
if (disabled) return;
|
|
1551
|
+
if ((_a2 = chevronRef.current) == null ? void 0 : _a2.contains(e.target)) return;
|
|
1552
|
+
if (!isEffectivelyOpen) {
|
|
1553
|
+
e.preventDefault();
|
|
1554
|
+
openList();
|
|
1555
|
+
suppressToggleRef.current = true;
|
|
1556
|
+
}
|
|
1557
|
+
},
|
|
1558
|
+
onInputMouseDown: (e) => {
|
|
1559
|
+
if (!open && !disabled) {
|
|
1560
|
+
e.preventDefault();
|
|
1561
|
+
openList();
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
onInputFocus: () => {
|
|
1565
|
+
if (suppressNextOpenRef.current) {
|
|
1566
|
+
suppressNextOpenRef.current = false;
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
if (openOnFocus && !disabled) {
|
|
1570
|
+
setOpen(true);
|
|
1571
|
+
setQuery("");
|
|
1572
|
+
const initialIndex = selectedIndex !== -1 ? selectedIndex : firstEnabledIndex;
|
|
1573
|
+
setActiveIndex(initialIndex);
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
onInputChange: (e) => {
|
|
1577
|
+
setQuery(e.target.value);
|
|
1578
|
+
if (!open) {
|
|
1579
|
+
suppressNextOpenRef.current = false;
|
|
1580
|
+
setOpen(true);
|
|
1581
|
+
}
|
|
1582
|
+
},
|
|
1583
|
+
onChevronClick: () => {
|
|
1584
|
+
if (disabled) return;
|
|
1585
|
+
if (suppressToggleRef.current) {
|
|
1586
|
+
suppressToggleRef.current = false;
|
|
1587
|
+
setOpen((o) => !o);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
suppressNextOpenRef.current = false;
|
|
1591
|
+
setOpen((o) => !o);
|
|
1592
|
+
},
|
|
1593
|
+
children: isEffectivelyOpen && /* @__PURE__ */ jsx11(Popover, { className: twMerge10(listboxHighlight, listClassName), children: ({ scrollRef }) => /* @__PURE__ */ jsx11(
|
|
1594
|
+
Listbox,
|
|
1595
|
+
{
|
|
1596
|
+
id: listboxId,
|
|
1597
|
+
options: visibleOptions,
|
|
1598
|
+
activeIndex,
|
|
1599
|
+
selectedId,
|
|
1600
|
+
onHoverIndex: setActiveIndex,
|
|
1601
|
+
onSelectIndex: (i) => commitSelection(visibleOptions[i]),
|
|
1602
|
+
listRef: scrollRef,
|
|
1603
|
+
emptyState,
|
|
1604
|
+
renderOption
|
|
1605
|
+
}
|
|
1606
|
+
) })
|
|
1607
|
+
}
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
var Combobox = React9.forwardRef(InnerCombobox);
|
|
1611
|
+
|
|
1612
|
+
// components/DatalistInput/DatalistInput.tsx
|
|
1613
|
+
import * as React10 from "react";
|
|
1614
|
+
import { twMerge as twMerge11 } from "tailwind-merge";
|
|
1615
|
+
import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1616
|
+
var DatalistInput = React10.forwardRef(
|
|
1617
|
+
function DatalistInput2({ label, description, options, className, id, disabled, ...rest }, ref) {
|
|
1618
|
+
var _a, _b, _c;
|
|
1619
|
+
const generatedId = React10.useId();
|
|
1620
|
+
const inputId = id != null ? id : generatedId;
|
|
1621
|
+
const descriptionId = React10.useId();
|
|
1622
|
+
const dropdownRef = React10.useRef(null);
|
|
1623
|
+
const listboxRef = React10.useRef(null);
|
|
1624
|
+
const inputRef = React10.useRef(null);
|
|
1625
|
+
const mergedRef = React10.useCallback(
|
|
1626
|
+
(node) => {
|
|
1627
|
+
inputRef.current = node;
|
|
1628
|
+
assignRef(ref, node);
|
|
1629
|
+
},
|
|
1630
|
+
[ref]
|
|
1631
|
+
);
|
|
1632
|
+
const [query, setQuery] = React10.useState((_b = (_a = rest.defaultValue) == null ? void 0 : _a.toString()) != null ? _b : "");
|
|
1633
|
+
const [open, setOpen] = React10.useState(false);
|
|
1634
|
+
const [activeIndex, setActiveIndex] = React10.useState(-1);
|
|
1635
|
+
const listboxId = `${inputId}-listbox`;
|
|
1636
|
+
const activeDescendant = activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : void 0;
|
|
1637
|
+
const controlledValue = (_c = rest.value) == null ? void 0 : _c.toString();
|
|
1638
|
+
const displayValue = controlledValue != null ? controlledValue : query;
|
|
1639
|
+
useOutsideClick([dropdownRef], () => setOpen(false));
|
|
1640
|
+
const filtered = React10.useMemo(() => {
|
|
1641
|
+
if (!displayValue.trim()) return options;
|
|
1642
|
+
return options.filter((opt) => opt.toLowerCase().includes(displayValue.toLowerCase()));
|
|
1643
|
+
}, [options, displayValue]);
|
|
1644
|
+
React10.useEffect(() => {
|
|
1645
|
+
if (controlledValue !== void 0) {
|
|
1646
|
+
setQuery(controlledValue);
|
|
1647
|
+
}
|
|
1648
|
+
}, [controlledValue]);
|
|
1649
|
+
const emitChange = (val) => {
|
|
1650
|
+
var _a2;
|
|
1651
|
+
setQuery(val);
|
|
1652
|
+
(_a2 = rest.onChange) == null ? void 0 : _a2.call(rest, {
|
|
1653
|
+
target: { value: val }
|
|
1654
|
+
});
|
|
1655
|
+
};
|
|
1656
|
+
const handleKeyDown = (e) => {
|
|
1657
|
+
if (!open && (e.key === "ArrowDown" || e.key === "ArrowUp")) {
|
|
1658
|
+
setOpen(true);
|
|
1659
|
+
setActiveIndex(0);
|
|
1660
|
+
}
|
|
1661
|
+
if (!open) return;
|
|
1662
|
+
switch (e.key) {
|
|
1663
|
+
case "ArrowDown":
|
|
1664
|
+
e.preventDefault();
|
|
1665
|
+
setActiveIndex((prev) => Math.min(filtered.length - 1, prev + 1));
|
|
1666
|
+
break;
|
|
1667
|
+
case "ArrowUp":
|
|
1668
|
+
e.preventDefault();
|
|
1669
|
+
setActiveIndex((prev) => Math.max(0, prev - 1));
|
|
1670
|
+
break;
|
|
1671
|
+
case "Enter":
|
|
1672
|
+
if (activeIndex >= 0 && filtered[activeIndex]) {
|
|
1673
|
+
emitChange(filtered[activeIndex]);
|
|
1674
|
+
setOpen(false);
|
|
1675
|
+
}
|
|
1676
|
+
break;
|
|
1677
|
+
case "Escape":
|
|
1678
|
+
setOpen(false);
|
|
1679
|
+
break;
|
|
1680
|
+
}
|
|
1681
|
+
};
|
|
1682
|
+
const handleSelect = (val) => {
|
|
1683
|
+
var _a2;
|
|
1684
|
+
emitChange(val);
|
|
1685
|
+
setOpen(false);
|
|
1686
|
+
(_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1687
|
+
};
|
|
1688
|
+
const handleInputChange = (e) => {
|
|
1689
|
+
var _a2;
|
|
1690
|
+
setQuery(e.target.value);
|
|
1691
|
+
setOpen(true);
|
|
1692
|
+
setActiveIndex(0);
|
|
1693
|
+
(_a2 = rest.onChange) == null ? void 0 : _a2.call(rest, e);
|
|
1694
|
+
};
|
|
1695
|
+
const setListRef = (node, scrollRef) => {
|
|
1696
|
+
scrollRef.current = node;
|
|
1697
|
+
listboxRef.current = node;
|
|
1698
|
+
};
|
|
1699
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
1700
|
+
label ? /* @__PURE__ */ jsx12(
|
|
1701
|
+
"label",
|
|
1702
|
+
{
|
|
1703
|
+
htmlFor: inputId,
|
|
1704
|
+
className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400",
|
|
1705
|
+
children: label
|
|
1706
|
+
}
|
|
1707
|
+
) : null,
|
|
1708
|
+
/* @__PURE__ */ jsx12(
|
|
1709
|
+
Dropdown,
|
|
1710
|
+
{
|
|
1711
|
+
ref: dropdownRef,
|
|
1712
|
+
isOpen: open,
|
|
1713
|
+
disabled,
|
|
1714
|
+
placeholder: rest.placeholder,
|
|
1715
|
+
displayValue,
|
|
1716
|
+
query: displayValue,
|
|
1717
|
+
inputRef: mergedRef,
|
|
1718
|
+
showChevron: false,
|
|
1719
|
+
inlineContent: /* @__PURE__ */ jsx12("span", { className: "text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-500", children: "CMD" }),
|
|
1720
|
+
onKeyDownCapture: handleKeyDown,
|
|
1721
|
+
onShellMouseDown: () => setOpen(true),
|
|
1722
|
+
onInputFocus: () => setOpen(true),
|
|
1723
|
+
onInputMouseDown: () => setOpen(true),
|
|
1724
|
+
onInputChange: handleInputChange,
|
|
1725
|
+
ariaControls: listboxId,
|
|
1726
|
+
ariaActiveDescendant: activeDescendant,
|
|
1727
|
+
ariaLabel: rest["aria-label"],
|
|
1728
|
+
inputClassName: className,
|
|
1729
|
+
inputProps: {
|
|
1730
|
+
...rest,
|
|
1731
|
+
id: inputId,
|
|
1732
|
+
"aria-describedby": description ? descriptionId : rest["aria-describedby"]
|
|
1733
|
+
},
|
|
1734
|
+
children: open && filtered.length ? /* @__PURE__ */ jsx12(Popover, { children: ({ scrollRef }) => /* @__PURE__ */ jsx12(
|
|
1735
|
+
"ul",
|
|
1736
|
+
{
|
|
1737
|
+
ref: (node) => setListRef(node, scrollRef),
|
|
1738
|
+
id: listboxId,
|
|
1739
|
+
role: "listbox",
|
|
1740
|
+
className: "combobox-scrollbar max-h-56 overflow-auto py-1 text-sm text-slate-800 dark:text-zinc-100",
|
|
1741
|
+
children: filtered.map((opt, index) => /* @__PURE__ */ jsx12(
|
|
1742
|
+
"li",
|
|
1743
|
+
{
|
|
1744
|
+
id: `${listboxId}-option-${index}`,
|
|
1745
|
+
role: "option",
|
|
1746
|
+
"aria-selected": index === activeIndex,
|
|
1747
|
+
children: /* @__PURE__ */ jsxs10(
|
|
1748
|
+
"button",
|
|
1749
|
+
{
|
|
1750
|
+
type: "button",
|
|
1751
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
1752
|
+
onClick: () => handleSelect(opt),
|
|
1753
|
+
className: twMerge11(
|
|
1754
|
+
"flex w-full items-center gap-3 px-3 py-2 text-left transition hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/70 dark:hover:bg-zinc-800/60",
|
|
1755
|
+
index === activeIndex && "bg-slate-50 dark:bg-zinc-800/60"
|
|
1756
|
+
),
|
|
1757
|
+
children: [
|
|
1758
|
+
/* @__PURE__ */ jsx12("span", { className: "text-[10px] uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-500", children: "cmd" }),
|
|
1759
|
+
/* @__PURE__ */ jsx12("span", { className: "font-semibold", children: opt })
|
|
1760
|
+
]
|
|
1761
|
+
}
|
|
1762
|
+
)
|
|
1763
|
+
},
|
|
1764
|
+
opt
|
|
1765
|
+
))
|
|
1766
|
+
}
|
|
1767
|
+
) }) : null
|
|
1768
|
+
}
|
|
1769
|
+
),
|
|
1770
|
+
description ? /* @__PURE__ */ jsx12("p", { id: descriptionId, className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null
|
|
1771
|
+
] });
|
|
1772
|
+
}
|
|
1773
|
+
);
|
|
1774
|
+
|
|
1775
|
+
// components/DatePicker/DatePicker.tsx
|
|
1776
|
+
import * as React12 from "react";
|
|
1777
|
+
import { twMerge as twMerge13 } from "tailwind-merge";
|
|
1778
|
+
|
|
1779
|
+
// components/Select/Select.tsx
|
|
1780
|
+
import * as React11 from "react";
|
|
1781
|
+
import { twMerge as twMerge12 } from "tailwind-merge";
|
|
1782
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1783
|
+
function Select({
|
|
1784
|
+
label,
|
|
1785
|
+
description,
|
|
1786
|
+
error,
|
|
1787
|
+
options,
|
|
1788
|
+
value,
|
|
1789
|
+
defaultValue,
|
|
1790
|
+
onChange,
|
|
1791
|
+
placeholder = "Select an option",
|
|
1792
|
+
className,
|
|
1793
|
+
disabled,
|
|
1794
|
+
leadingContent,
|
|
1795
|
+
inlineContent
|
|
1796
|
+
}) {
|
|
1797
|
+
var _a, _b;
|
|
1798
|
+
const containerRef = React11.useRef(null);
|
|
1799
|
+
const inputRef = React11.useRef(null);
|
|
1800
|
+
const chevronRef = React11.useRef(null);
|
|
1801
|
+
const popoverListRef = React11.useRef(null);
|
|
1802
|
+
const suppressToggleRef = React11.useRef(false);
|
|
1803
|
+
const id = React11.useId();
|
|
1804
|
+
const listboxId = `${id}-listbox`;
|
|
1805
|
+
const [open, setOpen] = React11.useState(false);
|
|
1806
|
+
const [activeIndex, setActiveIndex] = React11.useState(-1);
|
|
1807
|
+
const [selected, setSelected] = useControlledState(value, defaultValue != null ? defaultValue : null);
|
|
1808
|
+
useOutsideClick(
|
|
1809
|
+
[containerRef],
|
|
1810
|
+
() => setOpen(false)
|
|
1811
|
+
);
|
|
1812
|
+
const selectedOption = (_a = options.find((opt) => opt.value === selected)) != null ? _a : null;
|
|
1813
|
+
const selectedIndex = React11.useMemo(
|
|
1814
|
+
() => options.findIndex((opt) => opt.value === selected),
|
|
1815
|
+
[options, selected]
|
|
1816
|
+
);
|
|
1817
|
+
const firstEnabled = React11.useMemo(() => options.findIndex((o) => !o.disabled), [options]);
|
|
1818
|
+
const lastEnabled = React11.useMemo(() => {
|
|
1819
|
+
for (let i = options.length - 1; i >= 0; i -= 1) {
|
|
1820
|
+
if (!options[i].disabled) return i;
|
|
1821
|
+
}
|
|
1822
|
+
return -1;
|
|
1823
|
+
}, [options]);
|
|
1824
|
+
const cycleEnabled = React11.useCallback(
|
|
1825
|
+
(start, dir) => {
|
|
1826
|
+
if (!options.length) return -1;
|
|
1827
|
+
let next = start;
|
|
1828
|
+
for (let i = 0; i < options.length; i += 1) {
|
|
1829
|
+
next = (next + dir + options.length) % options.length;
|
|
1830
|
+
if (!options[next].disabled) return next;
|
|
1831
|
+
}
|
|
1832
|
+
return -1;
|
|
1833
|
+
},
|
|
1834
|
+
[options]
|
|
1835
|
+
);
|
|
1836
|
+
const commitSelection = (index) => {
|
|
1837
|
+
const opt = options[index];
|
|
1838
|
+
if (!opt || opt.disabled) return;
|
|
1839
|
+
setSelected(opt.value);
|
|
1840
|
+
onChange == null ? void 0 : onChange(opt.value);
|
|
1841
|
+
setOpen(false);
|
|
1842
|
+
requestAnimationFrame(() => {
|
|
1843
|
+
var _a2;
|
|
1844
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1845
|
+
});
|
|
1846
|
+
};
|
|
1847
|
+
const openList = () => {
|
|
1848
|
+
if (disabled) return;
|
|
1849
|
+
setOpen(true);
|
|
1850
|
+
setActiveIndex(selectedIndex !== -1 ? selectedIndex : firstEnabled);
|
|
1851
|
+
requestAnimationFrame(() => {
|
|
1852
|
+
var _a2;
|
|
1853
|
+
return (_a2 = inputRef.current) == null ? void 0 : _a2.focus();
|
|
1854
|
+
});
|
|
1855
|
+
};
|
|
1856
|
+
const handleKeyDown = (e) => {
|
|
1857
|
+
const isArrowDown = e.key === "ArrowDown" || e.code === "ArrowDown" || e.key === "Down";
|
|
1858
|
+
const isArrowUp = e.key === "ArrowUp" || e.code === "ArrowUp" || e.key === "Up";
|
|
1859
|
+
if (!open && (isArrowDown || isArrowUp)) {
|
|
1860
|
+
e.preventDefault();
|
|
1861
|
+
const direction = isArrowDown ? 1 : -1;
|
|
1862
|
+
const source = options;
|
|
1863
|
+
if (!source.length) return;
|
|
1864
|
+
const startIndex = selectedIndex === -1 ? direction === 1 ? firstEnabled : lastEnabled : selectedIndex;
|
|
1865
|
+
const nextIndex = startIndex === -1 ? -1 : cycleEnabled(startIndex, direction);
|
|
1866
|
+
if (nextIndex !== -1 && nextIndex !== selectedIndex) {
|
|
1867
|
+
commitSelection(nextIndex);
|
|
1868
|
+
}
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
if (!open && (e.key === "Enter" || e.key === " ")) {
|
|
1872
|
+
e.preventDefault();
|
|
1873
|
+
openList();
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
if (!open) return;
|
|
1877
|
+
switch (e.key) {
|
|
1878
|
+
case "ArrowDown":
|
|
1879
|
+
e.preventDefault();
|
|
1880
|
+
setActiveIndex((prev) => {
|
|
1881
|
+
const start = prev === -1 ? firstEnabled : prev;
|
|
1882
|
+
return cycleEnabled(start, 1);
|
|
1883
|
+
});
|
|
1884
|
+
break;
|
|
1885
|
+
case "ArrowUp":
|
|
1886
|
+
e.preventDefault();
|
|
1887
|
+
setActiveIndex((prev) => {
|
|
1888
|
+
const start = prev === -1 ? lastEnabled : prev;
|
|
1889
|
+
return cycleEnabled(start, -1);
|
|
1890
|
+
});
|
|
1891
|
+
break;
|
|
1892
|
+
case "Home":
|
|
1893
|
+
e.preventDefault();
|
|
1894
|
+
setActiveIndex(firstEnabled);
|
|
1895
|
+
break;
|
|
1896
|
+
case "End":
|
|
1897
|
+
e.preventDefault();
|
|
1898
|
+
setActiveIndex(lastEnabled);
|
|
1899
|
+
break;
|
|
1900
|
+
case "Enter":
|
|
1901
|
+
case " ":
|
|
1902
|
+
e.preventDefault();
|
|
1903
|
+
if (activeIndex >= 0) commitSelection(activeIndex);
|
|
1904
|
+
break;
|
|
1905
|
+
case "Escape":
|
|
1906
|
+
e.preventDefault();
|
|
1907
|
+
setOpen(false);
|
|
1908
|
+
break;
|
|
1909
|
+
}
|
|
1910
|
+
};
|
|
1911
|
+
React11.useEffect(() => {
|
|
1912
|
+
var _a2;
|
|
1913
|
+
if (!open) return;
|
|
1914
|
+
const refObj = popoverListRef.current;
|
|
1915
|
+
const el = (_a2 = refObj == null ? void 0 : refObj.current) == null ? void 0 : _a2.querySelector(`[data-index="${activeIndex}"]`);
|
|
1916
|
+
el == null ? void 0 : el.scrollIntoView({ block: "nearest" });
|
|
1917
|
+
}, [open, activeIndex]);
|
|
1918
|
+
const displayValue = (_b = selectedOption == null ? void 0 : selectedOption.label) != null ? _b : "";
|
|
1919
|
+
const highlightBorder = "border-slate-400 dark:border-slate-500";
|
|
1920
|
+
const listboxHighlight = open ? highlightBorder : "";
|
|
1921
|
+
return /* @__PURE__ */ jsxs11("div", { className: "space-y-1.5", children: [
|
|
1922
|
+
label ? /* @__PURE__ */ jsx13("p", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400", children: label }) : null,
|
|
1923
|
+
/* @__PURE__ */ jsx13(
|
|
1924
|
+
Dropdown,
|
|
1925
|
+
{
|
|
1926
|
+
ref: containerRef,
|
|
1927
|
+
isOpen: open,
|
|
1928
|
+
disabled,
|
|
1929
|
+
placeholder,
|
|
1930
|
+
displayValue,
|
|
1931
|
+
query: displayValue,
|
|
1932
|
+
className: twMerge12(
|
|
1933
|
+
"w-full",
|
|
1934
|
+
error && "border-rose-300 focus-within:border-rose-400 focus-within:shadow-[0_0_0_1px_rgba(248,113,113,0.35)] dark:border-rose-500/60",
|
|
1935
|
+
className
|
|
1936
|
+
),
|
|
1937
|
+
inputClassName: "font-semibold",
|
|
1938
|
+
highlightClass: highlightBorder,
|
|
1939
|
+
ariaControls: listboxId,
|
|
1940
|
+
ariaLabel: label,
|
|
1941
|
+
inputRef,
|
|
1942
|
+
chevronRef,
|
|
1943
|
+
leadingContent,
|
|
1944
|
+
inlineContent,
|
|
1945
|
+
onKeyDownCapture: handleKeyDown,
|
|
1946
|
+
onShellMouseDown: (e) => {
|
|
1947
|
+
var _a2;
|
|
1948
|
+
if (disabled) return;
|
|
1949
|
+
if ((_a2 = chevronRef.current) == null ? void 0 : _a2.contains(e.target)) return;
|
|
1950
|
+
if (!open) {
|
|
1951
|
+
e.preventDefault();
|
|
1952
|
+
openList();
|
|
1953
|
+
suppressToggleRef.current = true;
|
|
1954
|
+
}
|
|
1955
|
+
},
|
|
1956
|
+
onInputMouseDown: (e) => {
|
|
1957
|
+
if (!open && !disabled) {
|
|
1958
|
+
e.preventDefault();
|
|
1959
|
+
openList();
|
|
1960
|
+
}
|
|
1961
|
+
},
|
|
1962
|
+
onInputFocus: () => {
|
|
1963
|
+
if (suppressToggleRef.current) {
|
|
1964
|
+
suppressToggleRef.current = false;
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
onInputChange: () => {
|
|
1968
|
+
},
|
|
1969
|
+
onChevronClick: () => {
|
|
1970
|
+
if (disabled) return;
|
|
1971
|
+
if (suppressToggleRef.current) {
|
|
1972
|
+
suppressToggleRef.current = false;
|
|
1973
|
+
setOpen((o) => !o);
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
setOpen((o) => !o);
|
|
1977
|
+
},
|
|
1978
|
+
children: open && /* @__PURE__ */ jsx13(Popover, { className: listboxHighlight, children: ({ scrollRef }) => {
|
|
1979
|
+
popoverListRef.current = scrollRef;
|
|
1980
|
+
return /* @__PURE__ */ jsx13(
|
|
1981
|
+
"ul",
|
|
1982
|
+
{
|
|
1983
|
+
ref: scrollRef,
|
|
1984
|
+
id: listboxId,
|
|
1985
|
+
role: "listbox",
|
|
1986
|
+
className: "combobox-scrollbar max-h-64 overflow-auto px-1 pr-4",
|
|
1987
|
+
children: options.map((opt, index) => {
|
|
1988
|
+
const isSelected = selected === opt.value;
|
|
1989
|
+
const isActive = activeIndex === index;
|
|
1990
|
+
return /* @__PURE__ */ jsx13("li", { className: "list-none", "data-index": index, children: /* @__PURE__ */ jsxs11(
|
|
1991
|
+
"button",
|
|
1992
|
+
{
|
|
1993
|
+
type: "button",
|
|
1994
|
+
role: "option",
|
|
1995
|
+
"aria-selected": isSelected,
|
|
1996
|
+
disabled: opt.disabled,
|
|
1997
|
+
onMouseEnter: () => !opt.disabled && setActiveIndex(index),
|
|
1998
|
+
onClick: () => commitSelection(index),
|
|
1999
|
+
className: twMerge12(
|
|
2000
|
+
"flex w-full items-center gap-3 rounded-xl px-3 py-2 text-left text-sm transition",
|
|
2001
|
+
isActive ? "bg-slate-100 text-slate-900 dark:bg-zinc-800/70 dark:text-zinc-100" : "text-slate-700 hover:bg-slate-100 dark:text-zinc-200 dark:hover:bg-zinc-800/70",
|
|
2002
|
+
isSelected && "font-semibold",
|
|
2003
|
+
opt.disabled && "cursor-not-allowed opacity-50"
|
|
2004
|
+
),
|
|
2005
|
+
children: [
|
|
2006
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex-1 text-left", children: [
|
|
2007
|
+
/* @__PURE__ */ jsx13("span", { className: "block text-slate-900 dark:text-zinc-100", children: opt.label }),
|
|
2008
|
+
opt.description ? /* @__PURE__ */ jsx13("span", { className: "block text-xs text-slate-500 dark:text-zinc-400", children: opt.description }) : null
|
|
2009
|
+
] }),
|
|
2010
|
+
isSelected ? /* @__PURE__ */ jsx13(Check, { className: "ml-auto h-3 w-3 text-slate-600 dark:text-zinc-300" }) : /* @__PURE__ */ jsx13("span", { className: "ml-auto inline-flex h-3 w-3" })
|
|
2011
|
+
]
|
|
2012
|
+
}
|
|
2013
|
+
) }, opt.value);
|
|
2014
|
+
})
|
|
2015
|
+
}
|
|
2016
|
+
);
|
|
2017
|
+
} })
|
|
2018
|
+
}
|
|
2019
|
+
),
|
|
2020
|
+
description ? /* @__PURE__ */ jsx13("p", { className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2021
|
+
error ? /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-rose-500 dark:text-rose-400", children: error }) : null
|
|
2022
|
+
] });
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// components/DatePicker/DatePicker.tsx
|
|
2026
|
+
import { Fragment, jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2027
|
+
function formatLocalDateString(year, month, day) {
|
|
2028
|
+
const y = String(year).padStart(4, "0");
|
|
2029
|
+
const m = String(month + 1).padStart(2, "0");
|
|
2030
|
+
const d = String(day).padStart(2, "0");
|
|
2031
|
+
return `${y}-${m}-${d}`;
|
|
2032
|
+
}
|
|
2033
|
+
function parseLocalDateString(value) {
|
|
2034
|
+
if (!value) return null;
|
|
2035
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
|
2036
|
+
if (!match) return null;
|
|
2037
|
+
const [, y, m, d] = match;
|
|
2038
|
+
const year = Number(y);
|
|
2039
|
+
const month = Number(m) - 1;
|
|
2040
|
+
const day = Number(d);
|
|
2041
|
+
const date = new Date(year, month, day);
|
|
2042
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
2043
|
+
return { date, year, month, day };
|
|
2044
|
+
}
|
|
2045
|
+
function getMonthDays(year, month) {
|
|
2046
|
+
const first = new Date(year, month, 1);
|
|
2047
|
+
const start = first.getDay();
|
|
2048
|
+
const days = new Date(year, month + 1, 0).getDate();
|
|
2049
|
+
const grid = [];
|
|
2050
|
+
for (let i = 0; i < start; i += 1) grid.push({ day: null });
|
|
2051
|
+
for (let d = 1; d <= days; d += 1) {
|
|
2052
|
+
const date = new Date(year, month, d);
|
|
2053
|
+
grid.push({ day: d, date: date.toISOString().slice(0, 10) });
|
|
2054
|
+
}
|
|
2055
|
+
return grid;
|
|
2056
|
+
}
|
|
2057
|
+
var DatePicker = React12.forwardRef(function DatePicker2({
|
|
2058
|
+
label,
|
|
2059
|
+
description,
|
|
2060
|
+
error,
|
|
2061
|
+
className,
|
|
2062
|
+
disabled,
|
|
2063
|
+
value,
|
|
2064
|
+
defaultValue,
|
|
2065
|
+
onChange,
|
|
2066
|
+
type = "date",
|
|
2067
|
+
timeIntervalMinutes = 30,
|
|
2068
|
+
use24HourClock = true
|
|
2069
|
+
}, ref) {
|
|
2070
|
+
const isDate = type === "date";
|
|
2071
|
+
const clampInterval = Math.max(1, Math.min(60, Math.floor(timeIntervalMinutes)));
|
|
2072
|
+
const normalizeTimeString = (input) => {
|
|
2073
|
+
if (!input) return null;
|
|
2074
|
+
const match = /(\d{1,2}):(\d{2})/.exec(input);
|
|
2075
|
+
if (!match) return null;
|
|
2076
|
+
const h = Math.min(23, Math.max(0, Number(match[1])));
|
|
2077
|
+
const m = Math.min(59, Math.max(0, Number(match[2])));
|
|
2078
|
+
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
2079
|
+
};
|
|
2080
|
+
const formatTimeLabel = React12.useCallback(
|
|
2081
|
+
(time) => {
|
|
2082
|
+
if (use24HourClock) return time;
|
|
2083
|
+
const [hStr, m] = time.split(":");
|
|
2084
|
+
let hNum = Number(hStr);
|
|
2085
|
+
const suffix = hNum >= 12 ? "PM" : "AM";
|
|
2086
|
+
hNum = hNum % 12 || 12;
|
|
2087
|
+
return `${String(hNum).padStart(2, "0")}:${m} ${suffix}`;
|
|
2088
|
+
},
|
|
2089
|
+
[use24HourClock]
|
|
2090
|
+
);
|
|
2091
|
+
const initial = (() => {
|
|
2092
|
+
var _a, _b, _c;
|
|
2093
|
+
if (!isDate) {
|
|
2094
|
+
return (_b = (_a = normalizeTimeString(value)) != null ? _a : normalizeTimeString(defaultValue)) != null ? _b : (() => {
|
|
2095
|
+
const now = /* @__PURE__ */ new Date();
|
|
2096
|
+
const minutes = now.getMinutes();
|
|
2097
|
+
const rounded = Math.round(minutes / clampInterval) * clampInterval;
|
|
2098
|
+
const h = rounded >= 60 ? now.getHours() + 1 : now.getHours();
|
|
2099
|
+
const m = rounded % 60;
|
|
2100
|
+
const safeH = (h + 24) % 24;
|
|
2101
|
+
return `${String(safeH).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
|
2102
|
+
})();
|
|
2103
|
+
}
|
|
2104
|
+
const normalized = (_c = parseLocalDateString(value != null ? value : defaultValue)) == null ? void 0 : _c.date;
|
|
2105
|
+
if (normalized)
|
|
2106
|
+
return formatLocalDateString(
|
|
2107
|
+
normalized.getFullYear(),
|
|
2108
|
+
normalized.getMonth(),
|
|
2109
|
+
normalized.getDate()
|
|
2110
|
+
);
|
|
2111
|
+
const today = /* @__PURE__ */ new Date();
|
|
2112
|
+
return formatLocalDateString(today.getFullYear(), today.getMonth(), today.getDate());
|
|
2113
|
+
})();
|
|
2114
|
+
const [open, setOpen] = React12.useState(false);
|
|
2115
|
+
const [current, setCurrent] = React12.useState(initial);
|
|
2116
|
+
const [month, setMonth] = React12.useState(() => {
|
|
2117
|
+
var _a, _b;
|
|
2118
|
+
const base = (_b = (_a = parseLocalDateString(initial)) == null ? void 0 : _a.date) != null ? _b : /* @__PURE__ */ new Date();
|
|
2119
|
+
return { year: base.getFullYear(), month: base.getMonth() };
|
|
2120
|
+
});
|
|
2121
|
+
const [viewMode, setViewMode] = React12.useState("day");
|
|
2122
|
+
const dropdownRef = React12.useRef(null);
|
|
2123
|
+
const toggleRef = React12.useRef(null);
|
|
2124
|
+
const inputInnerRef = React12.useRef(null);
|
|
2125
|
+
const suppressToggleRef = React12.useRef(false);
|
|
2126
|
+
const canvasRef = React12.useRef(null);
|
|
2127
|
+
const [useShortLabel, setUseShortLabel] = React12.useState(false);
|
|
2128
|
+
const id = React12.useId();
|
|
2129
|
+
const popoverId = `${id}-popover`;
|
|
2130
|
+
const setInputRef = React12.useCallback(
|
|
2131
|
+
(node) => {
|
|
2132
|
+
inputInnerRef.current = node;
|
|
2133
|
+
assignRef(ref, node);
|
|
2134
|
+
},
|
|
2135
|
+
[ref]
|
|
2136
|
+
);
|
|
2137
|
+
useOutsideClick(
|
|
2138
|
+
[dropdownRef],
|
|
2139
|
+
() => setOpen(false)
|
|
2140
|
+
);
|
|
2141
|
+
const commit = (next) => {
|
|
2142
|
+
setCurrent(next);
|
|
2143
|
+
onChange == null ? void 0 : onChange(next);
|
|
2144
|
+
setOpen(false);
|
|
2145
|
+
setViewMode("day");
|
|
2146
|
+
};
|
|
2147
|
+
const parsedDate = React12.useMemo(() => {
|
|
2148
|
+
var _a;
|
|
2149
|
+
const parsed = parseLocalDateString(current);
|
|
2150
|
+
return (_a = parsed == null ? void 0 : parsed.date) != null ? _a : null;
|
|
2151
|
+
}, [current]);
|
|
2152
|
+
const longDisplayLabel = React12.useMemo(() => {
|
|
2153
|
+
if (!parsedDate) return current;
|
|
2154
|
+
return parsedDate.toLocaleDateString(void 0, {
|
|
2155
|
+
weekday: "short",
|
|
2156
|
+
month: "short",
|
|
2157
|
+
day: "numeric",
|
|
2158
|
+
year: "numeric"
|
|
2159
|
+
});
|
|
2160
|
+
}, [parsedDate, current]);
|
|
2161
|
+
const shortDisplayLabel = React12.useMemo(() => {
|
|
2162
|
+
if (!parsedDate) return current;
|
|
2163
|
+
return parsedDate.toLocaleDateString(void 0, {
|
|
2164
|
+
month: "2-digit",
|
|
2165
|
+
day: "2-digit",
|
|
2166
|
+
year: "2-digit"
|
|
2167
|
+
});
|
|
2168
|
+
}, [parsedDate, current]);
|
|
2169
|
+
const displayLabel = useShortLabel ? shortDisplayLabel : longDisplayLabel;
|
|
2170
|
+
const timeOptions = React12.useMemo(() => {
|
|
2171
|
+
const opts = [];
|
|
2172
|
+
for (let h = 0; h < 24; h += 1) {
|
|
2173
|
+
for (let m = 0; m < 60; m += clampInterval) {
|
|
2174
|
+
const hh = String(h).padStart(2, "0");
|
|
2175
|
+
const mm = String(m).padStart(2, "0");
|
|
2176
|
+
const valueStr = `${hh}:${mm}`;
|
|
2177
|
+
opts.push({ value: valueStr, label: formatTimeLabel(valueStr) });
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
return opts;
|
|
2181
|
+
}, [clampInterval, formatTimeLabel]);
|
|
2182
|
+
const highlightBorder = "border-slate-400 shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-slate-500";
|
|
2183
|
+
const monthNames = React12.useMemo(
|
|
2184
|
+
() => ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
|
|
2185
|
+
[]
|
|
2186
|
+
);
|
|
2187
|
+
const decadeStart = Math.floor(month.year / 10) * 10;
|
|
2188
|
+
const goPrev = () => {
|
|
2189
|
+
if (viewMode === "day") {
|
|
2190
|
+
setMonth((prev) => ({
|
|
2191
|
+
year: prev.month === 0 ? prev.year - 1 : prev.year,
|
|
2192
|
+
month: prev.month === 0 ? 11 : prev.month - 1
|
|
2193
|
+
}));
|
|
2194
|
+
} else if (viewMode === "month") {
|
|
2195
|
+
setMonth((prev) => ({ ...prev, year: prev.year - 1 }));
|
|
2196
|
+
} else {
|
|
2197
|
+
setMonth((prev) => ({ ...prev, year: prev.year - 10 }));
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
const goNext = () => {
|
|
2201
|
+
if (viewMode === "day") {
|
|
2202
|
+
setMonth((prev) => ({
|
|
2203
|
+
year: prev.month === 11 ? prev.year + 1 : prev.year,
|
|
2204
|
+
month: prev.month === 11 ? 0 : prev.month + 1
|
|
2205
|
+
}));
|
|
2206
|
+
} else if (viewMode === "month") {
|
|
2207
|
+
setMonth((prev) => ({ ...prev, year: prev.year + 1 }));
|
|
2208
|
+
} else {
|
|
2209
|
+
setMonth((prev) => ({ ...prev, year: prev.year + 10 }));
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
const rangeStart = decadeStart - 1;
|
|
2213
|
+
const rangeEnd = decadeStart + 10;
|
|
2214
|
+
const headerLabel = viewMode === "year" ? `${rangeStart} - ${rangeEnd}` : viewMode === "month" ? `${month.year}` : new Date(month.year, month.month).toLocaleString("default", {
|
|
2215
|
+
month: "long",
|
|
2216
|
+
year: "numeric"
|
|
2217
|
+
});
|
|
2218
|
+
function openPicker() {
|
|
2219
|
+
if (disabled) return;
|
|
2220
|
+
const base = parsedDate != null ? parsedDate : /* @__PURE__ */ new Date();
|
|
2221
|
+
setMonth({ year: base.getFullYear(), month: base.getMonth() });
|
|
2222
|
+
setViewMode("day");
|
|
2223
|
+
setOpen(true);
|
|
2224
|
+
}
|
|
2225
|
+
const evaluateLabelFit = React12.useCallback(() => {
|
|
2226
|
+
var _a;
|
|
2227
|
+
if (!parsedDate) {
|
|
2228
|
+
setUseShortLabel(false);
|
|
2229
|
+
return;
|
|
2230
|
+
}
|
|
2231
|
+
const inputEl = inputInnerRef.current;
|
|
2232
|
+
if (!inputEl) return;
|
|
2233
|
+
const inputStyles = getComputedStyle(inputEl);
|
|
2234
|
+
const padding = parseFloat(inputStyles.paddingLeft || "0") + parseFloat(inputStyles.paddingRight || "0");
|
|
2235
|
+
const available = inputEl.clientWidth - padding;
|
|
2236
|
+
if (available <= 0) return;
|
|
2237
|
+
const font = inputStyles.font || `${inputStyles.fontWeight} ${inputStyles.fontSize} ${inputStyles.fontFamily}`;
|
|
2238
|
+
const canvas = (_a = canvasRef.current) != null ? _a : document.createElement("canvas");
|
|
2239
|
+
canvasRef.current = canvas;
|
|
2240
|
+
const ctx = canvas.getContext("2d");
|
|
2241
|
+
if (!ctx) return;
|
|
2242
|
+
ctx.font = font;
|
|
2243
|
+
const fullWidth = ctx.measureText(longDisplayLabel).width;
|
|
2244
|
+
const fitsLong = fullWidth <= available;
|
|
2245
|
+
setUseShortLabel(!fitsLong);
|
|
2246
|
+
}, [parsedDate, longDisplayLabel]);
|
|
2247
|
+
React12.useLayoutEffect(() => {
|
|
2248
|
+
evaluateLabelFit();
|
|
2249
|
+
const ro = new ResizeObserver(() => evaluateLabelFit());
|
|
2250
|
+
if (dropdownRef.current) ro.observe(dropdownRef.current);
|
|
2251
|
+
return () => ro.disconnect();
|
|
2252
|
+
}, [evaluateLabelFit]);
|
|
2253
|
+
React12.useEffect(() => {
|
|
2254
|
+
const id2 = requestAnimationFrame(() => evaluateLabelFit());
|
|
2255
|
+
return () => cancelAnimationFrame(id2);
|
|
2256
|
+
}, [displayLabel, evaluateLabelFit]);
|
|
2257
|
+
if (!isDate) {
|
|
2258
|
+
return /* @__PURE__ */ jsx14(
|
|
2259
|
+
Select,
|
|
2260
|
+
{
|
|
2261
|
+
label,
|
|
2262
|
+
description,
|
|
2263
|
+
error,
|
|
2264
|
+
options: timeOptions,
|
|
2265
|
+
value: current,
|
|
2266
|
+
onChange: (val) => commit(val != null ? val : current),
|
|
2267
|
+
placeholder: "Select time",
|
|
2268
|
+
disabled,
|
|
2269
|
+
className,
|
|
2270
|
+
leadingContent: /* @__PURE__ */ jsx14("span", { className: "pointer-events-none rounded-xl bg-slate-100 px-2 py-1 shadow-inner dark:bg-zinc-800/70", children: /* @__PURE__ */ jsx14(Clock, { className: "h-4 w-4 text-slate-500 dark:text-zinc-300" }) })
|
|
2271
|
+
}
|
|
2272
|
+
);
|
|
2273
|
+
}
|
|
2274
|
+
return /* @__PURE__ */ jsxs12("div", { className: "space-y-1.5", children: [
|
|
2275
|
+
label ? /* @__PURE__ */ jsx14("p", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400", children: label }) : null,
|
|
2276
|
+
/* @__PURE__ */ jsx14("div", { className: "relative", children: /* @__PURE__ */ jsx14(
|
|
2277
|
+
Dropdown,
|
|
2278
|
+
{
|
|
2279
|
+
ref: dropdownRef,
|
|
2280
|
+
isOpen: open,
|
|
2281
|
+
disabled,
|
|
2282
|
+
placeholder: "",
|
|
2283
|
+
displayValue: displayLabel,
|
|
2284
|
+
query: displayLabel,
|
|
2285
|
+
className: "w-full",
|
|
2286
|
+
inputClassName: twMerge13("min-w-0 font-semibold", className),
|
|
2287
|
+
shellClassName: twMerge13(
|
|
2288
|
+
error && "border-rose-300 focus-within:border-rose-400 focus-within:shadow-[0_0_0_1px_rgba(248,113,113,0.35)] dark:border-rose-500/60"
|
|
2289
|
+
),
|
|
2290
|
+
leadingContent: /* @__PURE__ */ jsx14("span", { className: "pointer-events-none rounded-xl bg-slate-100 px-2 py-1 shadow-inner dark:bg-zinc-800/70", children: /* @__PURE__ */ jsx14(Calendar, { className: "h-4 w-4 text-slate-500 dark:text-zinc-300" }) }),
|
|
2291
|
+
highlightClass: highlightBorder,
|
|
2292
|
+
ariaControls: popoverId,
|
|
2293
|
+
ariaLabel: label,
|
|
2294
|
+
inputRef: setInputRef,
|
|
2295
|
+
chevronRef: toggleRef,
|
|
2296
|
+
onKeyDownCapture: () => {
|
|
2297
|
+
},
|
|
2298
|
+
onShellMouseDown: (e) => {
|
|
2299
|
+
var _a;
|
|
2300
|
+
if (disabled) return;
|
|
2301
|
+
if ((_a = toggleRef.current) == null ? void 0 : _a.contains(e.target)) return;
|
|
2302
|
+
if (!open) {
|
|
2303
|
+
e.preventDefault();
|
|
2304
|
+
suppressToggleRef.current = true;
|
|
2305
|
+
openPicker();
|
|
2306
|
+
requestAnimationFrame(() => {
|
|
2307
|
+
var _a2;
|
|
2308
|
+
return (_a2 = toggleRef.current) == null ? void 0 : _a2.focus();
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
},
|
|
2312
|
+
onInputMouseDown: (e) => {
|
|
2313
|
+
if (!open && !disabled) {
|
|
2314
|
+
e.preventDefault();
|
|
2315
|
+
suppressToggleRef.current = true;
|
|
2316
|
+
openPicker();
|
|
2317
|
+
requestAnimationFrame(() => {
|
|
2318
|
+
var _a;
|
|
2319
|
+
return (_a = toggleRef.current) == null ? void 0 : _a.focus();
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
},
|
|
2323
|
+
onInputFocus: () => {
|
|
2324
|
+
if (suppressToggleRef.current) {
|
|
2325
|
+
suppressToggleRef.current = false;
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
2328
|
+
openPicker();
|
|
2329
|
+
},
|
|
2330
|
+
onInputChange: () => {
|
|
2331
|
+
},
|
|
2332
|
+
onChevronClick: () => {
|
|
2333
|
+
if (disabled) return;
|
|
2334
|
+
if (suppressToggleRef.current) {
|
|
2335
|
+
suppressToggleRef.current = false;
|
|
2336
|
+
setOpen((o) => !o);
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
setOpen((o) => !o);
|
|
2340
|
+
},
|
|
2341
|
+
children: open && /* @__PURE__ */ jsx14(Popover, { className: twMerge13("p-3", highlightBorder), children: () => /* @__PURE__ */ jsxs12("div", { className: "space-y-3", id: popoverId, children: [
|
|
2342
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between text-sm text-slate-600 dark:text-zinc-300", children: [
|
|
2343
|
+
/* @__PURE__ */ jsx14(
|
|
2344
|
+
Button,
|
|
2345
|
+
{
|
|
2346
|
+
type: "button",
|
|
2347
|
+
onClick: goPrev,
|
|
2348
|
+
className: "h-8 w-8 min-w-0 rounded-xl border border-slate-200 bg-white p-0 text-sm font-semibold text-slate-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2349
|
+
children: /* @__PURE__ */ jsx14("span", { style: { transform: "translateY(-1.5px)" }, children: "<" })
|
|
2350
|
+
}
|
|
2351
|
+
),
|
|
2352
|
+
viewMode === "year" ? /* @__PURE__ */ jsx14("span", { className: "font-semibold text-slate-900 dark:text-zinc-100", children: headerLabel }) : /* @__PURE__ */ jsx14(
|
|
2353
|
+
"button",
|
|
2354
|
+
{
|
|
2355
|
+
type: "button",
|
|
2356
|
+
onClick: () => setViewMode((prev) => prev === "day" ? "month" : "year"),
|
|
2357
|
+
className: "font-semibold text-slate-900 transition hover:underline dark:text-zinc-100",
|
|
2358
|
+
children: headerLabel
|
|
2359
|
+
}
|
|
2360
|
+
),
|
|
2361
|
+
/* @__PURE__ */ jsx14(
|
|
2362
|
+
Button,
|
|
2363
|
+
{
|
|
2364
|
+
type: "button",
|
|
2365
|
+
onClick: goNext,
|
|
2366
|
+
className: "h-8 w-8 min-w-0 rounded-xl border border-slate-200 bg-white p-0 text-sm font-semibold text-slate-700 shadow-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2367
|
+
children: /* @__PURE__ */ jsx14("span", { style: { transform: "translateY(-1.5px)" }, children: ">" })
|
|
2368
|
+
}
|
|
2369
|
+
)
|
|
2370
|
+
] }),
|
|
2371
|
+
viewMode === "day" ? /* @__PURE__ */ jsxs12(Fragment, { children: [
|
|
2372
|
+
/* @__PURE__ */ jsx14("div", { className: "grid grid-cols-7 gap-1 text-center text-[11px] uppercase tracking-[0.2em] text-slate-400 dark:text-zinc-500", children: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"].map((d) => /* @__PURE__ */ jsx14("span", { children: d }, d)) }),
|
|
2373
|
+
/* @__PURE__ */ jsx14("div", { className: "grid grid-cols-7 gap-1", children: getMonthDays(month.year, month.month).map((cell, idx) => {
|
|
2374
|
+
var _a, _b;
|
|
2375
|
+
const isSelected = cell.date === current;
|
|
2376
|
+
return /* @__PURE__ */ jsx14(
|
|
2377
|
+
"button",
|
|
2378
|
+
{
|
|
2379
|
+
type: "button",
|
|
2380
|
+
disabled: !cell.date,
|
|
2381
|
+
onClick: () => {
|
|
2382
|
+
if (!cell.date) return;
|
|
2383
|
+
commit(cell.date);
|
|
2384
|
+
},
|
|
2385
|
+
className: twMerge13(
|
|
2386
|
+
"h-9 rounded-xl text-sm font-semibold text-slate-700 transition hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/70 dark:text-zinc-200 dark:hover:bg-zinc-800",
|
|
2387
|
+
isSelected && "bg-slate-200 text-slate-900 ring-1 ring-slate-400/70 shadow-sm dark:bg-zinc-800 dark:hover:bg-zinc-700/80 dark:text-zinc-100 dark:ring-zinc-500/70",
|
|
2388
|
+
!cell.date && "opacity-30"
|
|
2389
|
+
),
|
|
2390
|
+
children: (_b = cell.day) != null ? _b : ""
|
|
2391
|
+
},
|
|
2392
|
+
`${(_a = cell.date) != null ? _a : "empty"}-${idx}`
|
|
2393
|
+
);
|
|
2394
|
+
}) })
|
|
2395
|
+
] }) : viewMode === "month" ? /* @__PURE__ */ jsx14("div", { className: "grid grid-cols-3 gap-2", children: monthNames.map((name, idx) => {
|
|
2396
|
+
const isSelected = (parsedDate == null ? void 0 : parsedDate.getFullYear()) === month.year && (parsedDate == null ? void 0 : parsedDate.getMonth()) === idx;
|
|
2397
|
+
return /* @__PURE__ */ jsx14(
|
|
2398
|
+
"button",
|
|
2399
|
+
{
|
|
2400
|
+
type: "button",
|
|
2401
|
+
onClick: () => {
|
|
2402
|
+
setMonth((prev) => ({ ...prev, month: idx }));
|
|
2403
|
+
setViewMode("day");
|
|
2404
|
+
},
|
|
2405
|
+
className: twMerge13(
|
|
2406
|
+
"h-10 rounded-xl border border-slate-200 bg-white text-sm font-semibold text-slate-700 shadow-sm transition hover:-translate-y-[1px] hover:shadow-md dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2407
|
+
isSelected && "border-slate-400 bg-slate-100 dark:border-slate-500 dark:bg-zinc-800"
|
|
2408
|
+
),
|
|
2409
|
+
children: name
|
|
2410
|
+
},
|
|
2411
|
+
name
|
|
2412
|
+
);
|
|
2413
|
+
}) }) : /* @__PURE__ */ jsx14("div", { className: "grid grid-cols-3 gap-2", children: Array.from({ length: 12 }, (_, i) => rangeStart + i).map((yr) => {
|
|
2414
|
+
const isSelected = (parsedDate == null ? void 0 : parsedDate.getFullYear()) === yr;
|
|
2415
|
+
return /* @__PURE__ */ jsx14(
|
|
2416
|
+
"button",
|
|
2417
|
+
{
|
|
2418
|
+
type: "button",
|
|
2419
|
+
onClick: () => {
|
|
2420
|
+
setMonth((prev) => ({ ...prev, year: yr }));
|
|
2421
|
+
setViewMode("month");
|
|
2422
|
+
},
|
|
2423
|
+
className: twMerge13(
|
|
2424
|
+
"h-10 rounded-xl border border-slate-200 bg-white text-sm font-semibold text-slate-700 shadow-sm transition hover:-translate-y-[1px] hover:shadow-md dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2425
|
+
isSelected && "border-slate-400 bg-slate-100 dark:border-slate-500 dark:bg-zinc-800"
|
|
2426
|
+
),
|
|
2427
|
+
children: yr
|
|
2428
|
+
},
|
|
2429
|
+
yr
|
|
2430
|
+
);
|
|
2431
|
+
}) })
|
|
2432
|
+
] }) })
|
|
2433
|
+
}
|
|
2434
|
+
) }),
|
|
2435
|
+
description ? /* @__PURE__ */ jsx14("p", { className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2436
|
+
error ? /* @__PURE__ */ jsx14("p", { className: "text-xs font-medium text-rose-500 dark:text-rose-400", children: error }) : null
|
|
2437
|
+
] });
|
|
2438
|
+
});
|
|
2439
|
+
|
|
2440
|
+
// components/Dialog/Dialog.tsx
|
|
2441
|
+
import * as React13 from "react";
|
|
2442
|
+
import { createPortal } from "react-dom";
|
|
2443
|
+
import { twMerge as twMerge14 } from "tailwind-merge";
|
|
2444
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2445
|
+
function Dialog({
|
|
2446
|
+
open,
|
|
2447
|
+
onClose,
|
|
2448
|
+
title,
|
|
2449
|
+
description,
|
|
2450
|
+
children,
|
|
2451
|
+
footer,
|
|
2452
|
+
modal = true,
|
|
2453
|
+
draggable = true
|
|
2454
|
+
}) {
|
|
2455
|
+
const overlayRef = React13.useRef(null);
|
|
2456
|
+
const dialogRef = React13.useRef(null);
|
|
2457
|
+
const dragState = React13.useRef(null);
|
|
2458
|
+
const [dragOffset, setDragOffset] = React13.useState({ x: 0, y: 0 });
|
|
2459
|
+
React13.useEffect(() => {
|
|
2460
|
+
if (!open) return;
|
|
2461
|
+
const handleKey = (event) => {
|
|
2462
|
+
if (event.key === "Escape") {
|
|
2463
|
+
event.preventDefault();
|
|
2464
|
+
onClose();
|
|
2465
|
+
}
|
|
2466
|
+
};
|
|
2467
|
+
window.addEventListener("keydown", handleKey);
|
|
2468
|
+
return () => window.removeEventListener("keydown", handleKey);
|
|
2469
|
+
}, [open, onClose]);
|
|
2470
|
+
React13.useEffect(() => {
|
|
2471
|
+
var _a;
|
|
2472
|
+
if (!open) return;
|
|
2473
|
+
const previouslyFocused = document.activeElement;
|
|
2474
|
+
(_a = dialogRef.current) == null ? void 0 : _a.focus();
|
|
2475
|
+
return () => previouslyFocused == null ? void 0 : previouslyFocused.focus();
|
|
2476
|
+
}, [open]);
|
|
2477
|
+
React13.useEffect(() => {
|
|
2478
|
+
if (!open || modal) return;
|
|
2479
|
+
const handlePointer = (event) => {
|
|
2480
|
+
const target = event.target;
|
|
2481
|
+
if (dialogRef.current && !dialogRef.current.contains(target)) {
|
|
2482
|
+
onClose();
|
|
2483
|
+
}
|
|
2484
|
+
};
|
|
2485
|
+
document.addEventListener("pointerdown", handlePointer);
|
|
2486
|
+
return () => document.removeEventListener("pointerdown", handlePointer);
|
|
2487
|
+
}, [open, modal, onClose]);
|
|
2488
|
+
React13.useEffect(() => {
|
|
2489
|
+
if (!open) {
|
|
2490
|
+
setDragOffset({ x: 0, y: 0 });
|
|
2491
|
+
}
|
|
2492
|
+
}, [open]);
|
|
2493
|
+
React13.useEffect(() => {
|
|
2494
|
+
return () => {
|
|
2495
|
+
dragState.current = null;
|
|
2496
|
+
};
|
|
2497
|
+
}, []);
|
|
2498
|
+
const clampOffset = React13.useCallback(
|
|
2499
|
+
(x, y, size) => {
|
|
2500
|
+
if (!modal) return { x, y };
|
|
2501
|
+
const maxX = Math.max(0, (window.innerWidth - size.width) / 2);
|
|
2502
|
+
const maxY = Math.max(0, (window.innerHeight - size.height) / 2);
|
|
2503
|
+
return {
|
|
2504
|
+
x: Math.min(maxX, Math.max(-maxX, x)),
|
|
2505
|
+
y: Math.min(maxY, Math.max(-maxY, y))
|
|
2506
|
+
};
|
|
2507
|
+
},
|
|
2508
|
+
[modal]
|
|
2509
|
+
);
|
|
2510
|
+
const isInteractiveTarget = (node) => {
|
|
2511
|
+
if (!node) return false;
|
|
2512
|
+
if (node.closest("[data-dialog-draggable-stop]")) return true;
|
|
2513
|
+
const tag = node.tagName.toLowerCase();
|
|
2514
|
+
const interactive = ["button", "input", "select", "textarea", "option", "a", "label"];
|
|
2515
|
+
if (interactive.includes(tag)) return true;
|
|
2516
|
+
if (node.getAttribute("role") === "button" || node.getAttribute("role") === "link") return true;
|
|
2517
|
+
if (node.isContentEditable) return true;
|
|
2518
|
+
return false;
|
|
2519
|
+
};
|
|
2520
|
+
const onDragPointerDown = (event) => {
|
|
2521
|
+
var _a;
|
|
2522
|
+
if (!draggable) return;
|
|
2523
|
+
if (isInteractiveTarget(event.target)) return;
|
|
2524
|
+
event.preventDefault();
|
|
2525
|
+
const rect = (_a = dialogRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
2526
|
+
if (!rect) return;
|
|
2527
|
+
dragState.current = {
|
|
2528
|
+
startX: event.clientX,
|
|
2529
|
+
startY: event.clientY,
|
|
2530
|
+
originX: dragOffset.x,
|
|
2531
|
+
originY: dragOffset.y,
|
|
2532
|
+
width: rect.width,
|
|
2533
|
+
height: rect.height
|
|
2534
|
+
};
|
|
2535
|
+
const handleMove = (e) => {
|
|
2536
|
+
const current = dragState.current;
|
|
2537
|
+
if (!current) return;
|
|
2538
|
+
const deltaX = e.clientX - current.startX;
|
|
2539
|
+
const deltaY = e.clientY - current.startY;
|
|
2540
|
+
const next = {
|
|
2541
|
+
x: current.originX + deltaX,
|
|
2542
|
+
y: current.originY + deltaY
|
|
2543
|
+
};
|
|
2544
|
+
const clamped = clampOffset(next.x, next.y, { width: current.width, height: current.height });
|
|
2545
|
+
setDragOffset(clamped);
|
|
2546
|
+
};
|
|
2547
|
+
const handleUp = () => {
|
|
2548
|
+
dragState.current = null;
|
|
2549
|
+
window.removeEventListener("pointermove", handleMove);
|
|
2550
|
+
window.removeEventListener("pointerup", handleUp);
|
|
2551
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
2552
|
+
};
|
|
2553
|
+
window.addEventListener("pointermove", handleMove);
|
|
2554
|
+
window.addEventListener("pointerup", handleUp);
|
|
2555
|
+
window.addEventListener("pointercancel", handleUp);
|
|
2556
|
+
};
|
|
2557
|
+
if (!open) return null;
|
|
2558
|
+
const dialogCard = /* @__PURE__ */ jsxs13(
|
|
2559
|
+
"div",
|
|
2560
|
+
{
|
|
2561
|
+
ref: dialogRef,
|
|
2562
|
+
role: "dialog",
|
|
2563
|
+
"aria-modal": modal || void 0,
|
|
2564
|
+
tabIndex: -1,
|
|
2565
|
+
"aria-label": title,
|
|
2566
|
+
className: twMerge14(
|
|
2567
|
+
"w-[min(640px,92vw)] rounded-3xl border border-slate-200 bg-white/95 p-6 shadow-2xl ring-1 ring-white/80 dark:border-zinc-700/60 dark:bg-zinc-900/95 dark:ring-zinc-800/80",
|
|
2568
|
+
modal ? "" : "pointer-events-auto",
|
|
2569
|
+
draggable && "cursor-grab active:cursor-grabbing"
|
|
2570
|
+
),
|
|
2571
|
+
style: { transform: `translate(${dragOffset.x}px, ${dragOffset.y}px)` },
|
|
2572
|
+
onPointerDown: onDragPointerDown,
|
|
2573
|
+
children: [
|
|
2574
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-start justify-between gap-3", children: [
|
|
2575
|
+
/* @__PURE__ */ jsxs13("div", { children: [
|
|
2576
|
+
/* @__PURE__ */ jsx15("p", { className: "text-xs font-semibold uppercase tracking-[0.3em] text-slate-500 dark:text-zinc-400", children: "Dialog" }),
|
|
2577
|
+
/* @__PURE__ */ jsx15("h2", { className: "text-xl font-semibold text-slate-900 dark:text-zinc-100", children: title }),
|
|
2578
|
+
description ? /* @__PURE__ */ jsx15("p", { className: "mt-1 text-sm text-slate-600 dark:text-zinc-400", children: description }) : null
|
|
2579
|
+
] }),
|
|
2580
|
+
/* @__PURE__ */ jsx15(
|
|
2581
|
+
Button,
|
|
2582
|
+
{
|
|
2583
|
+
onClick: onClose,
|
|
2584
|
+
"aria-label": "Close dialog",
|
|
2585
|
+
className: "size-8 !px-0 !py-0 text-sm font-semibold",
|
|
2586
|
+
children: "X"
|
|
2587
|
+
}
|
|
2588
|
+
)
|
|
2589
|
+
] }),
|
|
2590
|
+
/* @__PURE__ */ jsx15("div", { className: "mt-4 space-y-3 text-sm text-slate-700 dark:text-zinc-300", children }),
|
|
2591
|
+
footer ? /* @__PURE__ */ jsx15("div", { className: "mt-6 flex flex-wrap justify-end gap-2", children: footer }) : null
|
|
2592
|
+
]
|
|
2593
|
+
}
|
|
2594
|
+
);
|
|
2595
|
+
return createPortal(
|
|
2596
|
+
modal ? /* @__PURE__ */ jsx15(
|
|
2597
|
+
"div",
|
|
2598
|
+
{
|
|
2599
|
+
ref: overlayRef,
|
|
2600
|
+
className: "fixed inset-0 z-50 flex items-center justify-center bg-zinc-950/60 backdrop-blur-sm",
|
|
2601
|
+
onMouseDown: (e) => {
|
|
2602
|
+
if (e.target === overlayRef.current) onClose();
|
|
2603
|
+
},
|
|
2604
|
+
children: dialogCard
|
|
2605
|
+
}
|
|
2606
|
+
) : /* @__PURE__ */ jsx15("div", { className: "pointer-events-none fixed bottom-6 right-6 z-50 flex justify-end", children: dialogCard }),
|
|
2607
|
+
document.body
|
|
2608
|
+
);
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
// components/Disclosure/Disclosure.tsx
|
|
2612
|
+
import * as React14 from "react";
|
|
2613
|
+
import { twMerge as twMerge15 } from "tailwind-merge";
|
|
2614
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2615
|
+
var Disclosure = React14.forwardRef(function Disclosure2({ title, children, className, subtle, ...rest }, ref) {
|
|
2616
|
+
const summaryId = React14.useId();
|
|
2617
|
+
return /* @__PURE__ */ jsxs14(
|
|
2618
|
+
"details",
|
|
2619
|
+
{
|
|
2620
|
+
...rest,
|
|
2621
|
+
ref,
|
|
2622
|
+
className: twMerge15(
|
|
2623
|
+
"group rounded-2xl border border-slate-200 bg-white/90 p-3 shadow-sm transition dark:border-zinc-700/60 dark:bg-zinc-900/70",
|
|
2624
|
+
subtle && "border-transparent bg-white/60 shadow-none dark:bg-zinc-900/40",
|
|
2625
|
+
className
|
|
2626
|
+
),
|
|
2627
|
+
children: [
|
|
2628
|
+
/* @__PURE__ */ jsxs14(
|
|
2629
|
+
"summary",
|
|
2630
|
+
{
|
|
2631
|
+
id: summaryId,
|
|
2632
|
+
className: "flex cursor-pointer list-none items-center justify-between gap-3 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400/70",
|
|
2633
|
+
children: [
|
|
2634
|
+
/* @__PURE__ */ jsx16("span", { className: "flex flex-1 items-center gap-2", children: /* @__PURE__ */ jsx16("span", { className: "rounded-lg bg-slate-100 px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-slate-500 shadow-inner dark:bg-zinc-800/70 dark:text-zinc-300", children: title }) }),
|
|
2635
|
+
/* @__PURE__ */ jsx16(
|
|
2636
|
+
"span",
|
|
2637
|
+
{
|
|
2638
|
+
className: "text-slate-400 transition-transform duration-200 group-open:rotate-180 dark:text-zinc-500",
|
|
2639
|
+
"aria-hidden": "true",
|
|
2640
|
+
children: "\u25BE"
|
|
2641
|
+
}
|
|
2642
|
+
)
|
|
2643
|
+
]
|
|
2644
|
+
}
|
|
2645
|
+
),
|
|
2646
|
+
/* @__PURE__ */ jsx16("div", { className: "mt-3 space-y-2 text-sm text-slate-600 dark:text-zinc-300", children })
|
|
2647
|
+
]
|
|
2648
|
+
}
|
|
2649
|
+
);
|
|
2650
|
+
});
|
|
2651
|
+
|
|
2652
|
+
// components/InputField/InputField.tsx
|
|
2653
|
+
import * as React15 from "react";
|
|
2654
|
+
import { twMerge as twMerge16 } from "tailwind-merge";
|
|
2655
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2656
|
+
var InputField = React15.forwardRef(function InputField2({ label, description, error, leadingIcon, trailingLabel, className, id, disabled, ...rest }, ref) {
|
|
2657
|
+
const generatedId = React15.useId();
|
|
2658
|
+
const inputId = id != null ? id : generatedId;
|
|
2659
|
+
const descriptionId = React15.useId();
|
|
2660
|
+
const errorId = React15.useId();
|
|
2661
|
+
const hintIds = [description ? descriptionId : null, error ? errorId : null].filter(Boolean);
|
|
2662
|
+
const resolvedAriaDescribedBy = hintIds.length ? hintIds.join(" ") : void 0;
|
|
2663
|
+
const containerClasses = twMerge16(
|
|
2664
|
+
"flex items-center gap-3 rounded-2xl border border-slate-300 bg-white/70 px-3 py-2 transition focus-within:border-slate-400 focus-within:shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-700 dark:bg-zinc-900/70 dark:focus-within:border-slate-500",
|
|
2665
|
+
disabled && "opacity-60",
|
|
2666
|
+
error && "border-rose-300 focus-within:border-rose-400 focus-within:shadow-[0_0_0_1px_rgba(248,113,113,0.35)] dark:border-rose-500/60"
|
|
2667
|
+
);
|
|
2668
|
+
const inputClasses = twMerge16(
|
|
2669
|
+
"flex-1 border-none bg-transparent text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none disabled:cursor-not-allowed dark:text-zinc-100 dark:placeholder:text-zinc-500",
|
|
2670
|
+
className
|
|
2671
|
+
);
|
|
2672
|
+
const leadingElm = leadingIcon ? /* @__PURE__ */ jsx17("span", { className: "text-slate-400 dark:text-zinc-500", "aria-hidden": "true", children: leadingIcon }) : null;
|
|
2673
|
+
const trailingElm = trailingLabel ? /* @__PURE__ */ jsx17("span", { className: "text-xs font-semibold uppercase tracking-wide text-slate-400 dark:text-zinc-500", children: trailingLabel }) : null;
|
|
2674
|
+
return /* @__PURE__ */ jsxs15("div", { className: "space-y-1.5", children: [
|
|
2675
|
+
label ? /* @__PURE__ */ jsx17(
|
|
2676
|
+
"label",
|
|
2677
|
+
{
|
|
2678
|
+
htmlFor: inputId,
|
|
2679
|
+
className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400",
|
|
2680
|
+
children: label
|
|
2681
|
+
}
|
|
2682
|
+
) : null,
|
|
2683
|
+
/* @__PURE__ */ jsxs15("div", { className: containerClasses, children: [
|
|
2684
|
+
leadingElm,
|
|
2685
|
+
/* @__PURE__ */ jsx17(
|
|
2686
|
+
"input",
|
|
2687
|
+
{
|
|
2688
|
+
...rest,
|
|
2689
|
+
ref,
|
|
2690
|
+
id: inputId,
|
|
2691
|
+
className: inputClasses,
|
|
2692
|
+
"aria-invalid": error ? true : void 0,
|
|
2693
|
+
"aria-describedby": resolvedAriaDescribedBy,
|
|
2694
|
+
disabled
|
|
2695
|
+
}
|
|
2696
|
+
),
|
|
2697
|
+
trailingElm
|
|
2698
|
+
] }),
|
|
2699
|
+
description ? /* @__PURE__ */ jsx17("p", { id: descriptionId, className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2700
|
+
error ? /* @__PURE__ */ jsx17("p", { id: errorId, className: "text-xs font-medium text-rose-500 dark:text-rose-400", children: error }) : null
|
|
2701
|
+
] });
|
|
2702
|
+
});
|
|
2703
|
+
|
|
2704
|
+
// components/NumberInput/NumberInput.tsx
|
|
2705
|
+
import * as React16 from "react";
|
|
2706
|
+
import { twMerge as twMerge17 } from "tailwind-merge";
|
|
2707
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2708
|
+
var NumberInput = React16.forwardRef(
|
|
2709
|
+
function NumberInput2({
|
|
2710
|
+
label,
|
|
2711
|
+
description,
|
|
2712
|
+
error,
|
|
2713
|
+
className,
|
|
2714
|
+
disabled,
|
|
2715
|
+
value,
|
|
2716
|
+
defaultValue = 0,
|
|
2717
|
+
onChange,
|
|
2718
|
+
step = 1,
|
|
2719
|
+
suffix,
|
|
2720
|
+
min,
|
|
2721
|
+
max
|
|
2722
|
+
}, ref) {
|
|
2723
|
+
const [internal, setInternal] = React16.useState(defaultValue);
|
|
2724
|
+
const isControlled = typeof value === "number";
|
|
2725
|
+
const resolved = isControlled ? value != null ? value : 0 : internal;
|
|
2726
|
+
const update = (next) => {
|
|
2727
|
+
const clamped = Math.max(min != null ? min : -Infinity, Math.min(max != null ? max : Infinity, next));
|
|
2728
|
+
if (!isControlled) setInternal(clamped);
|
|
2729
|
+
onChange == null ? void 0 : onChange(clamped);
|
|
2730
|
+
};
|
|
2731
|
+
return /* @__PURE__ */ jsxs16("div", { className: "space-y-1.5", children: [
|
|
2732
|
+
label ? /* @__PURE__ */ jsx18("p", { className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400", children: label }) : null,
|
|
2733
|
+
/* @__PURE__ */ jsxs16(
|
|
2734
|
+
"div",
|
|
2735
|
+
{
|
|
2736
|
+
className: twMerge17(
|
|
2737
|
+
"flex items-center gap-3 rounded-2xl border border-slate-300 bg-white/80 px-3 py-2 shadow-sm transition focus-within:border-slate-400 focus-within:shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-700 dark:bg-zinc-900/70 dark:focus-within:border-slate-500",
|
|
2738
|
+
disabled && "opacity-60",
|
|
2739
|
+
error && "border-rose-300 focus-within:border-rose-400 focus-within:shadow-[0_0_0_1px_rgba(248,113,113,0.35)] dark:border-rose-500/60"
|
|
2740
|
+
),
|
|
2741
|
+
children: [
|
|
2742
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex flex-col gap-1", children: [
|
|
2743
|
+
/* @__PURE__ */ jsx18(
|
|
2744
|
+
"button",
|
|
2745
|
+
{
|
|
2746
|
+
type: "button",
|
|
2747
|
+
"aria-label": "Increase",
|
|
2748
|
+
onClick: () => update(resolved + step),
|
|
2749
|
+
className: "grid size-7 place-items-center rounded-xl border border-slate-200 bg-white text-xs font-semibold text-slate-700 shadow-sm transition hover:-translate-y-[1px] hover:shadow-md disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2750
|
+
disabled,
|
|
2751
|
+
children: "+"
|
|
2752
|
+
}
|
|
2753
|
+
),
|
|
2754
|
+
/* @__PURE__ */ jsx18(
|
|
2755
|
+
"button",
|
|
2756
|
+
{
|
|
2757
|
+
type: "button",
|
|
2758
|
+
"aria-label": "Decrease",
|
|
2759
|
+
onClick: () => update(resolved - step),
|
|
2760
|
+
className: "grid size-7 place-items-center rounded-xl border border-slate-200 bg-white text-xs font-semibold text-slate-700 shadow-sm transition hover:-translate-y-[1px] hover:shadow-md disabled:opacity-50 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100",
|
|
2761
|
+
disabled,
|
|
2762
|
+
children: "-"
|
|
2763
|
+
}
|
|
2764
|
+
)
|
|
2765
|
+
] }),
|
|
2766
|
+
/* @__PURE__ */ jsx18(
|
|
2767
|
+
"input",
|
|
2768
|
+
{
|
|
2769
|
+
ref,
|
|
2770
|
+
type: "number",
|
|
2771
|
+
value: resolved,
|
|
2772
|
+
onChange: (e) => update(Number(e.target.value)),
|
|
2773
|
+
min,
|
|
2774
|
+
max,
|
|
2775
|
+
step,
|
|
2776
|
+
disabled,
|
|
2777
|
+
className: twMerge17(
|
|
2778
|
+
"flex-1 border-none bg-transparent text-2xl font-semibold tabular-nums text-slate-900 placeholder:text-slate-400 focus:outline-none dark:text-zinc-100 dark:placeholder:text-zinc-500 appearance-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none",
|
|
2779
|
+
className
|
|
2780
|
+
)
|
|
2781
|
+
}
|
|
2782
|
+
),
|
|
2783
|
+
suffix ? /* @__PURE__ */ jsx18("span", { className: "text-sm font-semibold uppercase tracking-wide text-slate-500 dark:text-zinc-300", children: suffix }) : null
|
|
2784
|
+
]
|
|
2785
|
+
}
|
|
2786
|
+
),
|
|
2787
|
+
description ? /* @__PURE__ */ jsx18("p", { className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2788
|
+
error ? /* @__PURE__ */ jsx18("p", { className: "text-xs font-medium text-rose-500 dark:text-rose-400", children: error }) : null
|
|
2789
|
+
] });
|
|
2790
|
+
}
|
|
2791
|
+
);
|
|
2792
|
+
|
|
2793
|
+
// components/OutputChip/OutputChip.tsx
|
|
2794
|
+
import * as React17 from "react";
|
|
2795
|
+
import { twMerge as twMerge18 } from "tailwind-merge";
|
|
2796
|
+
import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
2797
|
+
var toneStyles = {
|
|
2798
|
+
neutral: { bg: "bg-slate-900", text: "text-white", ring: "ring-slate-300" },
|
|
2799
|
+
success: { bg: "bg-emerald-500", text: "text-white", ring: "ring-emerald-200" },
|
|
2800
|
+
warning: { bg: "bg-amber-500", text: "text-white", ring: "ring-amber-200" },
|
|
2801
|
+
danger: { bg: "bg-rose-500", text: "text-white", ring: "ring-rose-200" }
|
|
2802
|
+
};
|
|
2803
|
+
var OutputChip = React17.forwardRef(function OutputChip2({ children, className, tone = "neutral", label, ...rest }, ref) {
|
|
2804
|
+
const styles = toneStyles[tone];
|
|
2805
|
+
return /* @__PURE__ */ jsxs17(
|
|
2806
|
+
"output",
|
|
2807
|
+
{
|
|
2808
|
+
...rest,
|
|
2809
|
+
ref,
|
|
2810
|
+
className: twMerge18(
|
|
2811
|
+
"inline-flex items-center justify-center gap-2 rounded-full px-3 py-1 text-xs font-semibold uppercase tracking-[0.25em] shadow-sm ring-1 text-center",
|
|
2812
|
+
styles.bg,
|
|
2813
|
+
styles.text,
|
|
2814
|
+
styles.ring,
|
|
2815
|
+
className
|
|
2816
|
+
),
|
|
2817
|
+
children: [
|
|
2818
|
+
label ? /* @__PURE__ */ jsx19("span", { className: "opacity-70", children: label }) : null,
|
|
2819
|
+
/* @__PURE__ */ jsx19("span", { children })
|
|
2820
|
+
]
|
|
2821
|
+
}
|
|
2822
|
+
);
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
// components/ProgressMeter/ProgressMeter.tsx
|
|
2826
|
+
import { twMerge as twMerge19 } from "tailwind-merge";
|
|
2827
|
+
import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
2828
|
+
function Progress({
|
|
2829
|
+
label,
|
|
2830
|
+
description,
|
|
2831
|
+
value,
|
|
2832
|
+
max = 100,
|
|
2833
|
+
showValue = true,
|
|
2834
|
+
className
|
|
2835
|
+
}) {
|
|
2836
|
+
const clamped = Math.min(max, Math.max(0, value));
|
|
2837
|
+
const percent = max === 0 ? 0 : Math.round(clamped / max * 100);
|
|
2838
|
+
return /* @__PURE__ */ jsxs18("div", { className: twMerge19("space-y-1.5", className), children: [
|
|
2839
|
+
label ? /* @__PURE__ */ jsxs18("div", { className: "flex items-center justify-between gap-3", children: [
|
|
2840
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs font-semibold uppercase tracking-[0.24em] text-slate-500 dark:text-zinc-400", children: label }),
|
|
2841
|
+
showValue ? /* @__PURE__ */ jsxs18("span", { className: "text-xs font-semibold text-slate-600 dark:text-zinc-300", children: [
|
|
2842
|
+
percent,
|
|
2843
|
+
"%"
|
|
2844
|
+
] }) : null
|
|
2845
|
+
] }) : null,
|
|
2846
|
+
description ? /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2847
|
+
/* @__PURE__ */ jsxs18("div", { className: "relative h-3 w-full overflow-hidden rounded-full border border-slate-200 bg-white shadow-inner ring-1 ring-slate-100/80 dark:border-zinc-700 dark:bg-zinc-900/80 dark:ring-zinc-800/70", children: [
|
|
2848
|
+
/* @__PURE__ */ jsx20(
|
|
2849
|
+
"progress",
|
|
2850
|
+
{
|
|
2851
|
+
value: clamped,
|
|
2852
|
+
max,
|
|
2853
|
+
className: "absolute inset-0 h-full w-full appearance-none",
|
|
2854
|
+
"aria-label": label
|
|
2855
|
+
}
|
|
2856
|
+
),
|
|
2857
|
+
/* @__PURE__ */ jsx20(
|
|
2858
|
+
"div",
|
|
2859
|
+
{
|
|
2860
|
+
className: "absolute inset-y-0 left-0 rounded-full bg-gradient-to-r from-slate-900 via-slate-700 to-slate-900 shadow-[0_0_0_1px_rgba(15,23,42,0.2)] transition-[width]",
|
|
2861
|
+
style: { width: `${percent}%` }
|
|
2862
|
+
}
|
|
2863
|
+
)
|
|
2864
|
+
] })
|
|
2865
|
+
] });
|
|
2866
|
+
}
|
|
2867
|
+
function Meter({
|
|
2868
|
+
label,
|
|
2869
|
+
description,
|
|
2870
|
+
value,
|
|
2871
|
+
min = 0,
|
|
2872
|
+
max = 100,
|
|
2873
|
+
thresholds,
|
|
2874
|
+
className
|
|
2875
|
+
}) {
|
|
2876
|
+
var _a, _b;
|
|
2877
|
+
const clamped = Math.min(max, Math.max(min, value));
|
|
2878
|
+
const percent = max === min ? 0 : (clamped - min) / (max - min) * 100;
|
|
2879
|
+
const activeColor = thresholds ? thresholds.reduce(
|
|
2880
|
+
(acc, t) => clamped >= t.value ? t.color : acc,
|
|
2881
|
+
(_b = (_a = thresholds[0]) == null ? void 0 : _a.color) != null ? _b : "linear-gradient(90deg, #22c55e, #0ea5e9)"
|
|
2882
|
+
) : "linear-gradient(90deg, #22c55e, #0ea5e9)";
|
|
2883
|
+
return /* @__PURE__ */ jsxs18("div", { className: twMerge19("space-y-1.5", className), children: [
|
|
2884
|
+
label ? /* @__PURE__ */ jsxs18("div", { className: "flex items-center justify-between gap-3", children: [
|
|
2885
|
+
/* @__PURE__ */ jsx20("p", { className: "text-xs font-semibold uppercase tracking-[0.24em] text-slate-500 dark:text-zinc-400", children: label }),
|
|
2886
|
+
/* @__PURE__ */ jsx20("span", { className: "text-xs font-semibold text-slate-600 dark:text-zinc-300", children: clamped })
|
|
2887
|
+
] }) : null,
|
|
2888
|
+
description ? /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
2889
|
+
/* @__PURE__ */ jsxs18("div", { className: "relative h-3 w-full overflow-hidden rounded-full border border-slate-200 bg-white shadow-inner ring-1 ring-slate-100/80 dark:border-zinc-700 dark:bg-zinc-900/80 dark:ring-zinc-800/70", children: [
|
|
2890
|
+
/* @__PURE__ */ jsx20(
|
|
2891
|
+
"meter",
|
|
2892
|
+
{
|
|
2893
|
+
value: clamped,
|
|
2894
|
+
min,
|
|
2895
|
+
max,
|
|
2896
|
+
className: "absolute inset-0 h-full w-full appearance-none",
|
|
2897
|
+
"aria-label": label
|
|
2898
|
+
}
|
|
2899
|
+
),
|
|
2900
|
+
/* @__PURE__ */ jsx20(
|
|
2901
|
+
"div",
|
|
2902
|
+
{
|
|
2903
|
+
className: "absolute inset-y-0 left-0 rounded-full shadow-[0_0_0_1px_rgba(15,23,42,0.2)] transition-[width]",
|
|
2904
|
+
style: { width: `${percent}%`, background: activeColor }
|
|
2905
|
+
}
|
|
2906
|
+
)
|
|
2907
|
+
] })
|
|
2908
|
+
] });
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// components/Radio/Radio.tsx
|
|
2912
|
+
import * as React18 from "react";
|
|
2913
|
+
import { twMerge as twMerge20 } from "tailwind-merge";
|
|
2914
|
+
import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
2915
|
+
var Radio = React18.forwardRef(function Radio2({
|
|
2916
|
+
label,
|
|
2917
|
+
description,
|
|
2918
|
+
extra,
|
|
2919
|
+
checked,
|
|
2920
|
+
defaultChecked = false,
|
|
2921
|
+
onChange,
|
|
2922
|
+
disabled,
|
|
2923
|
+
className,
|
|
2924
|
+
id,
|
|
2925
|
+
...rest
|
|
2926
|
+
}, forwardedRef) {
|
|
2927
|
+
const generatedId = React18.useId();
|
|
2928
|
+
const radioId = id != null ? id : generatedId;
|
|
2929
|
+
const descriptionId = React18.useId();
|
|
2930
|
+
const isControlled = typeof checked === "boolean";
|
|
2931
|
+
const [internalChecked, setInternalChecked] = React18.useState(defaultChecked);
|
|
2932
|
+
const resolvedChecked = isControlled ? !!checked : internalChecked;
|
|
2933
|
+
const handleChange = (event) => {
|
|
2934
|
+
const next = event.target.checked;
|
|
2935
|
+
if (!isControlled) {
|
|
2936
|
+
setInternalChecked(next);
|
|
2937
|
+
}
|
|
2938
|
+
onChange == null ? void 0 : onChange(next);
|
|
2939
|
+
};
|
|
2940
|
+
return /* @__PURE__ */ jsxs19(
|
|
2941
|
+
"label",
|
|
2942
|
+
{
|
|
2943
|
+
htmlFor: radioId,
|
|
2944
|
+
className: twMerge20(
|
|
2945
|
+
"flex items-center justify-between gap-3 rounded-2xl border border-transparent px-3 py-2 text-left transition hover:bg-slate-50 focus-within:bg-slate-50 dark:hover:bg-zinc-900/60 dark:focus-within:bg-zinc-900/60",
|
|
2946
|
+
disabled && "cursor-not-allowed opacity-60",
|
|
2947
|
+
className
|
|
2948
|
+
),
|
|
2949
|
+
children: [
|
|
2950
|
+
/* @__PURE__ */ jsxs19("span", { className: "flex flex-1 items-center gap-3", children: [
|
|
2951
|
+
/* @__PURE__ */ jsxs19("span", { className: "flex h-6 w-6 items-center justify-center", children: [
|
|
2952
|
+
/* @__PURE__ */ jsx21(
|
|
2953
|
+
"input",
|
|
2954
|
+
{
|
|
2955
|
+
...rest,
|
|
2956
|
+
ref: forwardedRef,
|
|
2957
|
+
id: radioId,
|
|
2958
|
+
type: "radio",
|
|
2959
|
+
className: "peer sr-only",
|
|
2960
|
+
checked: resolvedChecked,
|
|
2961
|
+
onChange: handleChange,
|
|
2962
|
+
"aria-describedby": description ? descriptionId : rest["aria-describedby"],
|
|
2963
|
+
disabled
|
|
2964
|
+
}
|
|
2965
|
+
),
|
|
2966
|
+
/* @__PURE__ */ jsx21(
|
|
2967
|
+
"span",
|
|
2968
|
+
{
|
|
2969
|
+
className: twMerge20(
|
|
2970
|
+
"grid size-5 place-items-center rounded-full border border-slate-300 bg-white text-slate-600 shadow-sm transition duration-150 peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-slate-400 dark:border-zinc-600 dark:bg-zinc-950/70 dark:text-zinc-200",
|
|
2971
|
+
resolvedChecked && "border-slate-400 bg-slate-100 shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-500 dark:bg-zinc-700/70"
|
|
2972
|
+
),
|
|
2973
|
+
"aria-hidden": "true",
|
|
2974
|
+
children: /* @__PURE__ */ jsx21(
|
|
2975
|
+
"span",
|
|
2976
|
+
{
|
|
2977
|
+
className: twMerge20(
|
|
2978
|
+
"block h-2.5 w-2.5 rounded-full bg-slate-900 opacity-0 transition duration-200 dark:bg-zinc-100",
|
|
2979
|
+
resolvedChecked && "opacity-100"
|
|
2980
|
+
)
|
|
2981
|
+
}
|
|
2982
|
+
)
|
|
2983
|
+
}
|
|
2984
|
+
)
|
|
2985
|
+
] }),
|
|
2986
|
+
/* @__PURE__ */ jsxs19("span", { className: "flex flex-1 flex-col", children: [
|
|
2987
|
+
/* @__PURE__ */ jsx21("span", { className: "text-sm font-semibold text-slate-900 dark:text-zinc-100", children: label }),
|
|
2988
|
+
description ? /* @__PURE__ */ jsx21("span", { id: descriptionId, className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null
|
|
2989
|
+
] })
|
|
2990
|
+
] }),
|
|
2991
|
+
extra ? /* @__PURE__ */ jsx21("span", { className: "self-center rounded-xl border border-slate-200 bg-white/80 px-3 py-1 text-xs font-semibold text-slate-600 dark:border-zinc-700 dark:bg-zinc-900/60 dark:text-zinc-200", children: extra }) : null
|
|
2992
|
+
]
|
|
2993
|
+
}
|
|
2994
|
+
);
|
|
2995
|
+
});
|
|
2996
|
+
|
|
2997
|
+
// components/Slider/Slider.tsx
|
|
2998
|
+
import * as React19 from "react";
|
|
2999
|
+
import { twMerge as twMerge21 } from "tailwind-merge";
|
|
3000
|
+
import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
3001
|
+
function clampValue(value, min, max) {
|
|
3002
|
+
return Math.min(max, Math.max(min, value));
|
|
3003
|
+
}
|
|
3004
|
+
function percentFromValue(value, min, max) {
|
|
3005
|
+
if (max === min) return 0;
|
|
3006
|
+
return clampValue((value - min) / (max - min) * 100, 0, 100);
|
|
3007
|
+
}
|
|
3008
|
+
function snapToStep(value, min, max, step) {
|
|
3009
|
+
const safeStep = step > 0 ? step : 1;
|
|
3010
|
+
const limited = clampValue(value, min, max);
|
|
3011
|
+
const stepped = Math.round((limited - min) / safeStep) * safeStep + min;
|
|
3012
|
+
return clampValue(Number(stepped.toFixed(6)), min, max);
|
|
3013
|
+
}
|
|
3014
|
+
var Slider = React19.forwardRef(function Slider2({
|
|
3015
|
+
label,
|
|
3016
|
+
value,
|
|
3017
|
+
defaultValue,
|
|
3018
|
+
onChange,
|
|
3019
|
+
formatValue = (val) => `${val}`,
|
|
3020
|
+
renderTrack,
|
|
3021
|
+
renderThumb,
|
|
3022
|
+
trackClassName,
|
|
3023
|
+
thumbClassName,
|
|
3024
|
+
className,
|
|
3025
|
+
min = 0,
|
|
3026
|
+
max = 100,
|
|
3027
|
+
step = 1,
|
|
3028
|
+
thumbSize = 24,
|
|
3029
|
+
edgeOverlap = 0,
|
|
3030
|
+
fillMode = "stretch",
|
|
3031
|
+
disabled,
|
|
3032
|
+
orientation = "horizontal",
|
|
3033
|
+
reversed = false,
|
|
3034
|
+
id,
|
|
3035
|
+
...rest
|
|
3036
|
+
}, forwardedRef) {
|
|
3037
|
+
const isControlled = typeof value === "number";
|
|
3038
|
+
const initialValue = typeof defaultValue === "number" ? defaultValue : min;
|
|
3039
|
+
const [internalValue, setInternalValue] = React19.useState(
|
|
3040
|
+
() => snapToStep(initialValue, min, max, step)
|
|
3041
|
+
);
|
|
3042
|
+
const [focused, setFocused] = React19.useState(false);
|
|
3043
|
+
const [dragging, setDragging] = React19.useState(false);
|
|
3044
|
+
const [trackMetrics, setTrackMetrics] = React19.useState({
|
|
3045
|
+
length: 0,
|
|
3046
|
+
mainOffset: 0,
|
|
3047
|
+
crossOffset: 0,
|
|
3048
|
+
thickness: 0
|
|
3049
|
+
});
|
|
3050
|
+
const generatedId = React19.useId();
|
|
3051
|
+
const inputId = id != null ? id : generatedId;
|
|
3052
|
+
const inputRef = React19.useRef(null);
|
|
3053
|
+
const interactionRef = React19.useRef(null);
|
|
3054
|
+
const trackContainerRef = React19.useRef(null);
|
|
3055
|
+
const trackRef = React19.useRef(null);
|
|
3056
|
+
const mergedRef = React19.useCallback(
|
|
3057
|
+
(node) => {
|
|
3058
|
+
inputRef.current = node;
|
|
3059
|
+
assignRef(forwardedRef, node);
|
|
3060
|
+
},
|
|
3061
|
+
[forwardedRef]
|
|
3062
|
+
);
|
|
3063
|
+
React19.useEffect(() => {
|
|
3064
|
+
setInternalValue((prev) => snapToStep(prev, min, max, step));
|
|
3065
|
+
}, [min, max, step]);
|
|
3066
|
+
const resolvedValue = snapToStep(
|
|
3067
|
+
isControlled ? typeof value === "number" ? value : min : internalValue,
|
|
3068
|
+
min,
|
|
3069
|
+
max,
|
|
3070
|
+
step
|
|
3071
|
+
);
|
|
3072
|
+
const percentage = percentFromValue(resolvedValue, min, max);
|
|
3073
|
+
const isVertical = orientation === "vertical";
|
|
3074
|
+
const thumbDiameter = Math.max(thumbSize, 2);
|
|
3075
|
+
const thumbRadius = thumbDiameter / 2;
|
|
3076
|
+
React19.useLayoutEffect(() => {
|
|
3077
|
+
const track = trackRef.current;
|
|
3078
|
+
const container = trackContainerRef.current;
|
|
3079
|
+
if (!track || !container) return;
|
|
3080
|
+
const update = () => {
|
|
3081
|
+
const trackRect = track.getBoundingClientRect();
|
|
3082
|
+
const containerRect = container.getBoundingClientRect();
|
|
3083
|
+
const isVert = orientation === "vertical";
|
|
3084
|
+
const length = isVert ? trackRect.height : trackRect.width;
|
|
3085
|
+
const thickness = isVert ? trackRect.width : trackRect.height;
|
|
3086
|
+
const mainOffset = isVert ? trackRect.top - containerRect.top : trackRect.left - containerRect.left;
|
|
3087
|
+
const crossOffset = isVert ? trackRect.left - containerRect.left : trackRect.top - containerRect.top;
|
|
3088
|
+
setTrackMetrics({ length, mainOffset, crossOffset, thickness });
|
|
3089
|
+
};
|
|
3090
|
+
update();
|
|
3091
|
+
const observer = new ResizeObserver(update);
|
|
3092
|
+
observer.observe(track);
|
|
3093
|
+
observer.observe(container);
|
|
3094
|
+
return () => observer.disconnect();
|
|
3095
|
+
}, [orientation]);
|
|
3096
|
+
const trackLength = trackMetrics.length;
|
|
3097
|
+
const trackOffset = trackMetrics.mainOffset;
|
|
3098
|
+
const trackCrossOffset = trackMetrics.crossOffset;
|
|
3099
|
+
const trackThickness = trackMetrics.thickness;
|
|
3100
|
+
const overlap = Math.max(edgeOverlap, 0);
|
|
3101
|
+
const minCenter = Math.max(thumbRadius - overlap, 0);
|
|
3102
|
+
const maxCenter = Math.max(trackLength - thumbRadius + overlap, minCenter);
|
|
3103
|
+
const usableLength = Math.max(maxCenter - minCenter, 0);
|
|
3104
|
+
const logicalRatio = clampValue((resolvedValue - min) / (max - min || 1), 0, 1);
|
|
3105
|
+
const positionRatio = (() => {
|
|
3106
|
+
if (isVertical) {
|
|
3107
|
+
return reversed ? logicalRatio : 1 - logicalRatio;
|
|
3108
|
+
}
|
|
3109
|
+
return reversed ? 1 - logicalRatio : logicalRatio;
|
|
3110
|
+
})();
|
|
3111
|
+
const positionPx = minCenter + usableLength * positionRatio;
|
|
3112
|
+
const gradientSize = React19.useMemo(() => {
|
|
3113
|
+
if (fillMode === "stretch" || trackLength === 0) return "100% 100%";
|
|
3114
|
+
return isVertical ? `100% ${trackLength}px` : `${trackLength}px 100%`;
|
|
3115
|
+
}, [fillMode, isVertical, trackLength]);
|
|
3116
|
+
const gradientPosition = React19.useMemo(() => {
|
|
3117
|
+
if (fillMode === "stretch") return void 0;
|
|
3118
|
+
if (!isVertical && reversed) return "right center";
|
|
3119
|
+
if (isVertical && !reversed) return "center bottom";
|
|
3120
|
+
return "left top";
|
|
3121
|
+
}, [fillMode, isVertical, reversed]);
|
|
3122
|
+
const progressStyle = React19.useMemo(() => {
|
|
3123
|
+
if (trackLength === 0) return {};
|
|
3124
|
+
const half = thumbRadius;
|
|
3125
|
+
if (!isVertical && !reversed) {
|
|
3126
|
+
const width = Math.min(positionPx + half, trackLength);
|
|
3127
|
+
return { left: 0, width, top: 0, bottom: 0 };
|
|
3128
|
+
}
|
|
3129
|
+
if (!isVertical && reversed) {
|
|
3130
|
+
const start2 = Math.max(positionPx - half, 0);
|
|
3131
|
+
const width = Math.max(trackLength - start2, 0);
|
|
3132
|
+
return { right: 0, width, top: 0, bottom: 0 };
|
|
3133
|
+
}
|
|
3134
|
+
if (isVertical && reversed) {
|
|
3135
|
+
const height2 = Math.min(positionPx + half, trackLength);
|
|
3136
|
+
return { top: 0, height: height2, left: 0, right: 0 };
|
|
3137
|
+
}
|
|
3138
|
+
const start = Math.max(positionPx - half, 0);
|
|
3139
|
+
const height = Math.max(Math.min(trackLength - start, trackLength), 0);
|
|
3140
|
+
return { bottom: 0, height, left: 0, right: 0 };
|
|
3141
|
+
}, [isVertical, reversed, positionPx, trackLength, thumbRadius]);
|
|
3142
|
+
const trackGradient = React19.useMemo(() => {
|
|
3143
|
+
if (!isVertical && !reversed) return "linear-gradient(90deg, #e2e8f0, #fff, #e2e8f0)";
|
|
3144
|
+
if (!isVertical && reversed) return "linear-gradient(270deg, #e2e8f0, #fff, #e2e8f0)";
|
|
3145
|
+
if (isVertical && reversed) return "linear-gradient(180deg, #e2e8f0, #fff, #e2e8f0)";
|
|
3146
|
+
return "linear-gradient(0deg, #e2e8f0, #fff, #e2e8f0)";
|
|
3147
|
+
}, [isVertical, reversed]);
|
|
3148
|
+
const fillGradient = React19.useMemo(() => {
|
|
3149
|
+
if (!isVertical && !reversed) return "linear-gradient(90deg, #38bdf8, #6366f1)";
|
|
3150
|
+
if (!isVertical && reversed) return "linear-gradient(270deg, #38bdf8, #6366f1)";
|
|
3151
|
+
if (isVertical && reversed) return "linear-gradient(180deg, #38bdf8, #6366f1)";
|
|
3152
|
+
return "linear-gradient(0deg, #38bdf8, #6366f1)";
|
|
3153
|
+
}, [isVertical, reversed]);
|
|
3154
|
+
const thumbStyle = React19.useMemo(() => {
|
|
3155
|
+
if (isVertical) {
|
|
3156
|
+
return { top: trackOffset + positionPx, left: trackCrossOffset + trackThickness / 2 };
|
|
3157
|
+
}
|
|
3158
|
+
return { left: trackOffset + positionPx, top: trackCrossOffset + trackThickness / 2 };
|
|
3159
|
+
}, [isVertical, positionPx, trackOffset, trackCrossOffset, trackThickness]);
|
|
3160
|
+
const renderProps = React19.useMemo(
|
|
3161
|
+
() => ({
|
|
3162
|
+
value: resolvedValue,
|
|
3163
|
+
min,
|
|
3164
|
+
max,
|
|
3165
|
+
percentage,
|
|
3166
|
+
disabled,
|
|
3167
|
+
focused,
|
|
3168
|
+
orientation,
|
|
3169
|
+
reversed,
|
|
3170
|
+
position: positionPx,
|
|
3171
|
+
trackLength,
|
|
3172
|
+
thumbSize: thumbDiameter,
|
|
3173
|
+
trackOffset,
|
|
3174
|
+
trackCrossOffset,
|
|
3175
|
+
trackThickness
|
|
3176
|
+
}),
|
|
3177
|
+
[
|
|
3178
|
+
resolvedValue,
|
|
3179
|
+
min,
|
|
3180
|
+
max,
|
|
3181
|
+
percentage,
|
|
3182
|
+
disabled,
|
|
3183
|
+
focused,
|
|
3184
|
+
orientation,
|
|
3185
|
+
reversed,
|
|
3186
|
+
positionPx,
|
|
3187
|
+
trackLength,
|
|
3188
|
+
thumbDiameter,
|
|
3189
|
+
trackOffset,
|
|
3190
|
+
trackCrossOffset,
|
|
3191
|
+
trackThickness
|
|
3192
|
+
]
|
|
3193
|
+
);
|
|
3194
|
+
const commitValue = React19.useCallback(
|
|
3195
|
+
(next) => {
|
|
3196
|
+
const snapped = snapToStep(next, min, max, step);
|
|
3197
|
+
if (!isControlled) {
|
|
3198
|
+
setInternalValue(snapped);
|
|
3199
|
+
}
|
|
3200
|
+
onChange == null ? void 0 : onChange(snapped);
|
|
3201
|
+
},
|
|
3202
|
+
[isControlled, max, min, onChange, step]
|
|
3203
|
+
);
|
|
3204
|
+
const handleInputChange = (event) => {
|
|
3205
|
+
const next = Number(event.target.value);
|
|
3206
|
+
if (Number.isNaN(next)) return;
|
|
3207
|
+
commitValue(next);
|
|
3208
|
+
};
|
|
3209
|
+
const formattedValue = formatValue(resolvedValue);
|
|
3210
|
+
const setValueFromPointer = React19.useCallback(
|
|
3211
|
+
(clientX, clientY) => {
|
|
3212
|
+
var _a;
|
|
3213
|
+
const rect = (_a = trackRef.current) == null ? void 0 : _a.getBoundingClientRect();
|
|
3214
|
+
if (!rect) return;
|
|
3215
|
+
const overlapComp = Math.max(thumbRadius - overlap, 0);
|
|
3216
|
+
const length = isVertical ? rect.height : rect.width;
|
|
3217
|
+
const usable = Math.max(length - overlapComp * 2, 1);
|
|
3218
|
+
if (length <= 0) return;
|
|
3219
|
+
const clampToTrack = (pos) => clampValue(pos, overlapComp, length - overlapComp);
|
|
3220
|
+
if (isVertical) {
|
|
3221
|
+
const position2 = clampToTrack(clientY - rect.top);
|
|
3222
|
+
const positionRatio3 = (position2 - overlapComp) / usable;
|
|
3223
|
+
const logicalRatio3 = reversed ? positionRatio3 : 1 - positionRatio3;
|
|
3224
|
+
const next2 = min + logicalRatio3 * (max - min);
|
|
3225
|
+
commitValue(next2);
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
const position = clampToTrack(clientX - rect.left);
|
|
3229
|
+
const positionRatio2 = (position - overlapComp) / usable;
|
|
3230
|
+
const logicalRatio2 = reversed ? 1 - positionRatio2 : positionRatio2;
|
|
3231
|
+
const next = min + logicalRatio2 * (max - min);
|
|
3232
|
+
commitValue(next);
|
|
3233
|
+
},
|
|
3234
|
+
[commitValue, isVertical, max, min, overlap, reversed, thumbRadius]
|
|
3235
|
+
);
|
|
3236
|
+
const handlePointerDown = React19.useCallback(
|
|
3237
|
+
(event) => {
|
|
3238
|
+
var _a, _b;
|
|
3239
|
+
event.preventDefault();
|
|
3240
|
+
(_a = interactionRef.current) == null ? void 0 : _a.setPointerCapture(event.pointerId);
|
|
3241
|
+
(_b = inputRef.current) == null ? void 0 : _b.focus({ preventScroll: true });
|
|
3242
|
+
setDragging(true);
|
|
3243
|
+
setValueFromPointer(event.clientX, event.clientY);
|
|
3244
|
+
const move = (moveEvent) => {
|
|
3245
|
+
moveEvent.preventDefault();
|
|
3246
|
+
setValueFromPointer(moveEvent.clientX, moveEvent.clientY);
|
|
3247
|
+
};
|
|
3248
|
+
const stop = (upEvent) => {
|
|
3249
|
+
var _a2;
|
|
3250
|
+
upEvent.preventDefault();
|
|
3251
|
+
(_a2 = interactionRef.current) == null ? void 0 : _a2.releasePointerCapture(event.pointerId);
|
|
3252
|
+
setDragging(false);
|
|
3253
|
+
window.removeEventListener("pointermove", move);
|
|
3254
|
+
window.removeEventListener("pointerup", stop);
|
|
3255
|
+
window.removeEventListener("pointercancel", stop);
|
|
3256
|
+
};
|
|
3257
|
+
window.addEventListener("pointermove", move, { passive: false });
|
|
3258
|
+
window.addEventListener("pointerup", stop, { passive: false });
|
|
3259
|
+
window.addEventListener("pointercancel", stop, { passive: false });
|
|
3260
|
+
},
|
|
3261
|
+
[setValueFromPointer]
|
|
3262
|
+
);
|
|
3263
|
+
const defaultTrack = /* @__PURE__ */ jsxs20(
|
|
3264
|
+
"div",
|
|
3265
|
+
{
|
|
3266
|
+
ref: trackRef,
|
|
3267
|
+
className: twMerge21(
|
|
3268
|
+
"relative overflow-hidden rounded-full shadow-inner ring-1 ring-slate-200/80 dark:ring-white/10",
|
|
3269
|
+
"bg-[length:100%_100%]",
|
|
3270
|
+
isVertical ? "mx-auto h-full w-3" : "h-3 w-full",
|
|
3271
|
+
disabled && "opacity-60",
|
|
3272
|
+
trackClassName
|
|
3273
|
+
),
|
|
3274
|
+
style: { backgroundImage: trackGradient },
|
|
3275
|
+
"aria-hidden": "true",
|
|
3276
|
+
children: [
|
|
3277
|
+
/* @__PURE__ */ jsx22(
|
|
3278
|
+
"div",
|
|
3279
|
+
{
|
|
3280
|
+
className: twMerge21(
|
|
3281
|
+
"absolute rounded-full shadow-[0_0_0_1px_rgba(59,130,246,0.18)]",
|
|
3282
|
+
dragging ? "transition-none" : "transition-[width,height] duration-150 ease-out",
|
|
3283
|
+
isVertical ? "left-0 right-0" : "inset-y-0"
|
|
3284
|
+
),
|
|
3285
|
+
style: {
|
|
3286
|
+
...progressStyle,
|
|
3287
|
+
backgroundImage: fillGradient,
|
|
3288
|
+
backgroundSize: gradientSize,
|
|
3289
|
+
backgroundPosition: gradientPosition
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
),
|
|
3293
|
+
/* @__PURE__ */ jsx22("div", { className: "pointer-events-none absolute inset-0 rounded-full ring-1 ring-black/5 dark:ring-white/10" })
|
|
3294
|
+
]
|
|
3295
|
+
}
|
|
3296
|
+
);
|
|
3297
|
+
const defaultThumb = /* @__PURE__ */ jsx22(
|
|
3298
|
+
"span",
|
|
3299
|
+
{
|
|
3300
|
+
className: twMerge21(
|
|
3301
|
+
"absolute top-1/2 -translate-y-1/2 -translate-x-1/2 rounded-full border border-white bg-white shadow-md shadow-slate-900/20 ring-1 ring-slate-200 transition-transform duration-150 dark:border-zinc-700/60 dark:bg-zinc-800 dark:shadow-black/30 dark:ring-zinc-700",
|
|
3302
|
+
focused && "scale-[1.04] ring-slate-300 dark:ring-slate-500",
|
|
3303
|
+
disabled && "opacity-60 shadow-none dark:shadow-none",
|
|
3304
|
+
thumbClassName
|
|
3305
|
+
),
|
|
3306
|
+
style: { ...thumbStyle, width: thumbDiameter, height: thumbDiameter },
|
|
3307
|
+
"aria-hidden": "true",
|
|
3308
|
+
children: /* @__PURE__ */ jsxs20("span", { className: "relative block h-full w-full", children: [
|
|
3309
|
+
/* @__PURE__ */ jsx22("span", { className: "absolute inset-[5px] rounded-full bg-gradient-to-br from-slate-50 to-slate-200 dark:from-slate-600 dark:to-slate-700" }),
|
|
3310
|
+
/* @__PURE__ */ jsx22("span", { className: "absolute inset-0 rounded-full bg-slate-950/5 backdrop-blur-[1px] dark:bg-white/5" })
|
|
3311
|
+
] })
|
|
3312
|
+
}
|
|
3313
|
+
);
|
|
3314
|
+
return /* @__PURE__ */ jsxs20("div", { className: twMerge21("w-full space-y-2", isVertical && "max-w-[120px]", className), children: [
|
|
3315
|
+
label ? /* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between gap-3", children: [
|
|
3316
|
+
/* @__PURE__ */ jsx22(
|
|
3317
|
+
"label",
|
|
3318
|
+
{
|
|
3319
|
+
htmlFor: inputId,
|
|
3320
|
+
className: "text-[11px] font-semibold uppercase tracking-[0.25em] text-slate-500 dark:text-zinc-400",
|
|
3321
|
+
children: label
|
|
3322
|
+
}
|
|
3323
|
+
),
|
|
3324
|
+
/* @__PURE__ */ jsx22("span", { className: "rounded-full bg-white/90 px-2 py-0.5 text-xs font-semibold text-slate-700 shadow-sm ring-1 ring-slate-200 dark:bg-zinc-900/80 dark:text-zinc-200 dark:ring-zinc-700", children: formattedValue })
|
|
3325
|
+
] }) : null,
|
|
3326
|
+
/* @__PURE__ */ jsxs20(
|
|
3327
|
+
"div",
|
|
3328
|
+
{
|
|
3329
|
+
ref: trackContainerRef,
|
|
3330
|
+
className: twMerge21(
|
|
3331
|
+
"relative w-full pt-2 pb-3",
|
|
3332
|
+
isVertical && "flex h-48 items-center justify-center px-4 pb-4 pt-4"
|
|
3333
|
+
),
|
|
3334
|
+
"data-orientation": orientation,
|
|
3335
|
+
children: [
|
|
3336
|
+
renderTrack ? renderTrack({ ...renderProps, children: defaultTrack }) : defaultTrack,
|
|
3337
|
+
renderThumb ? renderThumb(renderProps) : defaultThumb,
|
|
3338
|
+
/* @__PURE__ */ jsx22(
|
|
3339
|
+
"input",
|
|
3340
|
+
{
|
|
3341
|
+
...rest,
|
|
3342
|
+
ref: mergedRef,
|
|
3343
|
+
id: inputId,
|
|
3344
|
+
type: "range",
|
|
3345
|
+
min,
|
|
3346
|
+
max,
|
|
3347
|
+
step,
|
|
3348
|
+
value: resolvedValue,
|
|
3349
|
+
onChange: handleInputChange,
|
|
3350
|
+
onInput: handleInputChange,
|
|
3351
|
+
onFocus: () => setFocused(true),
|
|
3352
|
+
onBlur: () => setFocused(false),
|
|
3353
|
+
disabled,
|
|
3354
|
+
"aria-valuetext": formattedValue,
|
|
3355
|
+
"aria-orientation": orientation,
|
|
3356
|
+
"data-orientation": orientation,
|
|
3357
|
+
className: "absolute inset-0 h-10 w-full appearance-none opacity-0 pointer-events-none"
|
|
3358
|
+
}
|
|
3359
|
+
),
|
|
3360
|
+
/* @__PURE__ */ jsx22(
|
|
3361
|
+
"div",
|
|
3362
|
+
{
|
|
3363
|
+
ref: interactionRef,
|
|
3364
|
+
"aria-hidden": "true",
|
|
3365
|
+
onPointerDown: handlePointerDown,
|
|
3366
|
+
className: twMerge21(
|
|
3367
|
+
"absolute inset-0 cursor-pointer bg-transparent",
|
|
3368
|
+
isVertical ? "left-1/2 w-8 -translate-x-1/2" : "top-1/2 h-8 -translate-y-1/2"
|
|
3369
|
+
)
|
|
3370
|
+
}
|
|
3371
|
+
)
|
|
3372
|
+
]
|
|
3373
|
+
}
|
|
3374
|
+
)
|
|
3375
|
+
] });
|
|
3376
|
+
});
|
|
3377
|
+
|
|
3378
|
+
// components/StackedList/StackedList.tsx
|
|
3379
|
+
import { twMerge as twMerge22 } from "tailwind-merge";
|
|
3380
|
+
import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
3381
|
+
function StackedList({ items, dense, className, ...rest }) {
|
|
3382
|
+
return /* @__PURE__ */ jsx23(
|
|
3383
|
+
"div",
|
|
3384
|
+
{
|
|
3385
|
+
...rest,
|
|
3386
|
+
className: twMerge22(
|
|
3387
|
+
"rounded-3xl border border-slate-200 bg-white/80 shadow-xl shadow-slate-200/40 dark:border-zinc-800 dark:bg-zinc-900/80 dark:shadow-none",
|
|
3388
|
+
className
|
|
3389
|
+
),
|
|
3390
|
+
children: /* @__PURE__ */ jsx23("ul", { role: "list", className: "divide-y divide-slate-100 dark:divide-zinc-800", children: items.map((item) => /* @__PURE__ */ jsxs21(
|
|
3391
|
+
"li",
|
|
3392
|
+
{
|
|
3393
|
+
className: twMerge22("flex items-start gap-4 px-5 py-4", dense && "py-3"),
|
|
3394
|
+
children: [
|
|
3395
|
+
item.icon ? /* @__PURE__ */ jsx23(
|
|
3396
|
+
"div",
|
|
3397
|
+
{
|
|
3398
|
+
className: "mt-0.5 rounded-2xl bg-slate-100 p-2 text-slate-600 dark:bg-zinc-900/50 dark:text-zinc-200",
|
|
3399
|
+
"aria-hidden": "true",
|
|
3400
|
+
children: item.icon
|
|
3401
|
+
}
|
|
3402
|
+
) : null,
|
|
3403
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex-1", children: [
|
|
3404
|
+
/* @__PURE__ */ jsx23("p", { className: "text-sm font-semibold text-slate-900 dark:text-zinc-100", children: item.title }),
|
|
3405
|
+
item.description ? /* @__PURE__ */ jsx23("p", { className: "text-sm text-slate-500 dark:text-zinc-400", children: item.description }) : null
|
|
3406
|
+
] }),
|
|
3407
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex flex-col items-end gap-1 text-right", children: [
|
|
3408
|
+
item.meta ? /* @__PURE__ */ jsx23("span", { className: "text-xs uppercase tracking-wide text-slate-400 dark:text-zinc-500", children: item.meta }) : null,
|
|
3409
|
+
item.action
|
|
3410
|
+
] })
|
|
3411
|
+
]
|
|
3412
|
+
},
|
|
3413
|
+
item.id
|
|
3414
|
+
)) })
|
|
3415
|
+
}
|
|
3416
|
+
);
|
|
3417
|
+
}
|
|
3418
|
+
|
|
3419
|
+
// components/Table/Table.tsx
|
|
3420
|
+
import * as React20 from "react";
|
|
3421
|
+
import { twMerge as twMerge23 } from "tailwind-merge";
|
|
3422
|
+
import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
3423
|
+
var MIN_THUMB = 24;
|
|
3424
|
+
var TRACK_PADDING2 = 6;
|
|
3425
|
+
var TRACK_THICKNESS = 6;
|
|
3426
|
+
var TRACK_INSET = 8;
|
|
3427
|
+
var V_TRACK_INSET = 10;
|
|
3428
|
+
function useScrollbarMetrics(ref, axis, extraSpace) {
|
|
3429
|
+
const [state, setState] = React20.useState({
|
|
3430
|
+
visible: false,
|
|
3431
|
+
size: MIN_THUMB,
|
|
3432
|
+
offset: 0
|
|
3433
|
+
});
|
|
3434
|
+
React20.useLayoutEffect(() => {
|
|
3435
|
+
const el = ref.current;
|
|
3436
|
+
if (!el) return;
|
|
3437
|
+
let raf = 0;
|
|
3438
|
+
const update = () => {
|
|
3439
|
+
const target = ref.current;
|
|
3440
|
+
if (!target) return;
|
|
3441
|
+
const scrollRange = axis === "vertical" ? target.scrollHeight - target.clientHeight : target.scrollWidth - target.clientWidth;
|
|
3442
|
+
const trackLength = axis === "vertical" ? Math.max(0, target.clientHeight + extraSpace - TRACK_PADDING2 * 2 - V_TRACK_INSET * 2) : Math.max(0, target.clientWidth + extraSpace - 2 * (TRACK_PADDING2 + TRACK_INSET));
|
|
3443
|
+
if (scrollRange <= 0 || trackLength <= 0) {
|
|
3444
|
+
setState((prev) => prev.visible ? { ...prev, visible: false } : prev);
|
|
3445
|
+
return;
|
|
3446
|
+
}
|
|
3447
|
+
const ratio = axis === "vertical" ? target.clientHeight / target.scrollHeight : target.clientWidth / target.scrollWidth;
|
|
3448
|
+
const thumbSize = Math.max(trackLength * ratio, MIN_THUMB);
|
|
3449
|
+
const maxOffset = Math.max(0, trackLength - thumbSize);
|
|
3450
|
+
const currentOffset = maxOffset > 0 ? (axis === "vertical" ? target.scrollTop : target.scrollLeft) / scrollRange * maxOffset : 0;
|
|
3451
|
+
setState({ visible: true, size: thumbSize, offset: currentOffset });
|
|
3452
|
+
};
|
|
3453
|
+
const handleScroll = () => {
|
|
3454
|
+
cancelAnimationFrame(raf);
|
|
3455
|
+
raf = window.requestAnimationFrame(update);
|
|
3456
|
+
};
|
|
3457
|
+
const resizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(update) : null;
|
|
3458
|
+
resizeObserver == null ? void 0 : resizeObserver.observe(el);
|
|
3459
|
+
el.addEventListener("scroll", handleScroll, { passive: true });
|
|
3460
|
+
update();
|
|
3461
|
+
return () => {
|
|
3462
|
+
resizeObserver == null ? void 0 : resizeObserver.disconnect();
|
|
3463
|
+
el.removeEventListener("scroll", handleScroll);
|
|
3464
|
+
cancelAnimationFrame(raf);
|
|
3465
|
+
};
|
|
3466
|
+
}, [ref, axis, extraSpace]);
|
|
3467
|
+
return state;
|
|
3468
|
+
}
|
|
3469
|
+
function useThumbDrag(ref, axis, thumbState, extraSpace) {
|
|
3470
|
+
const startDrag = React20.useCallback(
|
|
3471
|
+
(event, startScrollOverride) => {
|
|
3472
|
+
var _a, _b;
|
|
3473
|
+
const el = ref.current;
|
|
3474
|
+
if (!el) return;
|
|
3475
|
+
event.preventDefault();
|
|
3476
|
+
const startPos = axis === "vertical" ? event.clientY : event.clientX;
|
|
3477
|
+
const startScroll = startScrollOverride != null ? startScrollOverride : axis === "vertical" ? el.scrollTop : el.scrollLeft;
|
|
3478
|
+
const thumbSize = thumbState.size;
|
|
3479
|
+
const handleMove = (moveEvent) => {
|
|
3480
|
+
const delta = (axis === "vertical" ? moveEvent.clientY : moveEvent.clientX) - startPos;
|
|
3481
|
+
const scrollRange = axis === "vertical" ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;
|
|
3482
|
+
const trackLength = axis === "vertical" ? el.clientHeight + extraSpace - TRACK_PADDING2 * 2 - V_TRACK_INSET * 2 : el.clientWidth + extraSpace - 2 * (TRACK_PADDING2 + TRACK_INSET);
|
|
3483
|
+
if (scrollRange <= 0 || trackLength <= thumbSize) return;
|
|
3484
|
+
const ratio = scrollRange / (trackLength - thumbSize);
|
|
3485
|
+
const next = startScroll + delta * ratio;
|
|
3486
|
+
if (axis === "vertical") el.scrollTop = Math.min(scrollRange, Math.max(0, next));
|
|
3487
|
+
else el.scrollLeft = Math.min(scrollRange, Math.max(0, next));
|
|
3488
|
+
};
|
|
3489
|
+
const handleUp = () => {
|
|
3490
|
+
window.removeEventListener("pointermove", handleMove);
|
|
3491
|
+
window.removeEventListener("pointerup", handleUp);
|
|
3492
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
3493
|
+
};
|
|
3494
|
+
(_b = (_a = event.currentTarget).setPointerCapture) == null ? void 0 : _b.call(_a, event.pointerId);
|
|
3495
|
+
window.addEventListener("pointermove", handleMove);
|
|
3496
|
+
window.addEventListener("pointerup", handleUp);
|
|
3497
|
+
window.addEventListener("pointercancel", handleUp);
|
|
3498
|
+
},
|
|
3499
|
+
[axis, ref, thumbState.size, extraSpace]
|
|
3500
|
+
);
|
|
3501
|
+
const onThumbPointerDown = React20.useCallback(
|
|
3502
|
+
(event) => {
|
|
3503
|
+
event.stopPropagation();
|
|
3504
|
+
startDrag(event);
|
|
3505
|
+
},
|
|
3506
|
+
[startDrag]
|
|
3507
|
+
);
|
|
3508
|
+
return { onThumbPointerDown, startDrag };
|
|
3509
|
+
}
|
|
3510
|
+
function Table({
|
|
3511
|
+
columns,
|
|
3512
|
+
data,
|
|
3513
|
+
caption,
|
|
3514
|
+
className,
|
|
3515
|
+
scrollAreaStyle,
|
|
3516
|
+
style
|
|
3517
|
+
}) {
|
|
3518
|
+
const scrollRef = React20.useRef(null);
|
|
3519
|
+
const [padRight, setPadRight] = React20.useState(0);
|
|
3520
|
+
const [padBottom, setPadBottom] = React20.useState(0);
|
|
3521
|
+
const vThumb = useScrollbarMetrics(scrollRef, "vertical", padBottom);
|
|
3522
|
+
const hThumb = useScrollbarMetrics(scrollRef, "horizontal", padRight);
|
|
3523
|
+
React20.useEffect(() => {
|
|
3524
|
+
setPadRight(vThumb.visible ? TRACK_PADDING2 + 10 : 0);
|
|
3525
|
+
}, [vThumb.visible]);
|
|
3526
|
+
React20.useEffect(() => {
|
|
3527
|
+
setPadBottom(hThumb.visible ? TRACK_PADDING2 + 10 : 0);
|
|
3528
|
+
}, [hThumb.visible]);
|
|
3529
|
+
const { onThumbPointerDown: handleVThumbDown, startDrag: startVDrag } = useThumbDrag(
|
|
3530
|
+
scrollRef,
|
|
3531
|
+
"vertical",
|
|
3532
|
+
vThumb,
|
|
3533
|
+
padBottom
|
|
3534
|
+
);
|
|
3535
|
+
const { onThumbPointerDown: handleHThumbDown, startDrag: startHDrag } = useThumbDrag(
|
|
3536
|
+
scrollRef,
|
|
3537
|
+
"horizontal",
|
|
3538
|
+
hThumb,
|
|
3539
|
+
padRight
|
|
3540
|
+
);
|
|
3541
|
+
const handleTrackPointerDown = React20.useCallback(
|
|
3542
|
+
(axis, thumb, startDrag, event) => {
|
|
3543
|
+
const el = scrollRef.current;
|
|
3544
|
+
if (!el) return;
|
|
3545
|
+
event.preventDefault();
|
|
3546
|
+
event.stopPropagation();
|
|
3547
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
3548
|
+
const clickOffset = axis === "vertical" ? event.clientY - rect.top : event.clientX - rect.left;
|
|
3549
|
+
const trackLength = axis === "vertical" ? rect.height : rect.width;
|
|
3550
|
+
const scrollRange = axis === "vertical" ? el.scrollHeight - el.clientHeight : el.scrollWidth - el.clientWidth;
|
|
3551
|
+
if (scrollRange <= 0 || trackLength <= 0) return;
|
|
3552
|
+
const effective = Math.max(1, trackLength - thumb.size);
|
|
3553
|
+
const ratio = Math.max(0, Math.min(effective, clickOffset - thumb.size / 2)) / effective;
|
|
3554
|
+
const target = ratio * scrollRange;
|
|
3555
|
+
if (axis === "vertical") el.scrollTop = target;
|
|
3556
|
+
else el.scrollLeft = target;
|
|
3557
|
+
startDrag(event, axis === "vertical" ? el.scrollTop : el.scrollLeft);
|
|
3558
|
+
},
|
|
3559
|
+
[]
|
|
3560
|
+
);
|
|
3561
|
+
const vSlot = padRight || TRACK_PADDING2;
|
|
3562
|
+
const hSlot = padBottom || TRACK_PADDING2;
|
|
3563
|
+
const vOffset = Math.max(TRACK_PADDING2, (vSlot - TRACK_THICKNESS) / 2);
|
|
3564
|
+
const hOffset = Math.max(TRACK_PADDING2, (hSlot - TRACK_THICKNESS) / 2);
|
|
3565
|
+
const hBottom = Math.max(TRACK_PADDING2 / 2, (hSlot - TRACK_THICKNESS) / 2);
|
|
3566
|
+
return /* @__PURE__ */ jsx24(
|
|
3567
|
+
"div",
|
|
3568
|
+
{
|
|
3569
|
+
className: twMerge23(
|
|
3570
|
+
"overflow-hidden rounded-3xl border border-slate-200 shadow-sm dark:border-zinc-800",
|
|
3571
|
+
className
|
|
3572
|
+
),
|
|
3573
|
+
style,
|
|
3574
|
+
children: /* @__PURE__ */ jsxs22(
|
|
3575
|
+
"div",
|
|
3576
|
+
{
|
|
3577
|
+
className: "relative overflow-hidden bg-white/90 dark:bg-zinc-950/80",
|
|
3578
|
+
style: { paddingRight: padRight, paddingBottom: padBottom },
|
|
3579
|
+
children: [
|
|
3580
|
+
/* @__PURE__ */ jsx24(
|
|
3581
|
+
"div",
|
|
3582
|
+
{
|
|
3583
|
+
ref: scrollRef,
|
|
3584
|
+
className: "table-scrollbar overflow-auto",
|
|
3585
|
+
style: { ...scrollAreaStyle },
|
|
3586
|
+
children: /* @__PURE__ */ jsxs22("table", { className: "w-full border-collapse text-sm text-slate-700 dark:text-zinc-200", children: [
|
|
3587
|
+
caption ? /* @__PURE__ */ jsx24("caption", { className: "bg-slate-50 px-4 py-2 text-left text-xs font-semibold uppercase tracking-[0.25em] text-slate-500 dark:bg-zinc-900/70 dark:text-zinc-400", children: caption }) : null,
|
|
3588
|
+
/* @__PURE__ */ jsx24("thead", { className: "bg-white/90 text-xs uppercase tracking-[0.16em] text-slate-500 dark:bg-zinc-900/80 dark:text-zinc-400", children: /* @__PURE__ */ jsx24("tr", { children: columns.map((col) => /* @__PURE__ */ jsx24(
|
|
3589
|
+
"th",
|
|
3590
|
+
{
|
|
3591
|
+
scope: "col",
|
|
3592
|
+
className: twMerge23(
|
|
3593
|
+
"px-4 py-3 text-left font-semibold",
|
|
3594
|
+
col.align === "right" && "text-right",
|
|
3595
|
+
col.align === "center" && "text-center"
|
|
3596
|
+
),
|
|
3597
|
+
children: col.header
|
|
3598
|
+
},
|
|
3599
|
+
String(col.key)
|
|
3600
|
+
)) }) }),
|
|
3601
|
+
/* @__PURE__ */ jsx24("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ jsx24(
|
|
3602
|
+
"tr",
|
|
3603
|
+
{
|
|
3604
|
+
className: twMerge23(
|
|
3605
|
+
"border-t border-slate-100 transition dark:border-zinc-800",
|
|
3606
|
+
rowIndex % 2 === 0 ? "bg-slate-100/70 hover:bg-slate-200 dark:bg-zinc-950/70 dark:hover:bg-zinc-800" : "bg-white/80 hover:bg-slate-200 dark:bg-zinc-900/60 dark:hover:bg-zinc-800"
|
|
3607
|
+
),
|
|
3608
|
+
children: columns.map((col) => /* @__PURE__ */ jsx24(
|
|
3609
|
+
"td",
|
|
3610
|
+
{
|
|
3611
|
+
className: twMerge23(
|
|
3612
|
+
"px-4 py-3 align-middle",
|
|
3613
|
+
col.align === "right" && "text-right",
|
|
3614
|
+
col.align === "center" && "text-center"
|
|
3615
|
+
),
|
|
3616
|
+
children: col.render ? col.render(row[col.key], row) : row[col.key]
|
|
3617
|
+
},
|
|
3618
|
+
String(col.key)
|
|
3619
|
+
))
|
|
3620
|
+
},
|
|
3621
|
+
rowIndex
|
|
3622
|
+
)) })
|
|
3623
|
+
] })
|
|
3624
|
+
}
|
|
3625
|
+
),
|
|
3626
|
+
vThumb.visible ? /* @__PURE__ */ jsx24(
|
|
3627
|
+
"div",
|
|
3628
|
+
{
|
|
3629
|
+
className: "absolute z-20 pointer-events-auto rounded-full bg-white/80 shadow-inner dark:bg-zinc-900/70",
|
|
3630
|
+
style: {
|
|
3631
|
+
right: vOffset,
|
|
3632
|
+
top: TRACK_PADDING2 + V_TRACK_INSET,
|
|
3633
|
+
bottom: TRACK_PADDING2 + V_TRACK_INSET,
|
|
3634
|
+
width: TRACK_THICKNESS
|
|
3635
|
+
},
|
|
3636
|
+
onPointerDown: (e) => handleTrackPointerDown("vertical", vThumb, startVDrag, e),
|
|
3637
|
+
children: /* @__PURE__ */ jsx24("div", { className: "relative h-full w-full rounded-full bg-slate-900/5 shadow-inner dark:bg-white/10", children: /* @__PURE__ */ jsx24(
|
|
3638
|
+
"div",
|
|
3639
|
+
{
|
|
3640
|
+
className: "absolute left-1/2 w-full -translate-x-1/2 rounded-full bg-slate-400/80 shadow-sm transition-colors dark:bg-zinc-500/70",
|
|
3641
|
+
style: { height: `${vThumb.size}px`, top: `${vThumb.offset}px` },
|
|
3642
|
+
onPointerDown: handleVThumbDown
|
|
3643
|
+
}
|
|
3644
|
+
) })
|
|
3645
|
+
}
|
|
3646
|
+
) : null,
|
|
3647
|
+
hThumb.visible ? /* @__PURE__ */ jsx24(
|
|
3648
|
+
"div",
|
|
3649
|
+
{
|
|
3650
|
+
className: "absolute z-20 pointer-events-auto rounded-full bg-white/80 shadow-inner dark:bg-zinc-900/70",
|
|
3651
|
+
style: {
|
|
3652
|
+
left: hOffset + TRACK_INSET,
|
|
3653
|
+
right: hOffset + TRACK_INSET,
|
|
3654
|
+
bottom: hBottom,
|
|
3655
|
+
height: TRACK_THICKNESS
|
|
3656
|
+
},
|
|
3657
|
+
onPointerDown: (e) => handleTrackPointerDown("horizontal", hThumb, startHDrag, e),
|
|
3658
|
+
children: /* @__PURE__ */ jsx24("div", { className: "relative h-full w-full rounded-full bg-slate-900/5 shadow-inner dark:bg-white/10", children: /* @__PURE__ */ jsx24(
|
|
3659
|
+
"div",
|
|
3660
|
+
{
|
|
3661
|
+
className: "absolute top-1/2 h-full -translate-y-1/2 rounded-full bg-slate-400/80 shadow-sm transition-colors dark:bg-zinc-500/70",
|
|
3662
|
+
style: { width: `${hThumb.size}px`, left: `${hThumb.offset}px` },
|
|
3663
|
+
onPointerDown: handleHThumbDown
|
|
3664
|
+
}
|
|
3665
|
+
) })
|
|
3666
|
+
}
|
|
3667
|
+
) : null
|
|
3668
|
+
]
|
|
3669
|
+
}
|
|
3670
|
+
)
|
|
3671
|
+
}
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
// components/Textarea/Textarea.tsx
|
|
3676
|
+
import * as React21 from "react";
|
|
3677
|
+
import { twMerge as twMerge24 } from "tailwind-merge";
|
|
3678
|
+
import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3679
|
+
var MIN_THUMB2 = 24;
|
|
3680
|
+
var TRACK_THICKNESS2 = 6;
|
|
3681
|
+
var TRACK_TOP = 8;
|
|
3682
|
+
var TRACK_BOTTOM = 22;
|
|
3683
|
+
var TRACK_REDUCTION = 20;
|
|
3684
|
+
var TRACK_REDUCTION_HALF = TRACK_REDUCTION / 2;
|
|
3685
|
+
var Textarea = React21.forwardRef(function Textarea2({
|
|
3686
|
+
label,
|
|
3687
|
+
description,
|
|
3688
|
+
error,
|
|
3689
|
+
className,
|
|
3690
|
+
id,
|
|
3691
|
+
disabled,
|
|
3692
|
+
maxLength,
|
|
3693
|
+
showCount = true,
|
|
3694
|
+
resizeDirection = "vertical",
|
|
3695
|
+
...rest
|
|
3696
|
+
}, ref) {
|
|
3697
|
+
var _a, _b;
|
|
3698
|
+
const generatedId = React21.useId();
|
|
3699
|
+
const textareaId = id != null ? id : generatedId;
|
|
3700
|
+
const descriptionId = React21.useId();
|
|
3701
|
+
const errorId = React21.useId();
|
|
3702
|
+
const textareaRef = React21.useRef(null);
|
|
3703
|
+
const shellRef = React21.useRef(null);
|
|
3704
|
+
const resizeListenersRef = React21.useRef({});
|
|
3705
|
+
const [thumb, setThumb] = React21.useState({
|
|
3706
|
+
visible: false,
|
|
3707
|
+
size: MIN_THUMB2,
|
|
3708
|
+
offset: 0
|
|
3709
|
+
});
|
|
3710
|
+
const hintIds = [description ? descriptionId : null, error ? errorId : null].filter(Boolean);
|
|
3711
|
+
const resolvedAriaDescribedBy = hintIds.length ? hintIds.join(" ") : void 0;
|
|
3712
|
+
const [value, setValue] = React21.useState((_b = (_a = rest.defaultValue) == null ? void 0 : _a.toString()) != null ? _b : "");
|
|
3713
|
+
const [height, setHeight] = React21.useState(void 0);
|
|
3714
|
+
const [width, setWidth] = React21.useState(void 0);
|
|
3715
|
+
const setRefs = React21.useCallback(
|
|
3716
|
+
(node) => {
|
|
3717
|
+
textareaRef.current = node;
|
|
3718
|
+
if (typeof ref === "function") {
|
|
3719
|
+
ref(node);
|
|
3720
|
+
} else if (ref) {
|
|
3721
|
+
ref.current = node;
|
|
3722
|
+
}
|
|
3723
|
+
},
|
|
3724
|
+
[ref]
|
|
3725
|
+
);
|
|
3726
|
+
React21.useEffect(() => {
|
|
3727
|
+
if (typeof rest.value === "string") {
|
|
3728
|
+
setValue(rest.value);
|
|
3729
|
+
}
|
|
3730
|
+
}, [rest.value]);
|
|
3731
|
+
React21.useLayoutEffect(() => {
|
|
3732
|
+
if (textareaRef.current && height === void 0) {
|
|
3733
|
+
setHeight(textareaRef.current.offsetHeight);
|
|
3734
|
+
}
|
|
3735
|
+
}, [height]);
|
|
3736
|
+
React21.useLayoutEffect(() => {
|
|
3737
|
+
const el = textareaRef.current;
|
|
3738
|
+
if (!el) return;
|
|
3739
|
+
let raf = 0;
|
|
3740
|
+
const update = () => {
|
|
3741
|
+
var _a2, _b2;
|
|
3742
|
+
const target = textareaRef.current;
|
|
3743
|
+
if (!target) return;
|
|
3744
|
+
const scrollRange = target.scrollHeight - target.clientHeight;
|
|
3745
|
+
const hostHeight = (_b2 = (_a2 = shellRef.current) == null ? void 0 : _a2.clientHeight) != null ? _b2 : target.clientHeight;
|
|
3746
|
+
const trackLength = Math.max(0, hostHeight - TRACK_TOP - TRACK_BOTTOM - TRACK_REDUCTION);
|
|
3747
|
+
if (scrollRange <= 0 || trackLength <= 0) {
|
|
3748
|
+
setThumb((prev) => prev.visible ? { ...prev, visible: false } : prev);
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
const ratio = target.clientHeight / target.scrollHeight;
|
|
3752
|
+
const size = Math.max(trackLength * ratio, MIN_THUMB2);
|
|
3753
|
+
const maxOffset = Math.max(0, trackLength - size);
|
|
3754
|
+
const offset = maxOffset > 0 ? Math.min(maxOffset, target.scrollTop / scrollRange * maxOffset) : 0;
|
|
3755
|
+
setThumb({ visible: true, size, offset });
|
|
3756
|
+
};
|
|
3757
|
+
const handleScroll = () => {
|
|
3758
|
+
cancelAnimationFrame(raf);
|
|
3759
|
+
raf = window.requestAnimationFrame(update);
|
|
3760
|
+
};
|
|
3761
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(update) : null;
|
|
3762
|
+
ro == null ? void 0 : ro.observe(el);
|
|
3763
|
+
el.addEventListener("scroll", handleScroll, { passive: true });
|
|
3764
|
+
update();
|
|
3765
|
+
return () => {
|
|
3766
|
+
ro == null ? void 0 : ro.disconnect();
|
|
3767
|
+
el.removeEventListener("scroll", handleScroll);
|
|
3768
|
+
cancelAnimationFrame(raf);
|
|
3769
|
+
};
|
|
3770
|
+
}, [height]);
|
|
3771
|
+
React21.useLayoutEffect(() => {
|
|
3772
|
+
if (!shellRef.current || width !== void 0) return;
|
|
3773
|
+
setWidth(shellRef.current.offsetWidth);
|
|
3774
|
+
}, [width]);
|
|
3775
|
+
React21.useEffect(() => {
|
|
3776
|
+
return () => {
|
|
3777
|
+
if (resizeListenersRef.current.move) {
|
|
3778
|
+
window.removeEventListener("mousemove", resizeListenersRef.current.move);
|
|
3779
|
+
}
|
|
3780
|
+
if (resizeListenersRef.current.up) {
|
|
3781
|
+
window.removeEventListener("mouseup", resizeListenersRef.current.up);
|
|
3782
|
+
}
|
|
3783
|
+
};
|
|
3784
|
+
}, []);
|
|
3785
|
+
const handleResizeStart = (event) => {
|
|
3786
|
+
var _a2, _b2, _c, _d, _e;
|
|
3787
|
+
event.preventDefault();
|
|
3788
|
+
if (!textareaRef.current) return;
|
|
3789
|
+
const allowY = resizeDirection === "vertical" || resizeDirection === "both";
|
|
3790
|
+
const allowX = resizeDirection === "horizontal" || resizeDirection === "both";
|
|
3791
|
+
const startY = event.clientY;
|
|
3792
|
+
const startX = event.clientX;
|
|
3793
|
+
const startHeight = textareaRef.current.offsetHeight;
|
|
3794
|
+
const startWidth = (_b2 = (_a2 = shellRef.current) == null ? void 0 : _a2.offsetWidth) != null ? _b2 : textareaRef.current.offsetWidth;
|
|
3795
|
+
const minHeight = 120;
|
|
3796
|
+
const minWidth = 240;
|
|
3797
|
+
const parentWidth = (_e = (_d = (_c = shellRef.current) == null ? void 0 : _c.parentElement) == null ? void 0 : _d.clientWidth) != null ? _e : startWidth;
|
|
3798
|
+
const onMove = (moveEvent) => {
|
|
3799
|
+
if (allowY) {
|
|
3800
|
+
const nextHeight = Math.max(minHeight, startHeight + (moveEvent.clientY - startY));
|
|
3801
|
+
setHeight(nextHeight);
|
|
3802
|
+
}
|
|
3803
|
+
if (allowX) {
|
|
3804
|
+
const proposed = startWidth + (moveEvent.clientX - startX);
|
|
3805
|
+
const nextWidth = Math.min(parentWidth, Math.max(minWidth, proposed));
|
|
3806
|
+
setWidth(nextWidth);
|
|
3807
|
+
}
|
|
3808
|
+
};
|
|
3809
|
+
const onUp = () => {
|
|
3810
|
+
window.removeEventListener("mousemove", onMove);
|
|
3811
|
+
window.removeEventListener("mouseup", onUp);
|
|
3812
|
+
resizeListenersRef.current = {};
|
|
3813
|
+
};
|
|
3814
|
+
resizeListenersRef.current = { move: onMove, up: onUp };
|
|
3815
|
+
window.addEventListener("mousemove", onMove);
|
|
3816
|
+
window.addEventListener("mouseup", onUp);
|
|
3817
|
+
};
|
|
3818
|
+
const handleThumbDrag = React21.useCallback(
|
|
3819
|
+
(event, startScrollOverride) => {
|
|
3820
|
+
var _a2, _b2;
|
|
3821
|
+
const el = textareaRef.current;
|
|
3822
|
+
if (!el) return;
|
|
3823
|
+
event.preventDefault();
|
|
3824
|
+
const startY = event.clientY;
|
|
3825
|
+
const startScroll = startScrollOverride != null ? startScrollOverride : el.scrollTop;
|
|
3826
|
+
const handleMove = (moveEvent) => {
|
|
3827
|
+
var _a3, _b3;
|
|
3828
|
+
const delta = moveEvent.clientY - startY;
|
|
3829
|
+
const scrollRange = el.scrollHeight - el.clientHeight;
|
|
3830
|
+
const hostHeight = (_b3 = (_a3 = shellRef.current) == null ? void 0 : _a3.clientHeight) != null ? _b3 : el.clientHeight;
|
|
3831
|
+
const trackLength = Math.max(0, hostHeight - TRACK_TOP - TRACK_BOTTOM - TRACK_REDUCTION);
|
|
3832
|
+
if (scrollRange <= 0 || trackLength <= thumb.size) return;
|
|
3833
|
+
const ratio = scrollRange / (trackLength - thumb.size);
|
|
3834
|
+
const next = startScroll + delta * ratio;
|
|
3835
|
+
el.scrollTop = Math.min(scrollRange, Math.max(0, next));
|
|
3836
|
+
};
|
|
3837
|
+
const handleUp = () => {
|
|
3838
|
+
window.removeEventListener("pointermove", handleMove);
|
|
3839
|
+
window.removeEventListener("pointerup", handleUp);
|
|
3840
|
+
window.removeEventListener("pointercancel", handleUp);
|
|
3841
|
+
};
|
|
3842
|
+
(_b2 = (_a2 = event.currentTarget).setPointerCapture) == null ? void 0 : _b2.call(_a2, event.pointerId);
|
|
3843
|
+
window.addEventListener("pointermove", handleMove);
|
|
3844
|
+
window.addEventListener("pointerup", handleUp);
|
|
3845
|
+
window.addEventListener("pointercancel", handleUp);
|
|
3846
|
+
},
|
|
3847
|
+
[thumb.size]
|
|
3848
|
+
);
|
|
3849
|
+
const shellClasses = twMerge24(
|
|
3850
|
+
"relative rounded-2xl border border-slate-300 bg-white/80 px-3 py-2 shadow-sm transition focus-within:border-slate-400 focus-within:shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-700 dark:bg-zinc-900/70 dark:focus-within:border-slate-500",
|
|
3851
|
+
disabled && "opacity-60",
|
|
3852
|
+
error && "border-rose-300 focus-within:border-rose-400 focus-within:shadow-[0_0_0_1px_rgba(248,113,113,0.35)] dark:border-rose-500/60"
|
|
3853
|
+
);
|
|
3854
|
+
const textareaClasses = twMerge24(
|
|
3855
|
+
"textarea-scrollbar min-h-[120px] w-full resize-none border-none bg-transparent pb-4 pr-5 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none dark:text-zinc-100 dark:placeholder:text-zinc-500",
|
|
3856
|
+
className
|
|
3857
|
+
);
|
|
3858
|
+
const count = value.length;
|
|
3859
|
+
const limit = maxLength != null ? maxLength : void 0;
|
|
3860
|
+
const { style, ...restProps } = rest;
|
|
3861
|
+
const textareaStyle = {
|
|
3862
|
+
...style,
|
|
3863
|
+
...height !== void 0 ? { height } : null,
|
|
3864
|
+
...width !== void 0 ? { width } : null,
|
|
3865
|
+
maxWidth: "100%"
|
|
3866
|
+
};
|
|
3867
|
+
const shellStyle = width !== void 0 ? { width, maxWidth: "100%" } : { maxWidth: "100%" };
|
|
3868
|
+
return /* @__PURE__ */ jsxs23("div", { className: "space-y-1.5", children: [
|
|
3869
|
+
label ? /* @__PURE__ */ jsx25(
|
|
3870
|
+
"label",
|
|
3871
|
+
{
|
|
3872
|
+
htmlFor: textareaId,
|
|
3873
|
+
className: "text-xs font-semibold uppercase tracking-[0.2em] text-slate-500 dark:text-zinc-400",
|
|
3874
|
+
children: label
|
|
3875
|
+
}
|
|
3876
|
+
) : null,
|
|
3877
|
+
/* @__PURE__ */ jsxs23("div", { className: shellClasses, ref: shellRef, style: shellStyle, children: [
|
|
3878
|
+
/* @__PURE__ */ jsx25(
|
|
3879
|
+
"textarea",
|
|
3880
|
+
{
|
|
3881
|
+
...restProps,
|
|
3882
|
+
ref: setRefs,
|
|
3883
|
+
id: textareaId,
|
|
3884
|
+
className: textareaClasses,
|
|
3885
|
+
"aria-invalid": error ? true : void 0,
|
|
3886
|
+
"aria-describedby": resolvedAriaDescribedBy,
|
|
3887
|
+
disabled,
|
|
3888
|
+
maxLength,
|
|
3889
|
+
style: textareaStyle,
|
|
3890
|
+
onChange: (e) => {
|
|
3891
|
+
var _a2;
|
|
3892
|
+
setValue(e.target.value);
|
|
3893
|
+
(_a2 = rest.onChange) == null ? void 0 : _a2.call(rest, e);
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
),
|
|
3897
|
+
thumb.visible ? /* @__PURE__ */ jsx25(
|
|
3898
|
+
"div",
|
|
3899
|
+
{
|
|
3900
|
+
className: "pointer-events-auto absolute z-20 rounded-full bg-white/60 shadow-inner backdrop-blur-sm transition-colors dark:bg-zinc-800/70",
|
|
3901
|
+
style: {
|
|
3902
|
+
right: 4,
|
|
3903
|
+
top: TRACK_TOP + TRACK_REDUCTION_HALF,
|
|
3904
|
+
bottom: TRACK_BOTTOM + TRACK_REDUCTION_HALF,
|
|
3905
|
+
width: TRACK_THICKNESS2
|
|
3906
|
+
},
|
|
3907
|
+
onPointerDown: (event) => {
|
|
3908
|
+
event.preventDefault();
|
|
3909
|
+
event.stopPropagation();
|
|
3910
|
+
const el = textareaRef.current;
|
|
3911
|
+
if (!el) return;
|
|
3912
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
3913
|
+
const clickOffset = event.clientY - rect.top;
|
|
3914
|
+
const trackLength = rect.height;
|
|
3915
|
+
const scrollRange = el.scrollHeight - el.clientHeight;
|
|
3916
|
+
if (scrollRange <= 0 || trackLength <= 0) return;
|
|
3917
|
+
const effective = Math.max(1, trackLength - thumb.size);
|
|
3918
|
+
const ratio = Math.max(0, Math.min(effective, clickOffset - thumb.size / 2)) / effective;
|
|
3919
|
+
const target = ratio * scrollRange;
|
|
3920
|
+
el.scrollTop = target;
|
|
3921
|
+
handleThumbDrag(event, target);
|
|
3922
|
+
},
|
|
3923
|
+
children: /* @__PURE__ */ jsx25("div", { className: "relative h-full w-full rounded-full bg-slate-900/5 shadow-inner dark:bg-white/10", children: /* @__PURE__ */ jsx25(
|
|
3924
|
+
"div",
|
|
3925
|
+
{
|
|
3926
|
+
className: "absolute left-1/2 w-full -translate-x-1/2 rounded-full bg-slate-400/80 shadow-sm transition-colors dark:bg-zinc-500/70",
|
|
3927
|
+
style: { height: `${thumb.size}px`, top: `${thumb.offset}px` },
|
|
3928
|
+
onPointerDown: (event) => {
|
|
3929
|
+
event.stopPropagation();
|
|
3930
|
+
handleThumbDrag(event);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
) })
|
|
3934
|
+
}
|
|
3935
|
+
) : null,
|
|
3936
|
+
resizeDirection !== "none" ? /* @__PURE__ */ jsxs23("div", { className: "pointer-events-none absolute bottom-2 right-2 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-[0.18em] text-slate-400 dark:text-zinc-500", children: [
|
|
3937
|
+
showCount && limit ? /* @__PURE__ */ jsxs23("div", { className: "pointer-events-auto", children: [
|
|
3938
|
+
count,
|
|
3939
|
+
"/",
|
|
3940
|
+
limit
|
|
3941
|
+
] }) : null,
|
|
3942
|
+
/* @__PURE__ */ jsx25(
|
|
3943
|
+
"button",
|
|
3944
|
+
{
|
|
3945
|
+
type: "button",
|
|
3946
|
+
"aria-label": "Resize textarea",
|
|
3947
|
+
onMouseDown: handleResizeStart,
|
|
3948
|
+
className: "pointer-events-auto inline-flex h-[14px] w-[14px] items-center justify-center rounded-[3px] bg-transparent text-slate-400 outline-none transition hover:text-slate-500 focus-visible:ring-2 focus-visible:ring-slate-300 focus-visible:ring-offset-1 focus-visible:ring-offset-white active:cursor-grab dark:bg-transparent dark:text-zinc-400 dark:hover:text-zinc-200 dark:focus-visible:ring-slate-500 dark:focus-visible:ring-offset-zinc-900",
|
|
3949
|
+
style: {
|
|
3950
|
+
border: "none",
|
|
3951
|
+
boxShadow: "none",
|
|
3952
|
+
appearance: "none",
|
|
3953
|
+
background: "transparent"
|
|
3954
|
+
},
|
|
3955
|
+
children: /* @__PURE__ */ jsx25(
|
|
3956
|
+
"svg",
|
|
3957
|
+
{
|
|
3958
|
+
viewBox: "0 0 12 12",
|
|
3959
|
+
"aria-hidden": "true",
|
|
3960
|
+
className: "h-3 w-3 text-slate-400 dark:text-zinc-400",
|
|
3961
|
+
children: /* @__PURE__ */ jsx25(
|
|
3962
|
+
"path",
|
|
3963
|
+
{
|
|
3964
|
+
d: "M2 10.5 10.5 2M4.5 10.5 10.5 4.5M7 10.5 10.5 7",
|
|
3965
|
+
stroke: "currentColor",
|
|
3966
|
+
strokeWidth: "1.1"
|
|
3967
|
+
}
|
|
3968
|
+
)
|
|
3969
|
+
}
|
|
3970
|
+
)
|
|
3971
|
+
}
|
|
3972
|
+
)
|
|
3973
|
+
] }) : null
|
|
3974
|
+
] }),
|
|
3975
|
+
description ? /* @__PURE__ */ jsx25("p", { id: descriptionId, className: "text-xs text-slate-500 dark:text-zinc-400", children: description }) : null,
|
|
3976
|
+
error ? /* @__PURE__ */ jsx25("p", { id: errorId, className: "text-xs font-medium text-rose-500 dark:text-rose-400", children: error }) : null
|
|
3977
|
+
] });
|
|
3978
|
+
});
|
|
3979
|
+
|
|
3980
|
+
// components/Toggle/Toggle.tsx
|
|
3981
|
+
import * as React22 from "react";
|
|
3982
|
+
import { twMerge as twMerge25 } from "tailwind-merge";
|
|
3983
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
3984
|
+
var Toggle = React22.forwardRef(function Toggle2({ checked, defaultChecked = false, onChange, disabled, className, onClick, ...rest }, ref) {
|
|
3985
|
+
const isControlled = typeof checked === "boolean";
|
|
3986
|
+
const [internalChecked, setInternalChecked] = React22.useState(defaultChecked);
|
|
3987
|
+
const resolvedChecked = isControlled ? !!checked : internalChecked;
|
|
3988
|
+
const handleClick = (event) => {
|
|
3989
|
+
if (disabled) {
|
|
3990
|
+
event.preventDefault();
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
const next = !resolvedChecked;
|
|
3994
|
+
if (!isControlled) {
|
|
3995
|
+
setInternalChecked(next);
|
|
3996
|
+
}
|
|
3997
|
+
onChange == null ? void 0 : onChange(next);
|
|
3998
|
+
onClick == null ? void 0 : onClick(event);
|
|
3999
|
+
};
|
|
4000
|
+
const buttonClasses = twMerge25(
|
|
4001
|
+
"relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded-full border border-slate-300 bg-slate-200 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-500 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-100",
|
|
4002
|
+
"dark:border-zinc-700/70 dark:bg-zinc-900/70 dark:focus-visible:ring-offset-slate-950",
|
|
4003
|
+
resolvedChecked && "border-slate-400 bg-slate-500/40 shadow-[0_0_0_1px_rgba(148,163,184,0.45)] dark:border-zinc-500 dark:bg-zinc-500",
|
|
4004
|
+
disabled && "cursor-not-allowed opacity-60",
|
|
4005
|
+
className
|
|
4006
|
+
);
|
|
4007
|
+
const thumbClasses = twMerge25(
|
|
4008
|
+
"pointer-events-none absolute left-1 top-[3px] size-5 rounded-full bg-white text-zinc-900 shadow-lg shadow-black/30 transition-transform duration-200",
|
|
4009
|
+
resolvedChecked ? "translate-x-[19px]" : "translate-x-0"
|
|
4010
|
+
);
|
|
4011
|
+
return /* @__PURE__ */ jsx26(
|
|
4012
|
+
"button",
|
|
4013
|
+
{
|
|
4014
|
+
...rest,
|
|
4015
|
+
ref,
|
|
4016
|
+
type: "button",
|
|
4017
|
+
role: "switch",
|
|
4018
|
+
"aria-checked": resolvedChecked,
|
|
4019
|
+
disabled,
|
|
4020
|
+
"data-state": resolvedChecked ? "on" : "off",
|
|
4021
|
+
className: buttonClasses,
|
|
4022
|
+
onClick: handleClick,
|
|
4023
|
+
children: /* @__PURE__ */ jsx26("span", { "aria-hidden": "true", className: thumbClasses })
|
|
4024
|
+
}
|
|
4025
|
+
);
|
|
4026
|
+
});
|
|
4027
|
+
var Toggle_default = Toggle;
|
|
4028
|
+
export {
|
|
4029
|
+
Alert,
|
|
4030
|
+
Badge,
|
|
4031
|
+
Button,
|
|
4032
|
+
Card,
|
|
4033
|
+
Checkbox,
|
|
4034
|
+
ColorPicker,
|
|
4035
|
+
Combobox,
|
|
4036
|
+
DatalistInput,
|
|
4037
|
+
DatePicker,
|
|
4038
|
+
Dialog,
|
|
4039
|
+
Disclosure,
|
|
4040
|
+
Dropdown,
|
|
4041
|
+
InputField,
|
|
4042
|
+
Meter,
|
|
4043
|
+
NumberInput,
|
|
4044
|
+
OutputChip,
|
|
4045
|
+
Popover,
|
|
4046
|
+
Progress,
|
|
4047
|
+
Radio,
|
|
4048
|
+
Select,
|
|
4049
|
+
Slider,
|
|
4050
|
+
StackedList,
|
|
4051
|
+
Table,
|
|
4052
|
+
Textarea,
|
|
4053
|
+
Toggle_default as Toggle
|
|
4054
|
+
};
|
|
4055
|
+
//# sourceMappingURL=index.js.map
|