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