@underverse-ui/underverse 0.2.64 → 0.2.65

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.
@@ -0,0 +1,1149 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // ../../lib/constants/constants-ui/button.ts
14
+ var VARIANT_STYLES_BTN = {
15
+ // Mặc định: tối giản, rõ ràng, dùng tokens hệ màu
16
+ default: "bg-background/80 backdrop-blur-sm text-foreground border border-border hover:bg-accent/50 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
17
+ // Viền: trong suốt, viền tinh tế, hover nhẹ
18
+ outline: "bg-transparent backdrop-blur-sm border border-input hover:bg-accent/40 hover:text-accent-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
19
+ // Primary: dùng brand, bỏ hiệu ứng scale/shadow mạnh
20
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-sm border border-primary/20 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
21
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/90 shadow-sm border border-secondary/20 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
22
+ success: "bg-success text-success-foreground hover:bg-success/90 shadow-sm border border-success/20 focus-visible:ring-2 focus-visible:ring-success/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
23
+ danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm border border-destructive/20 focus-visible:ring-2 focus-visible:ring-destructive/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
24
+ // Alias for shadcn-style prop naming used in some components
25
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-sm border border-destructive/20 focus-visible:ring-2 focus-visible:ring-destructive/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
26
+ warning: "bg-warning text-warning-foreground hover:bg-warning/90 shadow-sm border border-warning/20 focus-visible:ring-2 focus-visible:ring-warning/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
27
+ info: "bg-info text-info-foreground hover:bg-info/90 shadow-sm border border-info/20 focus-visible:ring-2 focus-visible:ring-info/60 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
28
+ // Ghost: minimal
29
+ ghost: "bg-transparent hover:bg-accent/20 hover:text-accent-foreground backdrop-blur-sm transition-colors",
30
+ // Link: chỉ văn bản
31
+ link: "text-primary bg-transparent underline-offset-4 hover:underline hover:text-primary/85 transition-colors",
32
+ // Gradient: smooth gradient từ primary → secondary (cùng tone)
33
+ gradient: "bg-linear-to-r from-primary via-primary/80 to-secondary text-primary-foreground hover:opacity-90 shadow-md border-0"
34
+ };
35
+ var SIZE_STYLES_BTN = {
36
+ sm: "px-3 py-1.5 text-sm h-8 min-w-8 md:px-2.5 md:py-1 md:h-7 md:text-xs",
37
+ md: "px-4 py-2 text-sm h-10 min-w-10 md:px-3 md:py-1.5 md:h-9",
38
+ lg: "px-6 py-3 text-base h-12 min-w-12 md:px-4 md:py-2 md:h-10 md:text-sm",
39
+ smx: "px-3.5 py-1.5 text-[13px] h-9 min-w-9 md:px-3 md:py-1 md:h-8 md:text-xs",
40
+ icon: "w-11 h-11 p-0 rounded-full flex items-center justify-center md:w-10 md:h-10"
41
+ };
42
+
43
+ // ../../lib/utils/cn.ts
44
+ import { clsx } from "clsx";
45
+ import { twMerge } from "tailwind-merge";
46
+ function cn(...inputs) {
47
+ return twMerge(clsx(...inputs));
48
+ }
49
+
50
+ // ../../components/ui/Button.tsx
51
+ import { forwardRef, useCallback, useRef, useState } from "react";
52
+ import { Activity } from "lucide-react";
53
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
54
+ var Button = forwardRef(
55
+ ({
56
+ onClick,
57
+ children,
58
+ type = "button",
59
+ icon: Icon,
60
+ iconRight: IconRight,
61
+ variant = "default",
62
+ size = "md",
63
+ className = "",
64
+ iConClassName = "",
65
+ disabled = false,
66
+ loading = false,
67
+ fullWidth = false,
68
+ title,
69
+ spinner: Spinner,
70
+ loadingText,
71
+ preserveChildrenOnLoading = false,
72
+ // custom behavior props (do not forward to DOM)
73
+ preventDoubleClick = false,
74
+ lockMs = 600,
75
+ asContainer = false,
76
+ noWrap = true,
77
+ noHoverOverlay = false,
78
+ gradient = false,
79
+ ...rest
80
+ }, ref) => {
81
+ const baseStyles = asContainer ? "relative inline-flex justify-center rounded-full font-medium transition-colors duration-150 ease-soft outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:translate-y-px" : "relative inline-flex items-center justify-center gap-2 rounded-full font-medium overflow-hidden transition-colors duration-150 ease-soft outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:translate-y-px";
82
+ const finalVariant = gradient ? "gradient" : variant;
83
+ const variantStyle = VARIANT_STYLES_BTN[finalVariant] || VARIANT_STYLES_BTN.default;
84
+ const sizeStyle = SIZE_STYLES_BTN[size] || SIZE_STYLES_BTN.md;
85
+ const SpinnerIcon = Spinner ?? Activity;
86
+ const [locked, setLocked] = useState(false);
87
+ const lockTimer = useRef(null);
88
+ const handleClick = useCallback(
89
+ async (e) => {
90
+ if (disabled || loading) return;
91
+ if (preventDoubleClick) {
92
+ if (locked) return;
93
+ setLocked(true);
94
+ try {
95
+ const result = onClick?.(e);
96
+ if (result && typeof result === "object" && typeof result.then === "function") {
97
+ await result;
98
+ setLocked(false);
99
+ } else {
100
+ const ms = lockMs ?? 600;
101
+ if (lockTimer.current) clearTimeout(lockTimer.current);
102
+ lockTimer.current = setTimeout(() => setLocked(false), ms);
103
+ }
104
+ } catch (err) {
105
+ setLocked(false);
106
+ throw err;
107
+ }
108
+ } else {
109
+ onClick?.(e);
110
+ }
111
+ },
112
+ [disabled, loading, onClick, locked, preventDoubleClick, lockMs]
113
+ );
114
+ const computedDisabled = disabled || loading || (preventDoubleClick ? locked : false);
115
+ return /* @__PURE__ */ jsxs(
116
+ "button",
117
+ {
118
+ ref,
119
+ type,
120
+ onClick: handleClick,
121
+ title,
122
+ className: cn(
123
+ baseStyles,
124
+ variantStyle,
125
+ sizeStyle,
126
+ "group",
127
+ noWrap && "whitespace-nowrap",
128
+ {
129
+ "cursor-pointer hover:opacity-95": !computedDisabled,
130
+ "opacity-50 cursor-not-allowed": computedDisabled,
131
+ "w-full": fullWidth
132
+ },
133
+ className
134
+ ),
135
+ disabled: computedDisabled,
136
+ "aria-disabled": computedDisabled,
137
+ "aria-busy": loading,
138
+ "data-variant": variant,
139
+ "data-size": size,
140
+ "data-locked": preventDoubleClick ? locked ? "true" : "false" : void 0,
141
+ "aria-label": rest["aria-label"] || title,
142
+ ...rest,
143
+ children: [
144
+ !noHoverOverlay ? /* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-linear-to-r from-primary-foreground/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200 dark:hidden" }) : null,
145
+ loading ? /* @__PURE__ */ jsxs(Fragment, { children: [
146
+ /* @__PURE__ */ jsx(SpinnerIcon, { className: "w-4 h-4 animate-spin", "aria-hidden": "true" }),
147
+ loadingText ? /* @__PURE__ */ jsx("span", { className: "ml-2", "aria-live": "polite", children: loadingText }) : null,
148
+ preserveChildrenOnLoading && !loadingText ? /* @__PURE__ */ jsx("span", { className: "ml-2 opacity-70", "aria-hidden": true, children }) : null
149
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
150
+ Icon ? /* @__PURE__ */ jsx(Icon, { className: cn("transition-transform duration-200", iConClassName ? iConClassName : "w-5 h-5") }) : null,
151
+ children,
152
+ IconRight ? /* @__PURE__ */ jsx(IconRight, { className: "w-4 h-4 transition-transform duration-200" }) : null
153
+ ] })
154
+ ]
155
+ }
156
+ );
157
+ }
158
+ );
159
+ Button.displayName = "Button";
160
+ var Button_default = Button;
161
+
162
+ // ../../components/ui/CheckBox.tsx
163
+ import * as React2 from "react";
164
+ import { Check } from "lucide-react";
165
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
166
+ var Checkbox = React2.forwardRef(
167
+ ({ className, label, labelClassName, containerClassName, checked, defaultChecked, onChange, ...props }, ref) => {
168
+ const [internalChecked, setInternalChecked] = React2.useState(defaultChecked ?? false);
169
+ const isControlled = checked !== void 0;
170
+ const isChecked = isControlled ? checked : internalChecked;
171
+ const handleChange = (e) => {
172
+ if (!isControlled) {
173
+ setInternalChecked(e.target.checked);
174
+ }
175
+ onChange?.(e);
176
+ };
177
+ return /* @__PURE__ */ jsxs2("label", { className: cn("inline-flex items-center gap-2 cursor-pointer select-none", containerClassName), children: [
178
+ /* @__PURE__ */ jsx2("input", { type: "checkbox", ref, checked: isChecked, onChange: handleChange, className: "hidden", ...props }),
179
+ /* @__PURE__ */ jsx2(
180
+ "div",
181
+ {
182
+ className: cn(
183
+ "w-5 h-5 border rounded-md flex items-center justify-center transition-all duration-200 ease-soft",
184
+ "hover:shadow-sm active:scale-95",
185
+ isChecked ? "bg-primary border-primary shadow-md text-primary-foreground" : "bg-background border-input hover:border-accent-foreground/30 hover:bg-accent/10"
186
+ ),
187
+ children: isChecked && /* @__PURE__ */ jsx2(Check, { className: "w-4 h-4" })
188
+ }
189
+ ),
190
+ label && /* @__PURE__ */ jsx2("span", { className: cn("text-sm text-foreground", labelClassName), children: label })
191
+ ] });
192
+ }
193
+ );
194
+ Checkbox.displayName = "Checkbox";
195
+
196
+ // ../../lib/i18n/translation-adapter.tsx
197
+ import * as React3 from "react";
198
+ import { jsx as jsx3 } from "react/jsx-runtime";
199
+ var defaultTranslations = {
200
+ en: {
201
+ Common: {
202
+ close: "Close",
203
+ closeAlert: "Close alert",
204
+ notifications: "Notifications",
205
+ newNotification: "New",
206
+ readStatus: "Read",
207
+ openLink: "Open link",
208
+ theme: "Theme",
209
+ lightTheme: "Light",
210
+ darkTheme: "Dark",
211
+ systemTheme: "System",
212
+ density: "Density",
213
+ compact: "Compact",
214
+ normal: "Normal",
215
+ comfortable: "Comfortable",
216
+ columns: "Columns"
217
+ },
218
+ ValidationInput: {
219
+ required: "This field is required",
220
+ typeMismatch: "Invalid format",
221
+ pattern: "Invalid pattern",
222
+ tooShort: "Too short",
223
+ tooLong: "Too long",
224
+ rangeUnderflow: "Below minimum",
225
+ rangeOverflow: "Above maximum",
226
+ stepMismatch: "Step mismatch",
227
+ badInput: "Bad input",
228
+ invalid: "Invalid value"
229
+ },
230
+ Loading: {
231
+ loadingPage: "Loading page",
232
+ pleaseWait: "Please wait"
233
+ },
234
+ DatePicker: {
235
+ placeholder: "Select date",
236
+ today: "Today",
237
+ clear: "Clear"
238
+ },
239
+ Pagination: {
240
+ navigationLabel: "Pagination navigation",
241
+ showingResults: "Showing {startItem}\u2013{endItem} of {totalItems}",
242
+ firstPage: "First page",
243
+ previousPage: "Previous page",
244
+ previous: "Previous",
245
+ nextPage: "Next page",
246
+ next: "Next",
247
+ lastPage: "Last page",
248
+ pageNumber: "Page {page}",
249
+ itemsPerPage: "Items per page",
250
+ search: "Search",
251
+ noOptions: "No options"
252
+ },
253
+ Form: {
254
+ required: "This field is required"
255
+ },
256
+ OCR: {
257
+ imageUpload: {
258
+ dragDropText: "Drag & drop files here",
259
+ browseFiles: "Browse files",
260
+ supportedFormats: "Supported formats: images"
261
+ }
262
+ }
263
+ },
264
+ vi: {
265
+ Common: {
266
+ close: "\u0110\xF3ng",
267
+ closeAlert: "\u0110\xF3ng c\u1EA3nh b\xE1o",
268
+ notifications: "Th\xF4ng b\xE1o",
269
+ newNotification: "M\u1EDBi",
270
+ readStatus: "\u0110\xE3 \u0111\u1ECDc",
271
+ openLink: "M\u1EDF li\xEAn k\u1EBFt",
272
+ theme: "Ch\u1EE7 \u0111\u1EC1",
273
+ lightTheme: "Giao di\u1EC7n s\xE1ng",
274
+ darkTheme: "Giao di\u1EC7n t\u1ED1i",
275
+ systemTheme: "Theo h\u1EC7 th\u1ED1ng",
276
+ density: "M\u1EADt \u0111\u1ED9",
277
+ compact: "G\u1ECDn",
278
+ normal: "Th\u01B0\u1EDDng",
279
+ comfortable: "Tho\u1EA3i m\xE1i",
280
+ columns: "C\u1ED9t"
281
+ },
282
+ ValidationInput: {
283
+ required: "Tr\u01B0\u1EDDng n\xE0y l\xE0 b\u1EAFt bu\u1ED9c",
284
+ typeMismatch: "\u0110\u1ECBnh d\u1EA1ng kh\xF4ng h\u1EE3p l\u1EC7",
285
+ pattern: "Sai m\u1EABu",
286
+ tooShort: "Qu\xE1 ng\u1EAFn",
287
+ tooLong: "Qu\xE1 d\xE0i",
288
+ rangeUnderflow: "Nh\u1ECF h\u01A1n gi\xE1 tr\u1ECB t\u1ED1i thi\u1EC3u",
289
+ rangeOverflow: "L\u1EDBn h\u01A1n gi\xE1 tr\u1ECB t\u1ED1i \u0111a",
290
+ stepMismatch: "Sai b\u01B0\u1EDBc",
291
+ badInput: "Gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7",
292
+ invalid: "Gi\xE1 tr\u1ECB kh\xF4ng h\u1EE3p l\u1EC7"
293
+ },
294
+ Loading: {
295
+ loadingPage: "\u0110ang t\u1EA3i trang",
296
+ pleaseWait: "Vui l\xF2ng ch\u1EDD"
297
+ },
298
+ DatePicker: {
299
+ placeholder: "Ch\u1ECDn ng\xE0y",
300
+ today: "H\xF4m nay",
301
+ clear: "X\xF3a"
302
+ },
303
+ Pagination: {
304
+ navigationLabel: "\u0110i\u1EC1u h\u01B0\u1EDBng ph\xE2n trang",
305
+ showingResults: "Hi\u1EC3n th\u1ECB {startItem}\u2013{endItem} trong t\u1ED5ng {totalItems}",
306
+ firstPage: "Trang \u0111\u1EA7u",
307
+ previousPage: "Trang tr\u01B0\u1EDBc",
308
+ previous: "Tr\u01B0\u1EDBc",
309
+ nextPage: "Trang sau",
310
+ next: "Sau",
311
+ lastPage: "Trang cu\u1ED1i",
312
+ pageNumber: "Trang {page}",
313
+ itemsPerPage: "S\u1ED1 m\u1EE5c/trang",
314
+ search: "T\xECm ki\u1EBFm",
315
+ noOptions: "Kh\xF4ng c\xF3 l\u1EF1a ch\u1ECDn"
316
+ },
317
+ Form: {
318
+ required: "Tr\u01B0\u1EDDng n\xE0y l\xE0 b\u1EAFt bu\u1ED9c"
319
+ },
320
+ OCR: {
321
+ imageUpload: {
322
+ dragDropText: "K\xE9o & th\u1EA3 \u1EA3nh v\xE0o \u0111\xE2y",
323
+ browseFiles: "Ch\u1ECDn t\u1EC7p",
324
+ supportedFormats: "H\u1ED7 tr\u1EE3 c\xE1c \u0111\u1ECBnh d\u1EA1ng \u1EA3nh"
325
+ }
326
+ }
327
+ },
328
+ ko: {
329
+ Common: {
330
+ close: "\uB2EB\uAE30",
331
+ closeAlert: "\uC54C\uB9BC \uB2EB\uAE30",
332
+ notifications: "\uC54C\uB9BC",
333
+ newNotification: "\uC0C8\uB85C\uC6B4",
334
+ readStatus: "\uC77D\uC74C",
335
+ openLink: "\uB9C1\uD06C \uC5F4\uAE30",
336
+ theme: "\uD14C\uB9C8",
337
+ lightTheme: "\uB77C\uC774\uD2B8",
338
+ darkTheme: "\uB2E4\uD06C",
339
+ systemTheme: "\uC2DC\uC2A4\uD15C",
340
+ density: "\uBC00\uB3C4",
341
+ compact: "\uCEF4\uD329\uD2B8",
342
+ normal: "\uBCF4\uD1B5",
343
+ comfortable: "\uC5EC\uC720",
344
+ columns: "\uC5F4"
345
+ },
346
+ ValidationInput: {
347
+ required: "\uD544\uC218 \uC785\uB825 \uD56D\uBAA9\uC785\uB2C8\uB2E4",
348
+ typeMismatch: "\uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
349
+ pattern: "\uD328\uD134\uC774 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
350
+ tooShort: "\uB108\uBB34 \uC9E7\uC2B5\uB2C8\uB2E4",
351
+ tooLong: "\uB108\uBB34 \uAE41\uB2C8\uB2E4",
352
+ rangeUnderflow: "\uCD5C\uC19F\uAC12\uBCF4\uB2E4 \uC791\uC2B5\uB2C8\uB2E4",
353
+ rangeOverflow: "\uCD5C\uB313\uAC12\uC744 \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4",
354
+ stepMismatch: "\uB2E8\uACC4\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
355
+ badInput: "\uC798\uBABB\uB41C \uC785\uB825\uC785\uB2C8\uB2E4",
356
+ invalid: "\uC720\uD6A8\uD558\uC9C0 \uC54A\uC740 \uAC12\uC785\uB2C8\uB2E4"
357
+ },
358
+ Loading: {
359
+ loadingPage: "\uD398\uC774\uC9C0 \uB85C\uB529 \uC911",
360
+ pleaseWait: "\uC7A0\uC2DC\uB9CC \uAE30\uB2E4\uB824 \uC8FC\uC138\uC694"
361
+ },
362
+ DatePicker: {
363
+ placeholder: "\uB0A0\uC9DC \uC120\uD0DD",
364
+ today: "\uC624\uB298",
365
+ clear: "\uC9C0\uC6B0\uAE30"
366
+ },
367
+ Pagination: {
368
+ navigationLabel: "\uD398\uC774\uC9C0 \uB124\uBE44\uAC8C\uC774\uC158",
369
+ showingResults: "{startItem}\u2013{endItem} / \uCD1D {totalItems}\uAC1C",
370
+ firstPage: "\uCCAB \uD398\uC774\uC9C0",
371
+ previousPage: "\uC774\uC804 \uD398\uC774\uC9C0",
372
+ previous: "\uC774\uC804",
373
+ nextPage: "\uB2E4\uC74C \uD398\uC774\uC9C0",
374
+ next: "\uB2E4\uC74C",
375
+ lastPage: "\uB9C8\uC9C0\uB9C9 \uD398\uC774\uC9C0",
376
+ pageNumber: "{page}\uD398\uC774\uC9C0",
377
+ itemsPerPage: "\uD398\uC774\uC9C0\uB2F9 \uD56D\uBAA9 \uC218",
378
+ search: "\uAC80\uC0C9",
379
+ noOptions: "\uC635\uC158 \uC5C6\uC74C"
380
+ },
381
+ Form: {
382
+ required: "\uD544\uC218 \uC785\uB825 \uD56D\uBAA9\uC785\uB2C8\uB2E4"
383
+ },
384
+ OCR: {
385
+ imageUpload: {
386
+ dragDropText: "\uC5EC\uAE30\uC5D0 \uD30C\uC77C\uC744 \uB4DC\uB798\uADF8 \uC564 \uB4DC\uB86D\uD558\uC138\uC694",
387
+ browseFiles: "\uD30C\uC77C \uCC3E\uC544\uBCF4\uAE30",
388
+ supportedFormats: "\uC9C0\uC6D0 \uD615\uC2DD: \uC774\uBBF8\uC9C0"
389
+ }
390
+ }
391
+ },
392
+ ja: {
393
+ Common: {
394
+ close: "\u9589\u3058\u308B",
395
+ closeAlert: "\u30A2\u30E9\u30FC\u30C8\u3092\u9589\u3058\u308B",
396
+ notifications: "\u901A\u77E5",
397
+ newNotification: "\u65B0\u898F",
398
+ readStatus: "\u65E2\u8AAD",
399
+ openLink: "\u30EA\u30F3\u30AF\u3092\u958B\u304F",
400
+ theme: "\u30C6\u30FC\u30DE",
401
+ lightTheme: "\u30E9\u30A4\u30C8",
402
+ darkTheme: "\u30C0\u30FC\u30AF",
403
+ systemTheme: "\u30B7\u30B9\u30C6\u30E0",
404
+ density: "\u5BC6\u5EA6",
405
+ compact: "\u30B3\u30F3\u30D1\u30AF\u30C8",
406
+ normal: "\u901A\u5E38",
407
+ comfortable: "\u3086\u3063\u305F\u308A",
408
+ columns: "\u5217"
409
+ },
410
+ ValidationInput: {
411
+ required: "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059",
412
+ typeMismatch: "\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
413
+ pattern: "\u30D1\u30BF\u30FC\u30F3\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093",
414
+ tooShort: "\u77ED\u3059\u304E\u307E\u3059",
415
+ tooLong: "\u9577\u3059\u304E\u307E\u3059",
416
+ rangeUnderflow: "\u6700\u5C0F\u5024\u3088\u308A\u5C0F\u3055\u3044\u3067\u3059",
417
+ rangeOverflow: "\u6700\u5927\u5024\u3092\u8D85\u3048\u3066\u3044\u307E\u3059",
418
+ stepMismatch: "\u30B9\u30C6\u30C3\u30D7\u304C\u4E00\u81F4\u3057\u307E\u305B\u3093",
419
+ badInput: "\u5165\u529B\u304C\u7121\u52B9\u3067\u3059",
420
+ invalid: "\u7121\u52B9\u306A\u5024\u3067\u3059"
421
+ },
422
+ Loading: {
423
+ loadingPage: "\u30DA\u30FC\u30B8\u3092\u8AAD\u307F\u8FBC\u307F\u4E2D",
424
+ pleaseWait: "\u3057\u3070\u3089\u304F\u304A\u5F85\u3061\u304F\u3060\u3055\u3044"
425
+ },
426
+ DatePicker: {
427
+ placeholder: "\u65E5\u4ED8\u3092\u9078\u629E",
428
+ today: "\u4ECA\u65E5",
429
+ clear: "\u30AF\u30EA\u30A2"
430
+ },
431
+ Pagination: {
432
+ navigationLabel: "\u30DA\u30FC\u30B8\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3",
433
+ showingResults: "{startItem}\u2013{endItem} / \u5168{totalItems}\u4EF6",
434
+ firstPage: "\u6700\u521D\u306E\u30DA\u30FC\u30B8",
435
+ previousPage: "\u524D\u306E\u30DA\u30FC\u30B8",
436
+ previous: "\u524D\u3078",
437
+ nextPage: "\u6B21\u306E\u30DA\u30FC\u30B8",
438
+ next: "\u6B21\u3078",
439
+ lastPage: "\u6700\u5F8C\u306E\u30DA\u30FC\u30B8",
440
+ pageNumber: "{page}\u30DA\u30FC\u30B8",
441
+ itemsPerPage: "1\u30DA\u30FC\u30B8\u3042\u305F\u308A\u306E\u9805\u76EE\u6570",
442
+ search: "\u691C\u7D22",
443
+ noOptions: "\u30AA\u30D7\u30B7\u30E7\u30F3\u306A\u3057"
444
+ },
445
+ Form: {
446
+ required: "\u3053\u306E\u9805\u76EE\u306F\u5FC5\u9808\u3067\u3059"
447
+ },
448
+ OCR: {
449
+ imageUpload: {
450
+ dragDropText: "\u3053\u3053\u306B\u30D5\u30A1\u30A4\u30EB\u3092\u30C9\u30E9\u30C3\u30B0\uFF06\u30C9\u30ED\u30C3\u30D7",
451
+ browseFiles: "\u30D5\u30A1\u30A4\u30EB\u3092\u53C2\u7167",
452
+ supportedFormats: "\u5BFE\u5FDC\u5F62\u5F0F\uFF1A\u753B\u50CF"
453
+ }
454
+ }
455
+ }
456
+ };
457
+ var TranslationContext = React3.createContext(null);
458
+ var UnderverseProvider = ({ children, locale = "en", translations }) => {
459
+ const t = React3.useCallback(
460
+ (namespace) => {
461
+ return (key) => {
462
+ const mergedTranslations = {
463
+ ...defaultTranslations[locale],
464
+ ...translations
465
+ };
466
+ const parts = namespace.split(".");
467
+ let current = mergedTranslations;
468
+ for (const part of parts) {
469
+ if (current && typeof current === "object" && part in current) {
470
+ current = current[part];
471
+ } else {
472
+ return key;
473
+ }
474
+ }
475
+ if (current && typeof current === "object" && key in current) {
476
+ const value = current[key];
477
+ return typeof value === "string" ? value : key;
478
+ }
479
+ return key;
480
+ };
481
+ },
482
+ [locale, translations]
483
+ );
484
+ return /* @__PURE__ */ jsx3(TranslationContext.Provider, { value: { locale, t }, children });
485
+ };
486
+ var nextIntlAvailable = false;
487
+ var nextIntlUseTranslations = null;
488
+ var nextIntlUseLocale = null;
489
+ try {
490
+ const nextIntl = __require("next-intl");
491
+ if (nextIntl && typeof nextIntl.useTranslations === "function") {
492
+ nextIntlAvailable = true;
493
+ nextIntlUseTranslations = nextIntl.useTranslations;
494
+ nextIntlUseLocale = nextIntl.useLocale;
495
+ }
496
+ } catch {
497
+ }
498
+ function interpolate(template, params) {
499
+ if (!params) return template;
500
+ return template.replace(/\{(\w+)\}/g, (_, key) => {
501
+ const value = params[key];
502
+ return value !== void 0 ? String(value) : `{${key}}`;
503
+ });
504
+ }
505
+ function getInternalTranslation(namespace, locale) {
506
+ return (key, params) => {
507
+ const localeTranslations = defaultTranslations[locale] || defaultTranslations.en;
508
+ const parts = namespace.split(".");
509
+ let current = localeTranslations;
510
+ for (const part of parts) {
511
+ if (current && typeof current === "object" && part in current) {
512
+ current = current[part];
513
+ } else {
514
+ return interpolate(key, params);
515
+ }
516
+ }
517
+ if (current && typeof current === "object" && key in current) {
518
+ const value = current[key];
519
+ return typeof value === "string" ? interpolate(value, params) : interpolate(key, params);
520
+ }
521
+ return interpolate(key, params);
522
+ };
523
+ }
524
+ function useTranslations(namespace) {
525
+ const underverseContext = React3.useContext(TranslationContext);
526
+ if (underverseContext) {
527
+ return (key, params) => {
528
+ const result = underverseContext.t(namespace)(key);
529
+ return interpolate(result, params);
530
+ };
531
+ }
532
+ if (nextIntlAvailable && nextIntlUseTranslations) {
533
+ try {
534
+ const nextIntlT = nextIntlUseTranslations(namespace);
535
+ return nextIntlT;
536
+ } catch {
537
+ }
538
+ }
539
+ return getInternalTranslation(namespace, "en");
540
+ }
541
+ function useLocale() {
542
+ const underverseContext = React3.useContext(TranslationContext);
543
+ if (underverseContext) {
544
+ return underverseContext.locale;
545
+ }
546
+ if (nextIntlAvailable && nextIntlUseLocale) {
547
+ try {
548
+ const locale = nextIntlUseLocale();
549
+ return locale === "vi" ? "vi" : "en";
550
+ } catch {
551
+ }
552
+ }
553
+ return "en";
554
+ }
555
+
556
+ // ../../components/ui/Input.tsx
557
+ import React4, { forwardRef as forwardRef3, useId, useState as useState3 } from "react";
558
+ import { Eye, EyeOff, Search, X, AlertCircle, CheckCircle, Loader2 } from "lucide-react";
559
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
560
+ var Input = forwardRef3(
561
+ ({
562
+ label,
563
+ error,
564
+ description,
565
+ className,
566
+ required,
567
+ variant = "default",
568
+ size = "md",
569
+ leftIcon: LeftIcon,
570
+ rightIcon: RightIcon,
571
+ clearable = false,
572
+ loading = false,
573
+ success = false,
574
+ onClear,
575
+ hint,
576
+ counter = false,
577
+ type = "text",
578
+ value,
579
+ maxLength,
580
+ ...rest
581
+ }, ref) => {
582
+ const [localError, setLocalError] = useState3(error);
583
+ const [showPassword, setShowPassword] = useState3(false);
584
+ const [isFocused, setIsFocused] = useState3(false);
585
+ const tv = useTranslations("ValidationInput");
586
+ const autoId = useId();
587
+ const needsId = !!(label || description || hint || error);
588
+ const resolvedId = rest.id || (needsId ? `input-${autoId}` : void 0);
589
+ const errMsg = error || localError;
590
+ const hasValue = value !== void 0 && value !== null && value !== "";
591
+ const charCount = typeof value === "string" ? value.length : 0;
592
+ const errorId = errMsg && resolvedId ? `${resolvedId}-error` : void 0;
593
+ const descId = !errMsg && (description || hint) && resolvedId ? `${resolvedId}-desc` : void 0;
594
+ const containerSpacing = size === "sm" ? "space-y-1.5" : "space-y-2";
595
+ const variantStyles = {
596
+ default: {
597
+ container: "bg-background border border-input hover:border-accent-foreground/20",
598
+ focus: "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
599
+ error: "border-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent"
600
+ },
601
+ filled: {
602
+ container: "bg-muted/50 border border-transparent hover:bg-muted/70",
603
+ focus: "focus-visible:outline-none focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
604
+ error: "bg-destructive/10 border-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent"
605
+ },
606
+ outlined: {
607
+ container: "bg-transparent border border-border hover:border-accent-foreground/30",
608
+ focus: "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
609
+ error: "border-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent"
610
+ },
611
+ minimal: {
612
+ container: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30",
613
+ focus: "focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none",
614
+ error: "border-destructive focus-visible:outline-none focus-visible:border-destructive"
615
+ }
616
+ };
617
+ const sizeStyles = {
618
+ sm: {
619
+ input: "px-3 py-1.5 text-sm h-8 md:h-7 md:py-1 md:text-xs",
620
+ icon: "w-4 h-4",
621
+ button: "h-7 w-7"
622
+ },
623
+ md: {
624
+ input: "px-4 py-2 text-sm h-10",
625
+ icon: "w-5 h-5",
626
+ button: "h-8 w-8"
627
+ },
628
+ lg: {
629
+ input: "px-5 py-4 text-base h-12",
630
+ icon: "w-6 h-6",
631
+ button: "h-10 w-10"
632
+ }
633
+ };
634
+ const getErrorKey = (v) => {
635
+ if (v.valueMissing) return "required";
636
+ if (v.typeMismatch) return "typeMismatch";
637
+ if (v.patternMismatch) return "pattern";
638
+ if (v.tooShort) return "tooShort";
639
+ if (v.tooLong) return "tooLong";
640
+ if (v.rangeUnderflow) return "rangeUnderflow";
641
+ if (v.rangeOverflow) return "rangeOverflow";
642
+ if (v.stepMismatch) return "stepMismatch";
643
+ if (v.badInput) return "badInput";
644
+ return "invalid";
645
+ };
646
+ const getStatusIcon = () => {
647
+ if (loading) return /* @__PURE__ */ jsx4(Loader2, { className: cn("animate-spin text-muted-foreground", sizeStyles[size].icon) });
648
+ if (success) return /* @__PURE__ */ jsx4(CheckCircle, { className: cn("text-success", sizeStyles[size].icon) });
649
+ if (errMsg) return /* @__PURE__ */ jsx4(AlertCircle, { className: cn("text-destructive", sizeStyles[size].icon) });
650
+ return null;
651
+ };
652
+ const showPasswordToggle = type === "password";
653
+ const actualType = showPasswordToggle && showPassword ? "text" : type;
654
+ const { onFocus: onFocusProp, onBlur: onBlurProp, disabled } = rest;
655
+ const {
656
+ label: _label,
657
+ error: _error,
658
+ description: _description,
659
+ variant: _variant,
660
+ size: _size,
661
+ leftIcon: _leftIcon,
662
+ rightIcon: _rightIcon,
663
+ clearable: _clearable,
664
+ loading: _loading,
665
+ success: _success,
666
+ onClear: _onClear,
667
+ hint: _hint,
668
+ counter: _counter,
669
+ ...restInput
670
+ } = rest;
671
+ const handleFocus = (e) => {
672
+ setIsFocused(true);
673
+ onFocusProp?.(e);
674
+ };
675
+ const handleBlur = (e) => {
676
+ setIsFocused(false);
677
+ onBlurProp?.(e);
678
+ };
679
+ const radiusClass = size === "sm" ? "rounded-full" : "rounded-full";
680
+ return /* @__PURE__ */ jsxs3("div", { className: cn("w-full group", containerSpacing), children: [
681
+ label && /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
682
+ /* @__PURE__ */ jsxs3(
683
+ "label",
684
+ {
685
+ htmlFor: resolvedId,
686
+ className: cn(
687
+ // Label size follows input size
688
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
689
+ "font-medium transition-colors duration-200",
690
+ // default color and highlight while any descendant focused
691
+ disabled ? "text-muted-foreground" : cn("text-foreground group-focus-within:text-primary", success && "text-primary"),
692
+ errMsg && "text-destructive"
693
+ ),
694
+ children: [
695
+ label,
696
+ required && /* @__PURE__ */ jsx4("span", { className: "text-destructive ml-1", children: "*" })
697
+ ]
698
+ }
699
+ ),
700
+ counter && maxLength && /* @__PURE__ */ jsxs3(
701
+ "span",
702
+ {
703
+ className: cn(
704
+ "text-xs transition-colors duration-200",
705
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
706
+ charCount >= maxLength && "text-destructive"
707
+ ),
708
+ children: [
709
+ charCount,
710
+ "/",
711
+ maxLength
712
+ ]
713
+ }
714
+ )
715
+ ] }),
716
+ /* @__PURE__ */ jsxs3("div", { className: "relative group", children: [
717
+ LeftIcon && /* @__PURE__ */ jsx4(
718
+ "div",
719
+ {
720
+ className: cn(
721
+ "absolute left-3 top-1/2 -translate-y-1/2 z-10",
722
+ "text-muted-foreground transition-colors duration-200",
723
+ // highlight icon when input (or controls) focused
724
+ "group-focus-within:text-primary"
725
+ ),
726
+ children: /* @__PURE__ */ jsx4(LeftIcon, { className: sizeStyles[size].icon })
727
+ }
728
+ ),
729
+ /* @__PURE__ */ jsx4(
730
+ "input",
731
+ {
732
+ ref,
733
+ type: actualType,
734
+ ...value !== void 0 ? { value } : {},
735
+ maxLength,
736
+ required,
737
+ id: resolvedId,
738
+ autoComplete: type === "password" ? "current-password" : type === "email" ? "email" : rest.autoComplete,
739
+ onFocus: handleFocus,
740
+ onBlur: handleBlur,
741
+ onInvalid: (e) => {
742
+ e.preventDefault();
743
+ const key = getErrorKey(e.currentTarget.validity);
744
+ setLocalError(tv(key));
745
+ },
746
+ onInput: () => setLocalError(void 0),
747
+ "aria-invalid": !!errMsg,
748
+ "aria-describedby": [errorId, descId].filter(Boolean).join(" ") || void 0,
749
+ className: cn(
750
+ "w-full text-foreground transition-all duration-200 ease-out",
751
+ "placeholder:text-muted-foreground focus:outline-none focus-visible:outline-none",
752
+ "disabled:cursor-not-allowed disabled:opacity-50",
753
+ // Size styles
754
+ sizeStyles[size].input,
755
+ radiusClass,
756
+ // Icon padding adjustments
757
+ LeftIcon && "pl-10",
758
+ (RightIcon || showPasswordToggle || clearable || loading || success || errMsg) && "pr-10",
759
+ // Variant styles
760
+ variantStyles[variant].container,
761
+ errMsg ? variantStyles[variant].error : variantStyles[variant].focus,
762
+ // Reduce visual weight for sm: no default drop shadows
763
+ size !== "sm" && variant !== "minimal" && "shadow-sm",
764
+ size !== "sm" && isFocused && "shadow-md",
765
+ className
766
+ ),
767
+ disabled,
768
+ ...restInput
769
+ }
770
+ ),
771
+ /* @__PURE__ */ jsxs3("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
772
+ getStatusIcon(),
773
+ RightIcon && !loading && !success && !errMsg && /* @__PURE__ */ jsx4(RightIcon, { className: cn("text-muted-foreground", sizeStyles[size].icon) }),
774
+ clearable && hasValue && !loading && /* @__PURE__ */ jsx4(
775
+ "button",
776
+ {
777
+ type: "button",
778
+ onClick: () => {
779
+ if (onClear) return onClear();
780
+ rest.onChange?.({ target: { value: "" } });
781
+ },
782
+ className: cn(
783
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-md",
784
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
785
+ "active:bg-accent active:text-accent-foreground",
786
+ sizeStyles[size].button
787
+ ),
788
+ tabIndex: 0,
789
+ "aria-label": "Clear input",
790
+ children: /* @__PURE__ */ jsx4(X, { className: sizeStyles[size].icon })
791
+ }
792
+ ),
793
+ showPasswordToggle && /* @__PURE__ */ jsx4(
794
+ "button",
795
+ {
796
+ type: "button",
797
+ onClick: () => setShowPassword(!showPassword),
798
+ className: cn(
799
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-full",
800
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring/40",
801
+ "active:bg-accent/50 active:text-accent-foreground",
802
+ sizeStyles[size].button
803
+ ),
804
+ tabIndex: 0,
805
+ "aria-label": showPassword ? "Hide password" : "Show password",
806
+ children: showPassword ? /* @__PURE__ */ jsx4(EyeOff, { className: sizeStyles[size].icon }) : /* @__PURE__ */ jsx4(Eye, { className: sizeStyles[size].icon })
807
+ }
808
+ )
809
+ ] }),
810
+ variant === "minimal" && /* @__PURE__ */ jsx4(
811
+ "div",
812
+ {
813
+ className: cn(
814
+ "absolute bottom-0 left-0 h-0.5 bg-linear-to-r from-primary to-primary/60 transition-all duration-300",
815
+ // default hidden
816
+ "w-0 opacity-0",
817
+ // expand underline when focused within input container
818
+ "group-focus-within:w-full group-focus-within:opacity-100"
819
+ )
820
+ }
821
+ )
822
+ ] }),
823
+ errMsg ? /* @__PURE__ */ jsxs3("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
824
+ /* @__PURE__ */ jsx4(AlertCircle, { className: "w-4 h-4 shrink-0" }),
825
+ /* @__PURE__ */ jsx4("span", { children: errMsg })
826
+ ] }) : null,
827
+ (description || hint) && !errMsg && /* @__PURE__ */ jsx4(
828
+ "p",
829
+ {
830
+ id: descId,
831
+ className: cn(
832
+ "text-xs transition-colors duration-200",
833
+ // follow focus state of the whole field area
834
+ "text-muted-foreground group-focus-within:text-primary/70"
835
+ ),
836
+ children: hint || description
837
+ }
838
+ )
839
+ ] });
840
+ }
841
+ );
842
+ Input.displayName = "Input";
843
+ var SearchInput = forwardRef3(
844
+ ({ onSearch, searchDelay = 300, placeholder = "Search\u2026", ...props }, ref) => {
845
+ const [searchValue, setSearchValue] = useState3(props.value || "");
846
+ React4.useEffect(() => {
847
+ if (!onSearch) return;
848
+ const timer = setTimeout(() => {
849
+ onSearch(searchValue);
850
+ }, searchDelay);
851
+ return () => clearTimeout(timer);
852
+ }, [searchValue, onSearch, searchDelay]);
853
+ return /* @__PURE__ */ jsx4(
854
+ Input,
855
+ {
856
+ ref,
857
+ type: "search",
858
+ leftIcon: Search,
859
+ placeholder,
860
+ clearable: true,
861
+ value: searchValue,
862
+ onChange: (e) => setSearchValue(e.target.value),
863
+ onClear: () => setSearchValue(""),
864
+ ...props
865
+ }
866
+ );
867
+ }
868
+ );
869
+ SearchInput.displayName = "SearchInput";
870
+ var PasswordInput = forwardRef3(
871
+ ({ showStrength = false, strengthLabels = ["Weak", "Fair", "Good", "Strong"], ...props }, ref) => {
872
+ const getPasswordStrength = (password) => {
873
+ let score = 0;
874
+ if (password.length >= 8) score++;
875
+ if (/[a-z]/.test(password)) score++;
876
+ if (/[A-Z]/.test(password)) score++;
877
+ if (/[0-9]/.test(password)) score++;
878
+ if (/[^A-Za-z0-9]/.test(password)) score++;
879
+ return Math.min(score, 4);
880
+ };
881
+ const strength = showStrength && typeof props.value === "string" ? getPasswordStrength(props.value) : 0;
882
+ const strengthColors = ["bg-destructive", "bg-warning", "bg-warning", "bg-success"];
883
+ const strengthLabel = strengthLabels[strength - 1];
884
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
885
+ /* @__PURE__ */ jsx4(Input, { ref, type: "password", ...props }),
886
+ showStrength && props.value && /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
887
+ /* @__PURE__ */ jsx4("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx4(
888
+ "div",
889
+ {
890
+ className: cn(
891
+ "h-1 flex-1 rounded-full transition-colors duration-300",
892
+ level <= strength ? strengthColors[strength - 1] : "bg-muted"
893
+ )
894
+ },
895
+ level
896
+ )) }),
897
+ strengthLabel && /* @__PURE__ */ jsx4(
898
+ "p",
899
+ {
900
+ className: cn(
901
+ "text-xs font-medium",
902
+ strength <= 1 ? "text-destructive" : strength <= 2 ? "text-warning" : strength <= 3 ? "text-warning" : "text-success"
903
+ ),
904
+ children: strengthLabel
905
+ }
906
+ )
907
+ ] })
908
+ ] });
909
+ }
910
+ );
911
+ PasswordInput.displayName = "PasswordInput";
912
+ var NumberInput = forwardRef3(
913
+ ({ min, max, step = 1, showSteppers = true, onIncrement, onDecrement, formatThousands = false, locale = "vi-VN", value, onChange, ...props }, ref) => {
914
+ const toNumber = (v) => {
915
+ if (v === "" || v === void 0 || v === null) return 0;
916
+ const n = Number(v);
917
+ return Number.isFinite(n) ? n : 0;
918
+ };
919
+ const format = React4.useCallback((n) => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(n), [locale]);
920
+ const parse = (s) => {
921
+ const digits = (s || "").replace(/\D+/g, "");
922
+ return digits ? Number(digits) : NaN;
923
+ };
924
+ const [displayValue, setDisplayValue] = React4.useState(
925
+ formatThousands ? value !== void 0 && value !== null && value !== "" ? format(toNumber(value)) : "" : String(value ?? "")
926
+ );
927
+ const currentValue = toNumber(value);
928
+ React4.useEffect(() => {
929
+ if (formatThousands) {
930
+ const next = value === "" || value === void 0 || value === null ? "" : format(toNumber(value));
931
+ setDisplayValue((prev) => prev === next ? prev : next);
932
+ } else {
933
+ const next = String(value ?? "");
934
+ setDisplayValue((prev) => prev === next ? prev : next);
935
+ }
936
+ }, [value, formatThousands, locale, format]);
937
+ const handleIncrement = () => {
938
+ if (onIncrement) {
939
+ onIncrement();
940
+ } else if (onChange) {
941
+ const newValue = Math.min(currentValue + step, max ?? Infinity);
942
+ if (formatThousands) setDisplayValue(format(newValue));
943
+ onChange({ target: { value: newValue.toString() } });
944
+ }
945
+ };
946
+ const handleDecrement = () => {
947
+ if (onDecrement) {
948
+ onDecrement();
949
+ } else if (onChange) {
950
+ const newValue = Math.max(currentValue - step, min ?? -Infinity);
951
+ if (formatThousands) setDisplayValue(format(newValue));
952
+ onChange({ target: { value: newValue.toString() } });
953
+ }
954
+ };
955
+ return /* @__PURE__ */ jsxs3("div", { className: "relative", children: [
956
+ /* @__PURE__ */ jsx4(
957
+ Input,
958
+ {
959
+ ref,
960
+ type: formatThousands ? "text" : "number",
961
+ min,
962
+ max,
963
+ step,
964
+ rightIcon: showSteppers ? void 0 : props.rightIcon,
965
+ value: displayValue,
966
+ onChange: (e) => {
967
+ if (!onChange) return;
968
+ if (!formatThousands) return onChange(e);
969
+ const raw = e.target.value;
970
+ const parsed = parse(raw);
971
+ if (Number.isNaN(parsed)) {
972
+ setDisplayValue("");
973
+ onChange({ target: { value: "" } });
974
+ } else {
975
+ const bounded = Math.min(Math.max(parsed, min ?? -Infinity), max ?? Infinity);
976
+ setDisplayValue(format(bounded));
977
+ onChange({ target: { value: bounded.toString() } });
978
+ }
979
+ },
980
+ ...props,
981
+ className: cn(
982
+ showSteppers && [
983
+ "pr-12",
984
+ // Hide native browser steppers
985
+ "[&::-webkit-outer-spin-button]:appearance-none",
986
+ "[&::-webkit-inner-spin-button]:appearance-none",
987
+ "[&::-webkit-inner-spin-button]:m-0",
988
+ "appearance-none"
989
+ ],
990
+ props.className
991
+ )
992
+ }
993
+ ),
994
+ showSteppers && /* @__PURE__ */ jsxs3("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5", children: [
995
+ /* @__PURE__ */ jsx4(
996
+ "button",
997
+ {
998
+ type: "button",
999
+ onClick: handleIncrement,
1000
+ disabled: max !== void 0 && currentValue >= max,
1001
+ className: cn(
1002
+ "flex items-center justify-center w-4 h-4 rounded-md transition-colors",
1003
+ "hover:bg-accent focus:outline-none focus:bg-accent",
1004
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1005
+ "text-muted-foreground hover:text-foreground"
1006
+ ),
1007
+ "aria-label": "Increase value",
1008
+ children: /* @__PURE__ */ jsx4("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "shrink-0", children: /* @__PURE__ */ jsx4("path", { d: "M4 2L6 6H2L4 2Z", fill: "currentColor" }) })
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx4(
1012
+ "button",
1013
+ {
1014
+ type: "button",
1015
+ onClick: handleDecrement,
1016
+ disabled: min !== void 0 && currentValue <= min,
1017
+ className: cn(
1018
+ "flex items-center justify-center w-4 h-4 rounded-md transition-colors",
1019
+ "hover:bg-accent focus:outline-none focus:bg-accent",
1020
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1021
+ "text-muted-foreground hover:text-foreground"
1022
+ ),
1023
+ "aria-label": "Decrease value",
1024
+ children: /* @__PURE__ */ jsx4("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "shrink-0", children: /* @__PURE__ */ jsx4("path", { d: "M4 6L2 2H6L4 6Z", fill: "currentColor" }) })
1025
+ }
1026
+ )
1027
+ ] })
1028
+ ] });
1029
+ }
1030
+ );
1031
+ NumberInput.displayName = "NumberInput";
1032
+ var Textarea = forwardRef3(
1033
+ ({ label, error, description, variant = "default", resize = "vertical", counter = false, className, required, value, maxLength, ...props }, ref) => {
1034
+ const [isFocused, setIsFocused] = useState3(false);
1035
+ const charCount = typeof value === "string" ? value.length : 0;
1036
+ const variantStyles = {
1037
+ default: "bg-background border border-input hover:border-accent-foreground/20 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
1038
+ filled: "bg-muted/50 border border-transparent hover:bg-muted/70 focus-visible:outline-none focus-visible:bg-background focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
1039
+ outlined: "bg-transparent border border-border hover:border-accent-foreground/30 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
1040
+ minimal: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30 focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none"
1041
+ };
1042
+ const resizeClasses = {
1043
+ none: "resize-none",
1044
+ vertical: "resize-y",
1045
+ horizontal: "resize-x",
1046
+ both: "resize"
1047
+ };
1048
+ return /* @__PURE__ */ jsxs3("div", { className: "w-full space-y-2", children: [
1049
+ label && /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between", children: [
1050
+ /* @__PURE__ */ jsxs3(
1051
+ "label",
1052
+ {
1053
+ className: cn(
1054
+ "text-sm font-medium transition-colors duration-200",
1055
+ isFocused ? "text-primary" : "text-foreground",
1056
+ error && "text-destructive"
1057
+ ),
1058
+ children: [
1059
+ label,
1060
+ required && /* @__PURE__ */ jsx4("span", { className: "text-destructive ml-1", children: "*" })
1061
+ ]
1062
+ }
1063
+ ),
1064
+ counter && maxLength && /* @__PURE__ */ jsxs3(
1065
+ "span",
1066
+ {
1067
+ className: cn(
1068
+ "text-xs transition-colors duration-200",
1069
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
1070
+ charCount >= maxLength && "text-destructive"
1071
+ ),
1072
+ children: [
1073
+ charCount,
1074
+ "/",
1075
+ maxLength
1076
+ ]
1077
+ }
1078
+ )
1079
+ ] }),
1080
+ /* @__PURE__ */ jsx4(
1081
+ "textarea",
1082
+ {
1083
+ ref,
1084
+ value,
1085
+ maxLength,
1086
+ required,
1087
+ onFocus: () => setIsFocused(true),
1088
+ onBlur: () => setIsFocused(false),
1089
+ className: cn(
1090
+ "w-full rounded-full px-4 py-3 text-sm text-foreground transition-all duration-200",
1091
+ "placeholder:text-muted-foreground focus:outline-none min-h-20",
1092
+ "disabled:cursor-not-allowed disabled:opacity-50",
1093
+ variantStyles[variant],
1094
+ // DÒNG NÀY ĐÃ ĐƯỢC CẬP NHẬT:
1095
+ error && "border-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-destructive focus-visible:ring-offset-1 focus-visible:ring-offset-background focus-visible:border-transparent",
1096
+ resizeClasses[resize],
1097
+ isFocused && "shadow-md",
1098
+ variant !== "minimal" && "shadow-sm",
1099
+ className
1100
+ ),
1101
+ ...props
1102
+ }
1103
+ ),
1104
+ error && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1105
+ /* @__PURE__ */ jsx4(AlertCircle, { className: "w-4 h-4 shrink-0" }),
1106
+ /* @__PURE__ */ jsx4("span", { children: error })
1107
+ ] }),
1108
+ description && !error && /* @__PURE__ */ jsx4("p", { className: cn("text-xs transition-colors duration-200", isFocused ? "text-primary/70" : "text-muted-foreground"), children: description })
1109
+ ] });
1110
+ }
1111
+ );
1112
+ Textarea.displayName = "Textarea";
1113
+ var Input_default = Input;
1114
+
1115
+ // ../../components/ui/label.tsx
1116
+ import * as React5 from "react";
1117
+ import { cva } from "class-variance-authority";
1118
+ import { jsx as jsx5 } from "react/jsx-runtime";
1119
+ var labelVariants = cva(
1120
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
1121
+ );
1122
+ var Label = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx5(
1123
+ "label",
1124
+ {
1125
+ ref,
1126
+ className: cn(labelVariants(), className),
1127
+ ...props
1128
+ }
1129
+ ));
1130
+ Label.displayName = "Label";
1131
+
1132
+ export {
1133
+ __require,
1134
+ __export,
1135
+ VARIANT_STYLES_BTN,
1136
+ SIZE_STYLES_BTN,
1137
+ cn,
1138
+ Button_default,
1139
+ Checkbox,
1140
+ UnderverseProvider,
1141
+ useTranslations,
1142
+ useLocale,
1143
+ SearchInput,
1144
+ PasswordInput,
1145
+ NumberInput,
1146
+ Input_default,
1147
+ Label
1148
+ };
1149
+ //# sourceMappingURL=chunk-A5NZI37V.js.map