@assassin1717/aifelib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +739 -0
- package/dist/index.cjs +1534 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +342 -0
- package/dist/index.d.ts +342 -0
- package/dist/index.js +1458 -0
- package/dist/index.js.map +1 -0
- package/package.json +95 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1458 @@
|
|
|
1
|
+
// src/lib/cn.ts
|
|
2
|
+
import { clsx } from "clsx";
|
|
3
|
+
import { twMerge } from "tailwind-merge";
|
|
4
|
+
function cn(...inputs) {
|
|
5
|
+
return twMerge(clsx(inputs));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/components/button/button.tsx
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { Loader2 } from "lucide-react";
|
|
11
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
+
var variantClasses = {
|
|
13
|
+
primary: "bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800 focus-visible:ring-blue-500",
|
|
14
|
+
secondary: "bg-gray-100 text-gray-900 hover:bg-gray-200 active:bg-gray-300 focus-visible:ring-gray-400",
|
|
15
|
+
ghost: "bg-transparent text-gray-700 hover:bg-gray-100 active:bg-gray-200 focus-visible:ring-gray-400",
|
|
16
|
+
destructive: "bg-red-600 text-white hover:bg-red-700 active:bg-red-800 focus-visible:ring-red-500",
|
|
17
|
+
outline: "border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 active:bg-gray-100 focus-visible:ring-gray-400"
|
|
18
|
+
};
|
|
19
|
+
var sizeClasses = {
|
|
20
|
+
sm: "h-8 px-3 text-xs gap-1.5",
|
|
21
|
+
md: "h-10 px-4 text-sm gap-2",
|
|
22
|
+
lg: "h-12 px-5 text-base gap-2"
|
|
23
|
+
};
|
|
24
|
+
var Button = React.forwardRef(
|
|
25
|
+
({
|
|
26
|
+
className,
|
|
27
|
+
variant = "primary",
|
|
28
|
+
size = "md",
|
|
29
|
+
loading = false,
|
|
30
|
+
fullWidth = false,
|
|
31
|
+
disabled,
|
|
32
|
+
children,
|
|
33
|
+
...props
|
|
34
|
+
}, ref) => {
|
|
35
|
+
const isDisabled = disabled || loading;
|
|
36
|
+
return /* @__PURE__ */ jsxs(
|
|
37
|
+
"button",
|
|
38
|
+
{
|
|
39
|
+
ref,
|
|
40
|
+
disabled: isDisabled,
|
|
41
|
+
"aria-busy": loading,
|
|
42
|
+
className: cn(
|
|
43
|
+
// base
|
|
44
|
+
"inline-flex items-center justify-center rounded-md font-medium",
|
|
45
|
+
"transition-colors duration-150",
|
|
46
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
47
|
+
// disabled
|
|
48
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
49
|
+
// touch target — mínimo 44px em mobile
|
|
50
|
+
"min-h-[2.75rem] sm:min-h-0",
|
|
51
|
+
variantClasses[variant],
|
|
52
|
+
sizeClasses[size],
|
|
53
|
+
fullWidth && "w-full",
|
|
54
|
+
className
|
|
55
|
+
),
|
|
56
|
+
...props,
|
|
57
|
+
children: [
|
|
58
|
+
loading && /* @__PURE__ */ jsx(
|
|
59
|
+
Loader2,
|
|
60
|
+
{
|
|
61
|
+
className: "shrink-0 animate-spin",
|
|
62
|
+
size: size === "sm" ? 14 : size === "lg" ? 18 : 16,
|
|
63
|
+
"aria-hidden": "true"
|
|
64
|
+
}
|
|
65
|
+
),
|
|
66
|
+
children
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
Button.displayName = "Button";
|
|
73
|
+
|
|
74
|
+
// src/components/input/input.tsx
|
|
75
|
+
import * as React2 from "react";
|
|
76
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
77
|
+
var Input = React2.forwardRef(
|
|
78
|
+
({ className, error, startIcon, endIcon, ...props }, ref) => {
|
|
79
|
+
if (startIcon || endIcon) {
|
|
80
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative flex items-center", children: [
|
|
81
|
+
startIcon && /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute left-3 flex shrink-0 items-center text-gray-400", children: startIcon }),
|
|
82
|
+
/* @__PURE__ */ jsx2(
|
|
83
|
+
"input",
|
|
84
|
+
{
|
|
85
|
+
ref,
|
|
86
|
+
className: cn(
|
|
87
|
+
inputBase,
|
|
88
|
+
error && inputError,
|
|
89
|
+
startIcon && "pl-9",
|
|
90
|
+
endIcon && "pr-9",
|
|
91
|
+
className
|
|
92
|
+
),
|
|
93
|
+
"aria-invalid": error ? true : void 0,
|
|
94
|
+
...props
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
endIcon && /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute right-3 flex shrink-0 items-center text-gray-400", children: endIcon })
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
return /* @__PURE__ */ jsx2(
|
|
101
|
+
"input",
|
|
102
|
+
{
|
|
103
|
+
ref,
|
|
104
|
+
className: cn(inputBase, error && inputError, className),
|
|
105
|
+
"aria-invalid": error ? true : void 0,
|
|
106
|
+
...props
|
|
107
|
+
}
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
Input.displayName = "Input";
|
|
112
|
+
var inputBase = [
|
|
113
|
+
"w-full rounded-md border border-gray-300 bg-white px-3",
|
|
114
|
+
"text-sm text-gray-900 placeholder:text-gray-400",
|
|
115
|
+
// mobile-friendly height
|
|
116
|
+
"h-11 sm:h-10",
|
|
117
|
+
"transition-colors duration-150",
|
|
118
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
|
119
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50"
|
|
120
|
+
].join(" ");
|
|
121
|
+
var inputError = "border-red-500 focus:ring-red-500";
|
|
122
|
+
|
|
123
|
+
// src/components/textarea/textarea.tsx
|
|
124
|
+
import * as React3 from "react";
|
|
125
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
126
|
+
var Textarea = React3.forwardRef(
|
|
127
|
+
({ className, error, ...props }, ref) => /* @__PURE__ */ jsx3(
|
|
128
|
+
"textarea",
|
|
129
|
+
{
|
|
130
|
+
ref,
|
|
131
|
+
className: cn(
|
|
132
|
+
"w-full rounded-md border border-gray-300 bg-white px-3 py-2",
|
|
133
|
+
"text-sm text-gray-900 placeholder:text-gray-400",
|
|
134
|
+
"min-h-[80px] resize-y",
|
|
135
|
+
"transition-colors duration-150",
|
|
136
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
|
137
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50",
|
|
138
|
+
error && "border-red-500 focus:ring-red-500",
|
|
139
|
+
className
|
|
140
|
+
),
|
|
141
|
+
"aria-invalid": error ? true : void 0,
|
|
142
|
+
...props
|
|
143
|
+
}
|
|
144
|
+
)
|
|
145
|
+
);
|
|
146
|
+
Textarea.displayName = "Textarea";
|
|
147
|
+
|
|
148
|
+
// src/components/select/select.tsx
|
|
149
|
+
import * as React4 from "react";
|
|
150
|
+
import { ChevronDown } from "lucide-react";
|
|
151
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
152
|
+
var Select = React4.forwardRef(
|
|
153
|
+
({ className, options, placeholder, error, ...props }, ref) => /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
|
|
154
|
+
/* @__PURE__ */ jsxs3(
|
|
155
|
+
"select",
|
|
156
|
+
{
|
|
157
|
+
ref,
|
|
158
|
+
className: cn(
|
|
159
|
+
"w-full appearance-none rounded-md border border-gray-300 bg-white px-3 pr-9",
|
|
160
|
+
"text-sm text-gray-900",
|
|
161
|
+
// mobile-friendly height
|
|
162
|
+
"h-11 sm:h-10",
|
|
163
|
+
"transition-colors duration-150",
|
|
164
|
+
"focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent",
|
|
165
|
+
"disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-gray-50",
|
|
166
|
+
error && "border-red-500 focus:ring-red-500",
|
|
167
|
+
className
|
|
168
|
+
),
|
|
169
|
+
"aria-invalid": error ? true : void 0,
|
|
170
|
+
...props,
|
|
171
|
+
children: [
|
|
172
|
+
placeholder && /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: placeholder }),
|
|
173
|
+
options.map((opt) => /* @__PURE__ */ jsx4("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value))
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
),
|
|
177
|
+
/* @__PURE__ */ jsx4(
|
|
178
|
+
ChevronDown,
|
|
179
|
+
{
|
|
180
|
+
className: "pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-gray-400",
|
|
181
|
+
size: 16,
|
|
182
|
+
"aria-hidden": "true"
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
] })
|
|
186
|
+
);
|
|
187
|
+
Select.displayName = "Select";
|
|
188
|
+
|
|
189
|
+
// src/components/checkbox/checkbox.tsx
|
|
190
|
+
import * as React5 from "react";
|
|
191
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
192
|
+
var Checkbox = React5.forwardRef(
|
|
193
|
+
({ className, label, error, id, ...props }, ref) => {
|
|
194
|
+
const generatedId = React5.useId();
|
|
195
|
+
const checkboxId = id ?? generatedId;
|
|
196
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-start gap-3", children: [
|
|
197
|
+
/* @__PURE__ */ jsx5("span", { className: "flex min-h-[44px] min-w-[44px] items-center justify-center sm:min-h-0 sm:min-w-0", children: /* @__PURE__ */ jsx5(
|
|
198
|
+
"input",
|
|
199
|
+
{
|
|
200
|
+
ref,
|
|
201
|
+
id: checkboxId,
|
|
202
|
+
type: "checkbox",
|
|
203
|
+
className: cn(
|
|
204
|
+
"h-4 w-4 shrink-0 rounded border-gray-300 text-blue-600",
|
|
205
|
+
"focus:ring-2 focus:ring-blue-500 focus:ring-offset-2",
|
|
206
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
207
|
+
"cursor-pointer",
|
|
208
|
+
error && "border-red-500",
|
|
209
|
+
className
|
|
210
|
+
),
|
|
211
|
+
"aria-invalid": error ? true : void 0,
|
|
212
|
+
...props
|
|
213
|
+
}
|
|
214
|
+
) }),
|
|
215
|
+
label && /* @__PURE__ */ jsx5(
|
|
216
|
+
"label",
|
|
217
|
+
{
|
|
218
|
+
htmlFor: checkboxId,
|
|
219
|
+
className: "cursor-pointer select-none pt-0.5 text-sm text-gray-700",
|
|
220
|
+
children: label
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
Checkbox.displayName = "Checkbox";
|
|
227
|
+
|
|
228
|
+
// src/components/label/label.tsx
|
|
229
|
+
import * as React6 from "react";
|
|
230
|
+
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
231
|
+
var Label = React6.forwardRef(
|
|
232
|
+
({ className, children, required, ...props }, ref) => /* @__PURE__ */ jsxs5(
|
|
233
|
+
"label",
|
|
234
|
+
{
|
|
235
|
+
ref,
|
|
236
|
+
className: cn(
|
|
237
|
+
"block text-sm font-medium text-gray-700 select-none",
|
|
238
|
+
className
|
|
239
|
+
),
|
|
240
|
+
...props,
|
|
241
|
+
children: [
|
|
242
|
+
children,
|
|
243
|
+
required && /* @__PURE__ */ jsx6("span", { className: "ml-1 text-red-500", "aria-hidden": "true", children: "*" })
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
);
|
|
248
|
+
Label.displayName = "Label";
|
|
249
|
+
|
|
250
|
+
// src/components/form-field/form-field.tsx
|
|
251
|
+
import * as React7 from "react";
|
|
252
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
253
|
+
function FormField({
|
|
254
|
+
label,
|
|
255
|
+
htmlFor,
|
|
256
|
+
required,
|
|
257
|
+
hint,
|
|
258
|
+
error,
|
|
259
|
+
className,
|
|
260
|
+
children
|
|
261
|
+
}) {
|
|
262
|
+
const errorId = React7.useId();
|
|
263
|
+
const hintId = React7.useId();
|
|
264
|
+
const childrenWithProps = React7.Children.map(children, (child) => {
|
|
265
|
+
if (!React7.isValidElement(child)) return child;
|
|
266
|
+
return React7.cloneElement(child, {
|
|
267
|
+
id: htmlFor ?? child.props.id,
|
|
268
|
+
error: !!error || child.props.error,
|
|
269
|
+
"aria-describedby": [error ? errorId : null, hint ? hintId : null].filter(Boolean).join(" ") || void 0
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
return /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col gap-1.5", className), children: [
|
|
273
|
+
label && /* @__PURE__ */ jsx7(Label, { ...htmlFor !== void 0 ? { htmlFor } : {}, ...required ? { required } : {}, children: label }),
|
|
274
|
+
childrenWithProps,
|
|
275
|
+
hint && !error && /* @__PURE__ */ jsx7("p", { id: hintId, className: "text-xs text-gray-500", children: hint }),
|
|
276
|
+
error && /* @__PURE__ */ jsx7("p", { id: errorId, className: "text-xs text-red-600", role: "alert", children: error })
|
|
277
|
+
] });
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/components/spinner/spinner.tsx
|
|
281
|
+
import { Loader2 as Loader22 } from "lucide-react";
|
|
282
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
283
|
+
var sizeClasses2 = { sm: 16, md: 24, lg: 36 };
|
|
284
|
+
function Spinner({ size = "md", label = "Loading\u2026", className }) {
|
|
285
|
+
return /* @__PURE__ */ jsx8("span", { role: "status", "aria-label": label, className: cn("inline-flex", className), children: /* @__PURE__ */ jsx8(
|
|
286
|
+
Loader22,
|
|
287
|
+
{
|
|
288
|
+
size: sizeClasses2[size],
|
|
289
|
+
className: "animate-spin text-current",
|
|
290
|
+
"aria-hidden": "true"
|
|
291
|
+
}
|
|
292
|
+
) });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/components/badge/badge.tsx
|
|
296
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
297
|
+
var variantClasses2 = {
|
|
298
|
+
default: "bg-gray-100 text-gray-700",
|
|
299
|
+
success: "bg-green-100 text-green-700",
|
|
300
|
+
warning: "bg-yellow-100 text-yellow-700",
|
|
301
|
+
destructive: "bg-red-100 text-red-700",
|
|
302
|
+
info: "bg-blue-100 text-blue-700",
|
|
303
|
+
outline: "border border-gray-300 text-gray-700 bg-transparent"
|
|
304
|
+
};
|
|
305
|
+
function Badge({ variant = "default", className, children, ...props }) {
|
|
306
|
+
return /* @__PURE__ */ jsx9(
|
|
307
|
+
"span",
|
|
308
|
+
{
|
|
309
|
+
className: cn(
|
|
310
|
+
"inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium",
|
|
311
|
+
variantClasses2[variant],
|
|
312
|
+
className
|
|
313
|
+
),
|
|
314
|
+
...props,
|
|
315
|
+
children
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/components/alert/alert.tsx
|
|
321
|
+
import { CheckCircle2, AlertTriangle, XCircle, Info } from "lucide-react";
|
|
322
|
+
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
323
|
+
var config = {
|
|
324
|
+
info: { icon: Info, classes: "bg-blue-50 border-blue-200 text-blue-800" },
|
|
325
|
+
success: { icon: CheckCircle2, classes: "bg-green-50 border-green-200 text-green-800" },
|
|
326
|
+
warning: { icon: AlertTriangle, classes: "bg-yellow-50 border-yellow-200 text-yellow-800" },
|
|
327
|
+
destructive: { icon: XCircle, classes: "bg-red-50 border-red-200 text-red-800" }
|
|
328
|
+
};
|
|
329
|
+
function Alert({ variant = "info", title, children, className, onDismiss }) {
|
|
330
|
+
const { icon: Icon, classes } = config[variant];
|
|
331
|
+
return /* @__PURE__ */ jsxs7(
|
|
332
|
+
"div",
|
|
333
|
+
{
|
|
334
|
+
role: "alert",
|
|
335
|
+
className: cn("flex gap-3 rounded-md border p-4", classes, className),
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsx10(Icon, { size: 18, className: "mt-0.5 shrink-0", "aria-hidden": "true" }),
|
|
338
|
+
/* @__PURE__ */ jsxs7("div", { className: "min-w-0 flex-1 text-sm", children: [
|
|
339
|
+
title && /* @__PURE__ */ jsx10("p", { className: "font-medium", children: title }),
|
|
340
|
+
children && /* @__PURE__ */ jsx10("p", { className: cn(title && "mt-1", "opacity-90"), children })
|
|
341
|
+
] }),
|
|
342
|
+
onDismiss && /* @__PURE__ */ jsx10(
|
|
343
|
+
"button",
|
|
344
|
+
{
|
|
345
|
+
onClick: onDismiss,
|
|
346
|
+
"aria-label": "Dismiss",
|
|
347
|
+
className: "shrink-0 rounded p-0.5 opacity-60 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current",
|
|
348
|
+
children: /* @__PURE__ */ jsx10(XCircle, { size: 16, "aria-hidden": "true" })
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/components/card/card.tsx
|
|
357
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
358
|
+
var paddingClasses = { none: "", sm: "p-3", md: "p-5", lg: "p-7" };
|
|
359
|
+
function Card({ className, padding = "md", children, ...props }) {
|
|
360
|
+
return /* @__PURE__ */ jsx11(
|
|
361
|
+
"div",
|
|
362
|
+
{
|
|
363
|
+
className: cn(
|
|
364
|
+
"rounded-lg border border-gray-200 bg-white shadow-sm",
|
|
365
|
+
paddingClasses[padding],
|
|
366
|
+
className
|
|
367
|
+
),
|
|
368
|
+
...props,
|
|
369
|
+
children
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
function CardHeader({ className, ...props }) {
|
|
374
|
+
return /* @__PURE__ */ jsx11("div", { className: cn("mb-4 flex flex-col gap-1", className), ...props });
|
|
375
|
+
}
|
|
376
|
+
function CardTitle({ className, ...props }) {
|
|
377
|
+
return /* @__PURE__ */ jsx11("h3", { className: cn("text-base font-semibold text-gray-900", className), ...props });
|
|
378
|
+
}
|
|
379
|
+
function CardDescription({ className, ...props }) {
|
|
380
|
+
return /* @__PURE__ */ jsx11("p", { className: cn("text-sm text-gray-500", className), ...props });
|
|
381
|
+
}
|
|
382
|
+
function CardContent({ className, ...props }) {
|
|
383
|
+
return /* @__PURE__ */ jsx11("div", { className: cn("text-sm text-gray-700", className), ...props });
|
|
384
|
+
}
|
|
385
|
+
function CardFooter({ className, ...props }) {
|
|
386
|
+
return /* @__PURE__ */ jsx11(
|
|
387
|
+
"div",
|
|
388
|
+
{
|
|
389
|
+
className: cn("mt-4 flex flex-wrap items-center gap-2 border-t border-gray-100 pt-4", className),
|
|
390
|
+
...props
|
|
391
|
+
}
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// src/components/modal/modal.tsx
|
|
396
|
+
import * as React8 from "react";
|
|
397
|
+
import { createPortal } from "react-dom";
|
|
398
|
+
import { X } from "lucide-react";
|
|
399
|
+
import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
400
|
+
var sizeClasses3 = {
|
|
401
|
+
sm: "sm:max-w-sm",
|
|
402
|
+
md: "sm:max-w-md",
|
|
403
|
+
lg: "sm:max-w-lg",
|
|
404
|
+
xl: "sm:max-w-xl",
|
|
405
|
+
full: "sm:max-w-[95vw]"
|
|
406
|
+
};
|
|
407
|
+
function Modal({
|
|
408
|
+
open,
|
|
409
|
+
onClose,
|
|
410
|
+
title,
|
|
411
|
+
description,
|
|
412
|
+
size = "md",
|
|
413
|
+
className,
|
|
414
|
+
children,
|
|
415
|
+
footer
|
|
416
|
+
}) {
|
|
417
|
+
const overlayRef = React8.useRef(null);
|
|
418
|
+
const titleId = React8.useId();
|
|
419
|
+
const descId = React8.useId();
|
|
420
|
+
React8.useEffect(() => {
|
|
421
|
+
if (!open) return;
|
|
422
|
+
const onKey = (e) => {
|
|
423
|
+
if (e.key === "Escape") onClose();
|
|
424
|
+
};
|
|
425
|
+
document.addEventListener("keydown", onKey);
|
|
426
|
+
return () => document.removeEventListener("keydown", onKey);
|
|
427
|
+
}, [open, onClose]);
|
|
428
|
+
React8.useEffect(() => {
|
|
429
|
+
if (open) {
|
|
430
|
+
document.body.style.overflow = "hidden";
|
|
431
|
+
return () => {
|
|
432
|
+
document.body.style.overflow = "";
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
}, [open]);
|
|
436
|
+
React8.useEffect(() => {
|
|
437
|
+
if (!open) return;
|
|
438
|
+
const el = overlayRef.current;
|
|
439
|
+
if (!el) return;
|
|
440
|
+
const focusable = el.querySelectorAll(
|
|
441
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
442
|
+
);
|
|
443
|
+
const first = focusable[0];
|
|
444
|
+
const last = focusable[focusable.length - 1];
|
|
445
|
+
first?.focus();
|
|
446
|
+
const trap = (e) => {
|
|
447
|
+
if (e.key !== "Tab") return;
|
|
448
|
+
if (e.shiftKey) {
|
|
449
|
+
if (document.activeElement === first) {
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
last?.focus();
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
if (document.activeElement === last) {
|
|
455
|
+
e.preventDefault();
|
|
456
|
+
first?.focus();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
document.addEventListener("keydown", trap);
|
|
461
|
+
return () => document.removeEventListener("keydown", trap);
|
|
462
|
+
}, [open]);
|
|
463
|
+
if (!open) return null;
|
|
464
|
+
return createPortal(
|
|
465
|
+
/* @__PURE__ */ jsxs8(
|
|
466
|
+
"div",
|
|
467
|
+
{
|
|
468
|
+
ref: overlayRef,
|
|
469
|
+
role: "dialog",
|
|
470
|
+
"aria-modal": "true",
|
|
471
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
472
|
+
"aria-describedby": description ? descId : void 0,
|
|
473
|
+
className: "fixed inset-0 z-50 flex items-end justify-center sm:items-center sm:p-4",
|
|
474
|
+
children: [
|
|
475
|
+
/* @__PURE__ */ jsx12(
|
|
476
|
+
"div",
|
|
477
|
+
{
|
|
478
|
+
className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
|
|
479
|
+
"aria-hidden": "true",
|
|
480
|
+
onClick: onClose
|
|
481
|
+
}
|
|
482
|
+
),
|
|
483
|
+
/* @__PURE__ */ jsxs8(
|
|
484
|
+
"div",
|
|
485
|
+
{
|
|
486
|
+
className: cn(
|
|
487
|
+
"relative z-10 flex w-full flex-col bg-white shadow-xl",
|
|
488
|
+
// mobile: slides from bottom, rounded top only
|
|
489
|
+
"rounded-t-2xl sm:rounded-xl",
|
|
490
|
+
// desktop: constrained width
|
|
491
|
+
sizeClasses3[size],
|
|
492
|
+
// max height with scroll
|
|
493
|
+
"max-h-[90dvh] overflow-hidden",
|
|
494
|
+
className
|
|
495
|
+
),
|
|
496
|
+
children: [
|
|
497
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-start justify-between gap-4 border-b border-gray-100 px-5 py-4", children: [
|
|
498
|
+
/* @__PURE__ */ jsxs8("div", { className: "min-w-0", children: [
|
|
499
|
+
title && /* @__PURE__ */ jsx12("h2", { id: titleId, className: "text-base font-semibold text-gray-900", children: title }),
|
|
500
|
+
description && /* @__PURE__ */ jsx12("p", { id: descId, className: "mt-0.5 text-sm text-gray-500", children: description })
|
|
501
|
+
] }),
|
|
502
|
+
/* @__PURE__ */ jsx12(
|
|
503
|
+
"button",
|
|
504
|
+
{
|
|
505
|
+
onClick: onClose,
|
|
506
|
+
"aria-label": "Close",
|
|
507
|
+
className: "shrink-0 rounded p-1 text-gray-400 hover:text-gray-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
|
|
508
|
+
children: /* @__PURE__ */ jsx12(X, { size: 18, "aria-hidden": "true" })
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
] }),
|
|
512
|
+
/* @__PURE__ */ jsx12("div", { className: "flex-1 overflow-y-auto px-5 py-4", children }),
|
|
513
|
+
footer && /* @__PURE__ */ jsx12("div", { className: "flex flex-col-reverse gap-2 border-t border-gray-100 px-5 py-4 sm:flex-row sm:justify-end", children: footer })
|
|
514
|
+
]
|
|
515
|
+
}
|
|
516
|
+
)
|
|
517
|
+
]
|
|
518
|
+
}
|
|
519
|
+
),
|
|
520
|
+
document.body
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/components/confirm-dialog/confirm-dialog.tsx
|
|
525
|
+
import { Fragment, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
526
|
+
function ConfirmDialog({
|
|
527
|
+
open,
|
|
528
|
+
onClose,
|
|
529
|
+
onConfirm,
|
|
530
|
+
title,
|
|
531
|
+
description,
|
|
532
|
+
confirmLabel = "Confirm",
|
|
533
|
+
cancelLabel = "Cancel",
|
|
534
|
+
variant = "primary",
|
|
535
|
+
loading = false
|
|
536
|
+
}) {
|
|
537
|
+
return /* @__PURE__ */ jsx13(
|
|
538
|
+
Modal,
|
|
539
|
+
{
|
|
540
|
+
open,
|
|
541
|
+
onClose,
|
|
542
|
+
title,
|
|
543
|
+
...description !== void 0 ? { description } : {},
|
|
544
|
+
size: "sm",
|
|
545
|
+
footer: /* @__PURE__ */ jsxs9(Fragment, { children: [
|
|
546
|
+
/* @__PURE__ */ jsx13(Button, { variant: "ghost", onClick: onClose, disabled: loading, fullWidth: true, children: cancelLabel }),
|
|
547
|
+
/* @__PURE__ */ jsx13(Button, { variant, onClick: onConfirm, loading, fullWidth: true, children: confirmLabel })
|
|
548
|
+
] }),
|
|
549
|
+
children: /* @__PURE__ */ jsx13("span", {})
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// src/components/toast/toast.tsx
|
|
555
|
+
import { CheckCircle2 as CheckCircle22, AlertTriangle as AlertTriangle2, XCircle as XCircle2, Info as Info2, X as X2 } from "lucide-react";
|
|
556
|
+
import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
557
|
+
var iconMap = {
|
|
558
|
+
success: CheckCircle22,
|
|
559
|
+
error: XCircle2,
|
|
560
|
+
warning: AlertTriangle2,
|
|
561
|
+
info: Info2
|
|
562
|
+
};
|
|
563
|
+
var styleMap = {
|
|
564
|
+
success: "border-green-200 bg-green-50 text-green-800",
|
|
565
|
+
error: "border-red-200 bg-red-50 text-red-800",
|
|
566
|
+
warning: "border-yellow-200 bg-yellow-50 text-yellow-800",
|
|
567
|
+
info: "border-blue-200 bg-blue-50 text-blue-800"
|
|
568
|
+
};
|
|
569
|
+
function ToastItem({ toast, onDismiss }) {
|
|
570
|
+
const Icon = iconMap[toast.type];
|
|
571
|
+
return /* @__PURE__ */ jsxs10(
|
|
572
|
+
"div",
|
|
573
|
+
{
|
|
574
|
+
role: "status",
|
|
575
|
+
"aria-live": "polite",
|
|
576
|
+
className: cn(
|
|
577
|
+
"flex w-full items-start gap-3 rounded-lg border p-4 shadow-md",
|
|
578
|
+
"animate-in slide-in-from-bottom-2 duration-200",
|
|
579
|
+
styleMap[toast.type]
|
|
580
|
+
),
|
|
581
|
+
children: [
|
|
582
|
+
/* @__PURE__ */ jsx14(Icon, { size: 18, className: "mt-0.5 shrink-0", "aria-hidden": "true" }),
|
|
583
|
+
/* @__PURE__ */ jsxs10("div", { className: "min-w-0 flex-1 text-sm", children: [
|
|
584
|
+
/* @__PURE__ */ jsx14("p", { className: "font-medium", children: toast.title }),
|
|
585
|
+
toast.description && /* @__PURE__ */ jsx14("p", { className: "mt-0.5 opacity-80", children: toast.description })
|
|
586
|
+
] }),
|
|
587
|
+
/* @__PURE__ */ jsx14(
|
|
588
|
+
"button",
|
|
589
|
+
{
|
|
590
|
+
onClick: () => onDismiss(toast.id),
|
|
591
|
+
"aria-label": "Dismiss",
|
|
592
|
+
className: "shrink-0 rounded p-0.5 opacity-60 hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-current",
|
|
593
|
+
children: /* @__PURE__ */ jsx14(X2, { size: 14, "aria-hidden": "true" })
|
|
594
|
+
}
|
|
595
|
+
)
|
|
596
|
+
]
|
|
597
|
+
}
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/components/toast/toast-context.tsx
|
|
602
|
+
import * as React9 from "react";
|
|
603
|
+
import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
604
|
+
var ToastContext = React9.createContext(null);
|
|
605
|
+
function ToastProvider({ children }) {
|
|
606
|
+
const [toasts, setToasts] = React9.useState([]);
|
|
607
|
+
const removeToast = React9.useCallback((id) => {
|
|
608
|
+
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
609
|
+
}, []);
|
|
610
|
+
const addToast = React9.useCallback(
|
|
611
|
+
({ type, title, description, duration = 4e3 }) => {
|
|
612
|
+
const id = crypto.randomUUID();
|
|
613
|
+
setToasts((prev) => [
|
|
614
|
+
...prev,
|
|
615
|
+
{
|
|
616
|
+
id,
|
|
617
|
+
type,
|
|
618
|
+
title,
|
|
619
|
+
...description !== void 0 ? { description } : {},
|
|
620
|
+
...duration !== void 0 ? { duration } : {}
|
|
621
|
+
}
|
|
622
|
+
]);
|
|
623
|
+
if (duration > 0) {
|
|
624
|
+
setTimeout(() => removeToast(id), duration);
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
[removeToast]
|
|
628
|
+
);
|
|
629
|
+
return /* @__PURE__ */ jsxs11(ToastContext.Provider, { value: { addToast, removeToast }, children: [
|
|
630
|
+
children,
|
|
631
|
+
/* @__PURE__ */ jsx15(
|
|
632
|
+
"div",
|
|
633
|
+
{
|
|
634
|
+
"aria-label": "Notifications",
|
|
635
|
+
className: "fixed bottom-0 left-0 right-0 z-50 flex flex-col gap-2 p-4 sm:bottom-4 sm:left-auto sm:right-4 sm:w-96",
|
|
636
|
+
children: toasts.map((toast) => /* @__PURE__ */ jsx15(ToastItem, { toast, onDismiss: removeToast }, toast.id))
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
] });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/components/toast/use-toast.ts
|
|
643
|
+
import * as React10 from "react";
|
|
644
|
+
function useToast() {
|
|
645
|
+
const ctx = React10.useContext(ToastContext);
|
|
646
|
+
if (!ctx) {
|
|
647
|
+
throw new Error("useToast must be used inside <ToastProvider>");
|
|
648
|
+
}
|
|
649
|
+
return ctx;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// src/components/table/table.tsx
|
|
653
|
+
import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
654
|
+
function Table({ className, children, ...props }) {
|
|
655
|
+
return /* @__PURE__ */ jsx16("div", { className: "w-full sm:overflow-x-auto sm:rounded-lg sm:border sm:border-gray-200", children: /* @__PURE__ */ jsx16(
|
|
656
|
+
"table",
|
|
657
|
+
{
|
|
658
|
+
className: cn(
|
|
659
|
+
"w-full border-collapse text-sm",
|
|
660
|
+
// desktop keeps min-width so columns never crush
|
|
661
|
+
"sm:min-w-[600px]",
|
|
662
|
+
className
|
|
663
|
+
),
|
|
664
|
+
...props,
|
|
665
|
+
children
|
|
666
|
+
}
|
|
667
|
+
) });
|
|
668
|
+
}
|
|
669
|
+
function TableHeader({ className, ...props }) {
|
|
670
|
+
return /* @__PURE__ */ jsx16(
|
|
671
|
+
"thead",
|
|
672
|
+
{
|
|
673
|
+
className: cn(
|
|
674
|
+
"hidden sm:table-header-group",
|
|
675
|
+
"border-b border-gray-200 bg-gray-50",
|
|
676
|
+
className
|
|
677
|
+
),
|
|
678
|
+
...props
|
|
679
|
+
}
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
function TableBody({ className, ...props }) {
|
|
683
|
+
return /* @__PURE__ */ jsx16(
|
|
684
|
+
"tbody",
|
|
685
|
+
{
|
|
686
|
+
className: cn(
|
|
687
|
+
// mobile: stack rows as cards
|
|
688
|
+
"flex flex-col gap-3",
|
|
689
|
+
// sm+: normal tbody
|
|
690
|
+
"sm:table-row-group sm:gap-0 sm:divide-y sm:divide-gray-100 sm:bg-white",
|
|
691
|
+
className
|
|
692
|
+
),
|
|
693
|
+
...props
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
function TableRow({ className, ...props }) {
|
|
698
|
+
return /* @__PURE__ */ jsx16(
|
|
699
|
+
"tr",
|
|
700
|
+
{
|
|
701
|
+
className: cn(
|
|
702
|
+
// mobile: card
|
|
703
|
+
"block rounded-lg border border-gray-200 bg-white p-4 shadow-sm",
|
|
704
|
+
// sm+: normal row
|
|
705
|
+
"sm:table-row sm:rounded-none sm:border-0 sm:p-0 sm:shadow-none sm:transition-colors sm:hover:bg-gray-50",
|
|
706
|
+
className
|
|
707
|
+
),
|
|
708
|
+
...props
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
function TableHead({ className, align = "left", ...props }) {
|
|
713
|
+
return /* @__PURE__ */ jsx16(
|
|
714
|
+
"th",
|
|
715
|
+
{
|
|
716
|
+
scope: "col",
|
|
717
|
+
className: cn(
|
|
718
|
+
"px-4 py-3 text-xs font-semibold uppercase tracking-wide text-gray-500 whitespace-nowrap",
|
|
719
|
+
align === "center" && "text-center",
|
|
720
|
+
align === "right" && "text-right",
|
|
721
|
+
className
|
|
722
|
+
),
|
|
723
|
+
...props
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
function TableCell({ className, align = "left", label, children, ...props }) {
|
|
728
|
+
return /* @__PURE__ */ jsxs12(
|
|
729
|
+
"td",
|
|
730
|
+
{
|
|
731
|
+
className: cn(
|
|
732
|
+
// mobile: each cell is a flex row — "Label value"
|
|
733
|
+
"flex items-start justify-between gap-4 py-1.5 text-gray-700",
|
|
734
|
+
"last:pb-0 first:pt-0",
|
|
735
|
+
// sm+: normal table cell
|
|
736
|
+
"sm:table-cell sm:px-4 sm:py-3",
|
|
737
|
+
align === "center" && "sm:text-center",
|
|
738
|
+
align === "right" && "sm:text-right",
|
|
739
|
+
className
|
|
740
|
+
),
|
|
741
|
+
...props,
|
|
742
|
+
children: [
|
|
743
|
+
label && /* @__PURE__ */ jsx16("span", { className: "shrink-0 text-xs font-semibold uppercase tracking-wide text-gray-400 sm:hidden", children: label }),
|
|
744
|
+
/* @__PURE__ */ jsx16("span", { className: cn("sm:contents", label && "text-right sm:text-left"), children })
|
|
745
|
+
]
|
|
746
|
+
}
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/components/table/table-toolbar.tsx
|
|
751
|
+
import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
752
|
+
function TableToolbar({ filters, actions, className }) {
|
|
753
|
+
return /* @__PURE__ */ jsxs13(
|
|
754
|
+
"div",
|
|
755
|
+
{
|
|
756
|
+
className: cn(
|
|
757
|
+
"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
|
|
758
|
+
"mb-3",
|
|
759
|
+
className
|
|
760
|
+
),
|
|
761
|
+
children: [
|
|
762
|
+
filters && /* @__PURE__ */ jsx17("div", { className: "flex flex-wrap items-center gap-2", children: filters }),
|
|
763
|
+
actions && /* @__PURE__ */ jsx17("div", { className: "flex shrink-0 flex-wrap items-center gap-2 sm:ml-auto", children: actions })
|
|
764
|
+
]
|
|
765
|
+
}
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// src/components/table/table-empty-state.tsx
|
|
770
|
+
import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
771
|
+
function TableEmptyState({
|
|
772
|
+
colSpan,
|
|
773
|
+
title = "No results",
|
|
774
|
+
description,
|
|
775
|
+
action,
|
|
776
|
+
icon,
|
|
777
|
+
className
|
|
778
|
+
}) {
|
|
779
|
+
return /* @__PURE__ */ jsx18("tr", { className: "block sm:table-row", children: /* @__PURE__ */ jsx18("td", { colSpan, className: cn("block px-4 py-12 text-center sm:table-cell", className), children: /* @__PURE__ */ jsxs14("div", { className: "flex flex-col items-center gap-3", children: [
|
|
780
|
+
icon && /* @__PURE__ */ jsx18("span", { className: "text-gray-300", "aria-hidden": "true", children: icon }),
|
|
781
|
+
/* @__PURE__ */ jsx18("p", { className: "text-sm font-medium text-gray-600", children: title }),
|
|
782
|
+
description && /* @__PURE__ */ jsx18("p", { className: "max-w-xs text-xs text-gray-400", children: description }),
|
|
783
|
+
action && /* @__PURE__ */ jsx18("div", { className: "mt-1", children: action })
|
|
784
|
+
] }) }) });
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// src/components/page-header/page-header.tsx
|
|
788
|
+
import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
789
|
+
function PageHeader({ title, description, prefix, actions, className }) {
|
|
790
|
+
return /* @__PURE__ */ jsxs15("div", { className: cn("mb-6", className), children: [
|
|
791
|
+
prefix && /* @__PURE__ */ jsx19("div", { className: "mb-2", children: prefix }),
|
|
792
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between", children: [
|
|
793
|
+
/* @__PURE__ */ jsxs15("div", { className: "min-w-0", children: [
|
|
794
|
+
/* @__PURE__ */ jsx19("h1", { className: "truncate text-2xl font-bold text-gray-900 sm:text-3xl", children: title }),
|
|
795
|
+
description && /* @__PURE__ */ jsx19("p", { className: "mt-1 text-sm text-gray-500", children: description })
|
|
796
|
+
] }),
|
|
797
|
+
actions && /* @__PURE__ */ jsx19("div", { className: "flex shrink-0 flex-wrap items-center gap-2", children: actions })
|
|
798
|
+
] })
|
|
799
|
+
] });
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// src/components/empty-state/empty-state.tsx
|
|
803
|
+
import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
804
|
+
function EmptyState({
|
|
805
|
+
icon,
|
|
806
|
+
title,
|
|
807
|
+
description,
|
|
808
|
+
action,
|
|
809
|
+
secondaryAction,
|
|
810
|
+
className
|
|
811
|
+
}) {
|
|
812
|
+
return /* @__PURE__ */ jsxs16(
|
|
813
|
+
"div",
|
|
814
|
+
{
|
|
815
|
+
className: cn(
|
|
816
|
+
"flex flex-col items-center justify-center gap-4 rounded-lg border border-dashed border-gray-200 bg-gray-50 px-6 py-16 text-center",
|
|
817
|
+
className
|
|
818
|
+
),
|
|
819
|
+
children: [
|
|
820
|
+
icon && /* @__PURE__ */ jsx20("span", { className: "text-gray-300", "aria-hidden": "true", children: icon }),
|
|
821
|
+
/* @__PURE__ */ jsxs16("div", { className: "max-w-xs", children: [
|
|
822
|
+
/* @__PURE__ */ jsx20("p", { className: "text-sm font-semibold text-gray-700", children: title }),
|
|
823
|
+
description && /* @__PURE__ */ jsx20("p", { className: "mt-1 text-sm text-gray-400", children: description })
|
|
824
|
+
] }),
|
|
825
|
+
(action || secondaryAction) && /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center gap-2 sm:flex-row", children: [
|
|
826
|
+
action,
|
|
827
|
+
secondaryAction
|
|
828
|
+
] })
|
|
829
|
+
]
|
|
830
|
+
}
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// src/components/app-shell/app-shell.tsx
|
|
835
|
+
import * as React12 from "react";
|
|
836
|
+
|
|
837
|
+
// src/components/drawer/drawer.tsx
|
|
838
|
+
import * as React11 from "react";
|
|
839
|
+
import { createPortal as createPortal2 } from "react-dom";
|
|
840
|
+
import { X as X3 } from "lucide-react";
|
|
841
|
+
import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
842
|
+
function Drawer({
|
|
843
|
+
open,
|
|
844
|
+
onClose,
|
|
845
|
+
title,
|
|
846
|
+
description,
|
|
847
|
+
side = "right",
|
|
848
|
+
widthClass = "sm:w-96",
|
|
849
|
+
className,
|
|
850
|
+
children,
|
|
851
|
+
footer
|
|
852
|
+
}) {
|
|
853
|
+
const overlayRef = React11.useRef(null);
|
|
854
|
+
const titleId = React11.useId();
|
|
855
|
+
const descId = React11.useId();
|
|
856
|
+
React11.useEffect(() => {
|
|
857
|
+
if (!open) return;
|
|
858
|
+
const h = (e) => {
|
|
859
|
+
if (e.key === "Escape") onClose();
|
|
860
|
+
};
|
|
861
|
+
document.addEventListener("keydown", h);
|
|
862
|
+
return () => document.removeEventListener("keydown", h);
|
|
863
|
+
}, [open, onClose]);
|
|
864
|
+
React11.useEffect(() => {
|
|
865
|
+
if (open) {
|
|
866
|
+
document.body.style.overflow = "hidden";
|
|
867
|
+
return () => {
|
|
868
|
+
document.body.style.overflow = "";
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
}, [open]);
|
|
872
|
+
React11.useEffect(() => {
|
|
873
|
+
if (!open) return;
|
|
874
|
+
const el = overlayRef.current;
|
|
875
|
+
if (!el) return;
|
|
876
|
+
const focusable = el.querySelectorAll(
|
|
877
|
+
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
878
|
+
);
|
|
879
|
+
const first = focusable[0];
|
|
880
|
+
const last = focusable[focusable.length - 1];
|
|
881
|
+
first?.focus();
|
|
882
|
+
const trap = (e) => {
|
|
883
|
+
if (e.key !== "Tab") return;
|
|
884
|
+
if (e.shiftKey) {
|
|
885
|
+
if (document.activeElement === first) {
|
|
886
|
+
e.preventDefault();
|
|
887
|
+
last?.focus();
|
|
888
|
+
}
|
|
889
|
+
} else {
|
|
890
|
+
if (document.activeElement === last) {
|
|
891
|
+
e.preventDefault();
|
|
892
|
+
first?.focus();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
document.addEventListener("keydown", trap);
|
|
897
|
+
return () => document.removeEventListener("keydown", trap);
|
|
898
|
+
}, [open]);
|
|
899
|
+
if (!open) return null;
|
|
900
|
+
return createPortal2(
|
|
901
|
+
/* @__PURE__ */ jsxs17(
|
|
902
|
+
"div",
|
|
903
|
+
{
|
|
904
|
+
ref: overlayRef,
|
|
905
|
+
role: "dialog",
|
|
906
|
+
"aria-modal": "true",
|
|
907
|
+
"aria-labelledby": title ? titleId : void 0,
|
|
908
|
+
"aria-describedby": description ? descId : void 0,
|
|
909
|
+
className: "fixed inset-0 z-50 flex",
|
|
910
|
+
children: [
|
|
911
|
+
/* @__PURE__ */ jsx21(
|
|
912
|
+
"div",
|
|
913
|
+
{
|
|
914
|
+
className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
|
|
915
|
+
"aria-hidden": "true",
|
|
916
|
+
onClick: onClose
|
|
917
|
+
}
|
|
918
|
+
),
|
|
919
|
+
/* @__PURE__ */ jsxs17(
|
|
920
|
+
"div",
|
|
921
|
+
{
|
|
922
|
+
className: cn(
|
|
923
|
+
"absolute bottom-0 left-0 right-0 z-10 flex flex-col bg-white shadow-xl",
|
|
924
|
+
"max-h-[90dvh] rounded-t-2xl",
|
|
925
|
+
// sm+: full height side panel
|
|
926
|
+
"sm:inset-y-0 sm:bottom-auto sm:top-0 sm:max-h-full sm:rounded-none",
|
|
927
|
+
side === "right" ? "sm:right-0 sm:left-auto" : "sm:left-0 sm:right-auto",
|
|
928
|
+
widthClass,
|
|
929
|
+
className
|
|
930
|
+
),
|
|
931
|
+
children: [
|
|
932
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-start justify-between gap-4 border-b border-gray-100 px-5 py-4", children: [
|
|
933
|
+
/* @__PURE__ */ jsxs17("div", { className: "min-w-0", children: [
|
|
934
|
+
title && /* @__PURE__ */ jsx21("h2", { id: titleId, className: "text-base font-semibold text-gray-900", children: title }),
|
|
935
|
+
description && /* @__PURE__ */ jsx21("p", { id: descId, className: "mt-0.5 text-sm text-gray-500", children: description })
|
|
936
|
+
] }),
|
|
937
|
+
/* @__PURE__ */ jsx21(
|
|
938
|
+
"button",
|
|
939
|
+
{
|
|
940
|
+
onClick: onClose,
|
|
941
|
+
"aria-label": "Close",
|
|
942
|
+
className: "shrink-0 rounded p-1 text-gray-400 hover:text-gray-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
|
|
943
|
+
children: /* @__PURE__ */ jsx21(X3, { size: 18, "aria-hidden": "true" })
|
|
944
|
+
}
|
|
945
|
+
)
|
|
946
|
+
] }),
|
|
947
|
+
/* @__PURE__ */ jsx21("div", { className: "flex-1 overflow-y-auto px-5 py-4", children }),
|
|
948
|
+
footer && /* @__PURE__ */ jsx21("div", { className: "flex flex-col-reverse gap-2 border-t border-gray-100 px-5 py-4 sm:flex-row sm:justify-end", children: footer })
|
|
949
|
+
]
|
|
950
|
+
}
|
|
951
|
+
)
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
document.body
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/components/sidebar/sidebar.tsx
|
|
960
|
+
import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
961
|
+
function Sidebar({ logo, groups, footer, className }) {
|
|
962
|
+
return /* @__PURE__ */ jsxs18(
|
|
963
|
+
"aside",
|
|
964
|
+
{
|
|
965
|
+
className: cn(
|
|
966
|
+
"flex h-full w-64 flex-col border-r border-gray-200 bg-white",
|
|
967
|
+
className
|
|
968
|
+
),
|
|
969
|
+
"aria-label": "Sidebar navigation",
|
|
970
|
+
children: [
|
|
971
|
+
logo && /* @__PURE__ */ jsx22("div", { className: "flex h-16 shrink-0 items-center border-b border-gray-100 px-4", children: logo }),
|
|
972
|
+
/* @__PURE__ */ jsx22("nav", { className: "flex-1 overflow-y-auto px-3 py-4", children: groups.map((group, gi) => /* @__PURE__ */ jsxs18("div", { className: cn(gi > 0 && "mt-6"), children: [
|
|
973
|
+
group.title && /* @__PURE__ */ jsx22("p", { className: "mb-1 px-2 text-xs font-semibold uppercase tracking-wider text-gray-400", children: group.title }),
|
|
974
|
+
/* @__PURE__ */ jsx22("ul", { role: "list", className: "flex flex-col gap-0.5", children: group.items.map((item, ii) => /* @__PURE__ */ jsx22("li", { children: /* @__PURE__ */ jsx22(SidebarItem, { item }) }, ii)) })
|
|
975
|
+
] }, gi)) }),
|
|
976
|
+
footer && /* @__PURE__ */ jsx22("div", { className: "shrink-0 border-t border-gray-100 p-3", children: footer })
|
|
977
|
+
]
|
|
978
|
+
}
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
function SidebarItem({ item }) {
|
|
982
|
+
const Tag = item.href ? "a" : "button";
|
|
983
|
+
return /* @__PURE__ */ jsxs18(
|
|
984
|
+
Tag,
|
|
985
|
+
{
|
|
986
|
+
href: item.href,
|
|
987
|
+
onClick: item.onClick,
|
|
988
|
+
disabled: item.disabled,
|
|
989
|
+
"aria-current": item.active ? "page" : void 0,
|
|
990
|
+
className: cn(
|
|
991
|
+
"group flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium",
|
|
992
|
+
"transition-colors duration-100",
|
|
993
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
|
|
994
|
+
// mobile touch target
|
|
995
|
+
"min-h-[44px] sm:min-h-[36px]",
|
|
996
|
+
item.active ? "bg-blue-50 text-blue-700" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900",
|
|
997
|
+
item.disabled && "cursor-not-allowed opacity-40 pointer-events-none"
|
|
998
|
+
),
|
|
999
|
+
children: [
|
|
1000
|
+
item.icon && /* @__PURE__ */ jsx22(
|
|
1001
|
+
"span",
|
|
1002
|
+
{
|
|
1003
|
+
"aria-hidden": "true",
|
|
1004
|
+
className: cn(
|
|
1005
|
+
"shrink-0",
|
|
1006
|
+
item.active ? "text-blue-600" : "text-gray-400 group-hover:text-gray-600"
|
|
1007
|
+
),
|
|
1008
|
+
children: item.icon
|
|
1009
|
+
}
|
|
1010
|
+
),
|
|
1011
|
+
/* @__PURE__ */ jsx22("span", { className: "flex-1 truncate text-left", children: item.label }),
|
|
1012
|
+
item.badge !== void 0 && /* @__PURE__ */ jsx22(
|
|
1013
|
+
"span",
|
|
1014
|
+
{
|
|
1015
|
+
className: cn(
|
|
1016
|
+
"shrink-0 rounded-full px-2 py-0.5 text-xs font-medium",
|
|
1017
|
+
item.active ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-500"
|
|
1018
|
+
),
|
|
1019
|
+
children: item.badge
|
|
1020
|
+
}
|
|
1021
|
+
)
|
|
1022
|
+
]
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/components/topbar/topbar.tsx
|
|
1028
|
+
import { Menu } from "lucide-react";
|
|
1029
|
+
import { jsx as jsx23, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1030
|
+
function Topbar({
|
|
1031
|
+
onMenuOpen,
|
|
1032
|
+
title,
|
|
1033
|
+
actions,
|
|
1034
|
+
hideMenuButton = false,
|
|
1035
|
+
className
|
|
1036
|
+
}) {
|
|
1037
|
+
return /* @__PURE__ */ jsxs19(
|
|
1038
|
+
"header",
|
|
1039
|
+
{
|
|
1040
|
+
className: cn(
|
|
1041
|
+
"flex h-14 shrink-0 items-center gap-3 border-b border-gray-200 bg-white px-4",
|
|
1042
|
+
className
|
|
1043
|
+
),
|
|
1044
|
+
children: [
|
|
1045
|
+
!hideMenuButton && onMenuOpen && /* @__PURE__ */ jsx23(
|
|
1046
|
+
"button",
|
|
1047
|
+
{
|
|
1048
|
+
onClick: onMenuOpen,
|
|
1049
|
+
"aria-label": "Open navigation menu",
|
|
1050
|
+
className: cn(
|
|
1051
|
+
"inline-flex h-9 w-9 items-center justify-center rounded-md text-gray-500",
|
|
1052
|
+
"hover:bg-gray-100 hover:text-gray-700",
|
|
1053
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
|
|
1054
|
+
// visible on mobile, hidden when sidebar renders on sm+
|
|
1055
|
+
"sm:hidden"
|
|
1056
|
+
),
|
|
1057
|
+
children: /* @__PURE__ */ jsx23(Menu, { size: 20, "aria-hidden": "true" })
|
|
1058
|
+
}
|
|
1059
|
+
),
|
|
1060
|
+
title && /* @__PURE__ */ jsx23("div", { className: "flex-1 truncate text-sm font-semibold text-gray-900 sm:text-base", children: title }),
|
|
1061
|
+
!title && /* @__PURE__ */ jsx23("div", { className: "flex-1" }),
|
|
1062
|
+
actions && /* @__PURE__ */ jsx23("div", { className: "flex shrink-0 items-center gap-2", children: actions })
|
|
1063
|
+
]
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/components/app-shell/app-shell.tsx
|
|
1069
|
+
import { jsx as jsx24, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1070
|
+
function AppShell({ sidebar, topbar, children, className }) {
|
|
1071
|
+
const [mobileOpen, setMobileOpen] = React12.useState(false);
|
|
1072
|
+
return /* @__PURE__ */ jsxs20("div", { className: cn("flex h-dvh overflow-hidden bg-gray-50", className), children: [
|
|
1073
|
+
/* @__PURE__ */ jsx24("div", { className: "hidden sm:flex sm:shrink-0", children: /* @__PURE__ */ jsx24(Sidebar, { ...sidebar }) }),
|
|
1074
|
+
/* @__PURE__ */ jsx24(
|
|
1075
|
+
Drawer,
|
|
1076
|
+
{
|
|
1077
|
+
open: mobileOpen,
|
|
1078
|
+
onClose: () => setMobileOpen(false),
|
|
1079
|
+
side: "left",
|
|
1080
|
+
widthClass: "sm:w-64",
|
|
1081
|
+
className: "p-0 sm:rounded-none",
|
|
1082
|
+
children: /* @__PURE__ */ jsx24("div", { className: "-mx-5 -my-4 h-full", children: /* @__PURE__ */ jsx24(
|
|
1083
|
+
Sidebar,
|
|
1084
|
+
{
|
|
1085
|
+
...sidebar,
|
|
1086
|
+
className: "h-full border-r-0"
|
|
1087
|
+
}
|
|
1088
|
+
) })
|
|
1089
|
+
}
|
|
1090
|
+
),
|
|
1091
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex flex-1 flex-col overflow-hidden", children: [
|
|
1092
|
+
topbar !== void 0 && /* @__PURE__ */ jsx24(
|
|
1093
|
+
Topbar,
|
|
1094
|
+
{
|
|
1095
|
+
...topbar,
|
|
1096
|
+
onMenuOpen: () => setMobileOpen(true),
|
|
1097
|
+
hideMenuButton: false
|
|
1098
|
+
}
|
|
1099
|
+
),
|
|
1100
|
+
/* @__PURE__ */ jsx24("main", { className: "flex-1 overflow-y-auto p-4 sm:p-6", children })
|
|
1101
|
+
] })
|
|
1102
|
+
] });
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
// src/components/tabs/tabs.tsx
|
|
1106
|
+
import * as React13 from "react";
|
|
1107
|
+
import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1108
|
+
function Tabs({ tabs, value, onChange, children, className }) {
|
|
1109
|
+
const tabListRef = React13.useRef(null);
|
|
1110
|
+
const onKeyDown = (e, current) => {
|
|
1111
|
+
const enabled = tabs.filter((t) => !t.disabled);
|
|
1112
|
+
const idx = enabled.findIndex((t) => t.value === current);
|
|
1113
|
+
if (e.key === "ArrowRight") {
|
|
1114
|
+
e.preventDefault();
|
|
1115
|
+
const next = enabled[(idx + 1) % enabled.length];
|
|
1116
|
+
if (next) onChange(next.value);
|
|
1117
|
+
}
|
|
1118
|
+
if (e.key === "ArrowLeft") {
|
|
1119
|
+
e.preventDefault();
|
|
1120
|
+
const prev = enabled[(idx - 1 + enabled.length) % enabled.length];
|
|
1121
|
+
if (prev) onChange(prev.value);
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
return /* @__PURE__ */ jsxs21("div", { className: cn("w-full", className), children: [
|
|
1125
|
+
/* @__PURE__ */ jsx25(
|
|
1126
|
+
"div",
|
|
1127
|
+
{
|
|
1128
|
+
ref: tabListRef,
|
|
1129
|
+
role: "tablist",
|
|
1130
|
+
className: "flex overflow-x-auto border-b border-gray-200 scrollbar-none",
|
|
1131
|
+
style: { scrollbarWidth: "none" },
|
|
1132
|
+
children: tabs.map((tab) => {
|
|
1133
|
+
const isActive = tab.value === value;
|
|
1134
|
+
return /* @__PURE__ */ jsx25(
|
|
1135
|
+
"button",
|
|
1136
|
+
{
|
|
1137
|
+
role: "tab",
|
|
1138
|
+
id: `tab-${tab.value}`,
|
|
1139
|
+
"aria-selected": isActive,
|
|
1140
|
+
"aria-controls": `panel-${tab.value}`,
|
|
1141
|
+
disabled: tab.disabled,
|
|
1142
|
+
tabIndex: isActive ? 0 : -1,
|
|
1143
|
+
onClick: () => !tab.disabled && onChange(tab.value),
|
|
1144
|
+
onKeyDown: (e) => onKeyDown(e, tab.value),
|
|
1145
|
+
className: cn(
|
|
1146
|
+
"inline-flex shrink-0 items-center whitespace-nowrap border-b-2 px-4 py-3 text-sm font-medium",
|
|
1147
|
+
"transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-500",
|
|
1148
|
+
// mobile touch target
|
|
1149
|
+
"min-h-[44px]",
|
|
1150
|
+
isActive ? "border-blue-600 text-blue-600" : "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
|
1151
|
+
tab.disabled && "cursor-not-allowed opacity-40"
|
|
1152
|
+
),
|
|
1153
|
+
children: tab.label
|
|
1154
|
+
},
|
|
1155
|
+
tab.value
|
|
1156
|
+
);
|
|
1157
|
+
})
|
|
1158
|
+
}
|
|
1159
|
+
),
|
|
1160
|
+
children
|
|
1161
|
+
] });
|
|
1162
|
+
}
|
|
1163
|
+
function TabPanel({ value, activeValue, children, className }) {
|
|
1164
|
+
const isActive = value === activeValue;
|
|
1165
|
+
return /* @__PURE__ */ jsx25(
|
|
1166
|
+
"div",
|
|
1167
|
+
{
|
|
1168
|
+
role: "tabpanel",
|
|
1169
|
+
id: `panel-${value}`,
|
|
1170
|
+
"aria-labelledby": `tab-${value}`,
|
|
1171
|
+
hidden: !isActive,
|
|
1172
|
+
className: cn("py-4", className),
|
|
1173
|
+
children: isActive ? children : null
|
|
1174
|
+
}
|
|
1175
|
+
);
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
// src/components/dropdown-menu/dropdown-menu.tsx
|
|
1179
|
+
import * as React14 from "react";
|
|
1180
|
+
import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1181
|
+
function DropdownMenu({ trigger, items, align = "right", className }) {
|
|
1182
|
+
const [open, setOpen] = React14.useState(false);
|
|
1183
|
+
const containerRef = React14.useRef(null);
|
|
1184
|
+
const menuRef = React14.useRef(null);
|
|
1185
|
+
React14.useEffect(() => {
|
|
1186
|
+
if (!open) return;
|
|
1187
|
+
const handler = (e) => {
|
|
1188
|
+
if (!containerRef.current?.contains(e.target)) setOpen(false);
|
|
1189
|
+
};
|
|
1190
|
+
document.addEventListener("mousedown", handler);
|
|
1191
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
1192
|
+
}, [open]);
|
|
1193
|
+
React14.useEffect(() => {
|
|
1194
|
+
if (!open) return;
|
|
1195
|
+
const handler = (e) => {
|
|
1196
|
+
if (e.key === "Escape") {
|
|
1197
|
+
setOpen(false);
|
|
1198
|
+
containerRef.current?.querySelector("[data-trigger]")?.focus();
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
document.addEventListener("keydown", handler);
|
|
1202
|
+
const first = menuRef.current?.querySelector("[role='menuitem']:not([disabled])");
|
|
1203
|
+
first?.focus();
|
|
1204
|
+
return () => document.removeEventListener("keydown", handler);
|
|
1205
|
+
}, [open]);
|
|
1206
|
+
return /* @__PURE__ */ jsxs22("div", { ref: containerRef, className: cn("relative inline-block", className), children: [
|
|
1207
|
+
/* @__PURE__ */ jsx26(
|
|
1208
|
+
"span",
|
|
1209
|
+
{
|
|
1210
|
+
"data-trigger": true,
|
|
1211
|
+
onClick: () => setOpen((v) => !v),
|
|
1212
|
+
"aria-haspopup": "menu",
|
|
1213
|
+
"aria-expanded": open,
|
|
1214
|
+
className: "inline-flex",
|
|
1215
|
+
children: trigger
|
|
1216
|
+
}
|
|
1217
|
+
),
|
|
1218
|
+
open && /* @__PURE__ */ jsx26(
|
|
1219
|
+
"div",
|
|
1220
|
+
{
|
|
1221
|
+
ref: menuRef,
|
|
1222
|
+
role: "menu",
|
|
1223
|
+
className: cn(
|
|
1224
|
+
"absolute z-50 mt-1 min-w-[160px] overflow-hidden rounded-lg border border-gray-200 bg-white py-1 shadow-lg",
|
|
1225
|
+
align === "right" ? "right-0" : "left-0"
|
|
1226
|
+
),
|
|
1227
|
+
children: items.map((item, i) => /* @__PURE__ */ jsxs22(React14.Fragment, { children: [
|
|
1228
|
+
item.divider && /* @__PURE__ */ jsx26("div", { className: "my-1 border-t border-gray-100" }),
|
|
1229
|
+
/* @__PURE__ */ jsxs22(
|
|
1230
|
+
"button",
|
|
1231
|
+
{
|
|
1232
|
+
role: "menuitem",
|
|
1233
|
+
disabled: item.disabled,
|
|
1234
|
+
onClick: () => {
|
|
1235
|
+
if (item.disabled) return;
|
|
1236
|
+
item.onClick?.();
|
|
1237
|
+
setOpen(false);
|
|
1238
|
+
},
|
|
1239
|
+
className: cn(
|
|
1240
|
+
"flex w-full items-center gap-2 px-4 py-2 text-left text-sm",
|
|
1241
|
+
"transition-colors duration-100",
|
|
1242
|
+
"focus-visible:outline-none focus-visible:bg-gray-50",
|
|
1243
|
+
item.destructive ? "text-red-600 hover:bg-red-50" : "text-gray-700 hover:bg-gray-50",
|
|
1244
|
+
item.disabled && "cursor-not-allowed opacity-40"
|
|
1245
|
+
),
|
|
1246
|
+
children: [
|
|
1247
|
+
item.icon && /* @__PURE__ */ jsx26("span", { className: "shrink-0 text-current", "aria-hidden": "true", children: item.icon }),
|
|
1248
|
+
item.label
|
|
1249
|
+
]
|
|
1250
|
+
}
|
|
1251
|
+
)
|
|
1252
|
+
] }, i))
|
|
1253
|
+
}
|
|
1254
|
+
)
|
|
1255
|
+
] });
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
// src/components/pagination/pagination.tsx
|
|
1259
|
+
import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
|
|
1260
|
+
import { jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1261
|
+
function getPages(page, total, siblings) {
|
|
1262
|
+
const delta = siblings;
|
|
1263
|
+
const range = [];
|
|
1264
|
+
for (let i = Math.max(2, page - delta); i <= Math.min(total - 1, page + delta); i++) range.push(i);
|
|
1265
|
+
const pages = [1];
|
|
1266
|
+
const rangeFirst = range[0];
|
|
1267
|
+
const rangeLast = range[range.length - 1];
|
|
1268
|
+
if (rangeFirst !== void 0 && rangeFirst > 2) pages.push("\u2026");
|
|
1269
|
+
pages.push(...range);
|
|
1270
|
+
if (rangeLast !== void 0 && rangeLast < total - 1) pages.push("\u2026");
|
|
1271
|
+
if (total > 1) pages.push(total);
|
|
1272
|
+
return pages;
|
|
1273
|
+
}
|
|
1274
|
+
var btnBase = "inline-flex h-9 min-w-[36px] items-center justify-center rounded-md px-2 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:pointer-events-none disabled:opacity-40";
|
|
1275
|
+
function Pagination({
|
|
1276
|
+
page,
|
|
1277
|
+
totalPages,
|
|
1278
|
+
onChange,
|
|
1279
|
+
siblingCount = 1,
|
|
1280
|
+
className
|
|
1281
|
+
}) {
|
|
1282
|
+
if (totalPages <= 1) return null;
|
|
1283
|
+
const pages = getPages(page, totalPages, siblingCount);
|
|
1284
|
+
return /* @__PURE__ */ jsxs23(
|
|
1285
|
+
"nav",
|
|
1286
|
+
{
|
|
1287
|
+
"aria-label": "Pagination",
|
|
1288
|
+
className: cn("flex flex-wrap items-center justify-center gap-1", className),
|
|
1289
|
+
children: [
|
|
1290
|
+
/* @__PURE__ */ jsxs23(
|
|
1291
|
+
"button",
|
|
1292
|
+
{
|
|
1293
|
+
onClick: () => onChange(page - 1),
|
|
1294
|
+
disabled: page === 1,
|
|
1295
|
+
"aria-label": "Previous page",
|
|
1296
|
+
className: cn(btnBase, "text-gray-600 hover:bg-gray-100"),
|
|
1297
|
+
children: [
|
|
1298
|
+
/* @__PURE__ */ jsx27(ChevronLeft, { size: 16, "aria-hidden": "true" }),
|
|
1299
|
+
/* @__PURE__ */ jsx27("span", { className: "ml-1 hidden sm:inline", children: "Prev" })
|
|
1300
|
+
]
|
|
1301
|
+
}
|
|
1302
|
+
),
|
|
1303
|
+
/* @__PURE__ */ jsx27("div", { className: "hidden sm:flex sm:items-center sm:gap-1", children: pages.map(
|
|
1304
|
+
(p, i) => p === "\u2026" ? /* @__PURE__ */ jsx27("span", { className: "px-1 text-gray-400", children: /* @__PURE__ */ jsx27(MoreHorizontal, { size: 16, "aria-hidden": "true" }) }, `ellipsis-${i}`) : /* @__PURE__ */ jsx27(
|
|
1305
|
+
"button",
|
|
1306
|
+
{
|
|
1307
|
+
onClick: () => onChange(p),
|
|
1308
|
+
"aria-label": `Page ${p}`,
|
|
1309
|
+
"aria-current": p === page ? "page" : void 0,
|
|
1310
|
+
className: cn(
|
|
1311
|
+
btnBase,
|
|
1312
|
+
p === page ? "bg-blue-600 text-white hover:bg-blue-700" : "text-gray-600 hover:bg-gray-100"
|
|
1313
|
+
),
|
|
1314
|
+
children: p
|
|
1315
|
+
},
|
|
1316
|
+
p
|
|
1317
|
+
)
|
|
1318
|
+
) }),
|
|
1319
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-sm text-gray-500 sm:hidden", children: [
|
|
1320
|
+
page,
|
|
1321
|
+
" / ",
|
|
1322
|
+
totalPages
|
|
1323
|
+
] }),
|
|
1324
|
+
/* @__PURE__ */ jsxs23(
|
|
1325
|
+
"button",
|
|
1326
|
+
{
|
|
1327
|
+
onClick: () => onChange(page + 1),
|
|
1328
|
+
disabled: page === totalPages,
|
|
1329
|
+
"aria-label": "Next page",
|
|
1330
|
+
className: cn(btnBase, "text-gray-600 hover:bg-gray-100"),
|
|
1331
|
+
children: [
|
|
1332
|
+
/* @__PURE__ */ jsx27("span", { className: "mr-1 hidden sm:inline", children: "Next" }),
|
|
1333
|
+
/* @__PURE__ */ jsx27(ChevronRight, { size: 16, "aria-hidden": "true" })
|
|
1334
|
+
]
|
|
1335
|
+
}
|
|
1336
|
+
)
|
|
1337
|
+
]
|
|
1338
|
+
}
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/components/tooltip/tooltip.tsx
|
|
1343
|
+
import * as React15 from "react";
|
|
1344
|
+
import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
1345
|
+
var sideClasses = {
|
|
1346
|
+
top: {
|
|
1347
|
+
wrapper: "bottom-full left-1/2 mb-2 -translate-x-1/2",
|
|
1348
|
+
tip: "top-full left-1/2 -translate-x-1/2 border-t-gray-800 border-x-transparent border-b-transparent"
|
|
1349
|
+
},
|
|
1350
|
+
bottom: {
|
|
1351
|
+
wrapper: "top-full left-1/2 mt-2 -translate-x-1/2",
|
|
1352
|
+
tip: "bottom-full left-1/2 -translate-x-1/2 border-b-gray-800 border-x-transparent border-t-transparent"
|
|
1353
|
+
},
|
|
1354
|
+
left: {
|
|
1355
|
+
wrapper: "right-full top-1/2 mr-2 -translate-y-1/2",
|
|
1356
|
+
tip: "left-full top-1/2 -translate-y-1/2 border-l-gray-800 border-y-transparent border-r-transparent"
|
|
1357
|
+
},
|
|
1358
|
+
right: {
|
|
1359
|
+
wrapper: "left-full top-1/2 ml-2 -translate-y-1/2",
|
|
1360
|
+
tip: "right-full top-1/2 -translate-y-1/2 border-r-gray-800 border-y-transparent border-l-transparent"
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
function Tooltip({ content, side = "top", children, className }) {
|
|
1364
|
+
const id = React15.useId();
|
|
1365
|
+
const [visible, setVisible] = React15.useState(false);
|
|
1366
|
+
const show = () => setVisible(true);
|
|
1367
|
+
const hide = () => setVisible(false);
|
|
1368
|
+
const child = React15.cloneElement(children, {
|
|
1369
|
+
"aria-describedby": visible ? id : void 0,
|
|
1370
|
+
onMouseEnter: (e) => {
|
|
1371
|
+
show();
|
|
1372
|
+
children.props.onMouseEnter?.(e);
|
|
1373
|
+
},
|
|
1374
|
+
onMouseLeave: (e) => {
|
|
1375
|
+
hide();
|
|
1376
|
+
children.props.onMouseLeave?.(e);
|
|
1377
|
+
},
|
|
1378
|
+
onFocus: (e) => {
|
|
1379
|
+
show();
|
|
1380
|
+
children.props.onFocus?.(e);
|
|
1381
|
+
},
|
|
1382
|
+
onBlur: (e) => {
|
|
1383
|
+
hide();
|
|
1384
|
+
children.props.onBlur?.(e);
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
return /* @__PURE__ */ jsxs24("span", { className: "relative inline-flex", children: [
|
|
1388
|
+
child,
|
|
1389
|
+
visible && /* @__PURE__ */ jsxs24(
|
|
1390
|
+
"span",
|
|
1391
|
+
{
|
|
1392
|
+
id,
|
|
1393
|
+
role: "tooltip",
|
|
1394
|
+
className: cn(
|
|
1395
|
+
"pointer-events-none absolute z-50 whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-xs text-white shadow-sm",
|
|
1396
|
+
sideClasses[side].wrapper,
|
|
1397
|
+
className
|
|
1398
|
+
),
|
|
1399
|
+
children: [
|
|
1400
|
+
content,
|
|
1401
|
+
/* @__PURE__ */ jsx28(
|
|
1402
|
+
"span",
|
|
1403
|
+
{
|
|
1404
|
+
"aria-hidden": "true",
|
|
1405
|
+
className: cn(
|
|
1406
|
+
"absolute h-0 w-0 border-4",
|
|
1407
|
+
sideClasses[side].tip
|
|
1408
|
+
)
|
|
1409
|
+
}
|
|
1410
|
+
)
|
|
1411
|
+
]
|
|
1412
|
+
}
|
|
1413
|
+
)
|
|
1414
|
+
] });
|
|
1415
|
+
}
|
|
1416
|
+
export {
|
|
1417
|
+
Alert,
|
|
1418
|
+
AppShell,
|
|
1419
|
+
Badge,
|
|
1420
|
+
Button,
|
|
1421
|
+
Card,
|
|
1422
|
+
CardContent,
|
|
1423
|
+
CardDescription,
|
|
1424
|
+
CardFooter,
|
|
1425
|
+
CardHeader,
|
|
1426
|
+
CardTitle,
|
|
1427
|
+
Checkbox,
|
|
1428
|
+
ConfirmDialog,
|
|
1429
|
+
Drawer,
|
|
1430
|
+
DropdownMenu,
|
|
1431
|
+
EmptyState,
|
|
1432
|
+
FormField,
|
|
1433
|
+
Input,
|
|
1434
|
+
Label,
|
|
1435
|
+
Modal,
|
|
1436
|
+
PageHeader,
|
|
1437
|
+
Pagination,
|
|
1438
|
+
Select,
|
|
1439
|
+
Sidebar,
|
|
1440
|
+
Spinner,
|
|
1441
|
+
TabPanel,
|
|
1442
|
+
Table,
|
|
1443
|
+
TableBody,
|
|
1444
|
+
TableCell,
|
|
1445
|
+
TableEmptyState,
|
|
1446
|
+
TableHead,
|
|
1447
|
+
TableHeader,
|
|
1448
|
+
TableRow,
|
|
1449
|
+
TableToolbar,
|
|
1450
|
+
Tabs,
|
|
1451
|
+
Textarea,
|
|
1452
|
+
ToastProvider,
|
|
1453
|
+
Tooltip,
|
|
1454
|
+
Topbar,
|
|
1455
|
+
cn,
|
|
1456
|
+
useToast
|
|
1457
|
+
};
|
|
1458
|
+
//# sourceMappingURL=index.js.map
|