@classytic/fluid 0.2.4 → 0.3.2

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +149 -62
  3. package/dist/api-pagination-CJ0vR_w6.d.mts +34 -0
  4. package/dist/api-pagination-DBTE0yk4.mjs +190 -0
  5. package/dist/chunk-DQk6qfdC.mjs +18 -0
  6. package/dist/client/calendar.d.mts +105 -0
  7. package/dist/client/calendar.mjs +202 -0
  8. package/dist/client/core.d.mts +1614 -0
  9. package/dist/client/core.mjs +2779 -0
  10. package/dist/client/error.d.mts +125 -0
  11. package/dist/client/error.mjs +166 -0
  12. package/dist/client/hooks.d.mts +162 -0
  13. package/dist/client/hooks.mjs +447 -0
  14. package/dist/client/table.d.mts +84 -0
  15. package/dist/client/table.mjs +373 -0
  16. package/dist/client/theme.d.mts +6 -0
  17. package/dist/client/theme.mjs +65 -0
  18. package/dist/command.d.mts +134 -0
  19. package/dist/command.mjs +132 -0
  20. package/dist/compact.d.mts +359 -0
  21. package/dist/compact.mjs +892 -0
  22. package/dist/dashboard.d.mts +778 -0
  23. package/dist/dashboard.mjs +1617 -0
  24. package/dist/filter-utils-DqMmy_v-.mjs +72 -0
  25. package/dist/filter-utils-IZ0GtuPo.d.mts +40 -0
  26. package/dist/forms.d.mts +1549 -0
  27. package/dist/forms.mjs +3740 -0
  28. package/dist/index.d.mts +296 -0
  29. package/dist/index.mjs +432 -0
  30. package/dist/layouts.d.mts +215 -0
  31. package/dist/layouts.mjs +460 -0
  32. package/dist/search-context-DR7DBs7S.mjs +19 -0
  33. package/dist/search.d.mts +254 -0
  34. package/dist/search.mjs +523 -0
  35. package/dist/sheet-wrapper-CWNCvYMD.mjs +211 -0
  36. package/dist/use-base-search-BGgWnWaF.d.mts +35 -0
  37. package/dist/use-debounce-xmZucz5e.mjs +53 -0
  38. package/dist/use-keyboard-shortcut-Bl6YM5Q7.mjs +82 -0
  39. package/dist/use-keyboard-shortcut-_mRCh3QO.d.mts +24 -0
  40. package/dist/use-media-query-BnVNIKT4.mjs +17 -0
  41. package/dist/use-mobile-BX3SQVo2.mjs +20 -0
  42. package/dist/use-scroll-detection-CsgsQYvy.mjs +43 -0
  43. package/dist/utils-CDue7cEt.d.mts +6 -0
  44. package/dist/utils-DQ5SCVoW.mjs +10 -0
  45. package/package.json +85 -45
  46. package/styles.css +2 -2
  47. package/dist/chunk-GUHK2DTW.js +0 -15
  48. package/dist/chunk-GUHK2DTW.js.map +0 -1
  49. package/dist/chunk-H3NFL3GJ.js +0 -57
  50. package/dist/chunk-H3NFL3GJ.js.map +0 -1
  51. package/dist/chunk-J2YRTQE4.js +0 -293
  52. package/dist/chunk-J2YRTQE4.js.map +0 -1
  53. package/dist/compact.d.ts +0 -217
  54. package/dist/compact.js +0 -986
  55. package/dist/compact.js.map +0 -1
  56. package/dist/dashboard.d.ts +0 -387
  57. package/dist/dashboard.js +0 -1032
  58. package/dist/dashboard.js.map +0 -1
  59. package/dist/index.d.ts +0 -2140
  60. package/dist/index.js +0 -6422
  61. package/dist/index.js.map +0 -1
  62. package/dist/layout.d.ts +0 -25
  63. package/dist/layout.js +0 -4
  64. package/dist/layout.js.map +0 -1
  65. package/dist/search.d.ts +0 -172
  66. package/dist/search.js +0 -341
  67. package/dist/search.js.map +0 -1
  68. package/dist/use-base-search-AS5Z3SAy.d.ts +0 -64
  69. package/dist/utils-Cbsgs0XP.d.ts +0 -5
@@ -0,0 +1,892 @@
1
+ "use client";
2
+
3
+ import { t as cn } from "./utils-DQ5SCVoW.mjs";
4
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
+ import React, { createContext, memo, useContext, useId, useMemo, useRef, useState } from "react";
6
+ import { Check, Minus, Plus, Wand2, X } from "lucide-react";
7
+ import { Button } from "@/components/ui/button";
8
+ import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupTextarea } from "@/components/ui/input-group";
9
+ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
10
+ import { Badge } from "@/components/ui/badge";
11
+ import { Input } from "@/components/ui/input";
12
+ import { Controller } from "react-hook-form";
13
+ import { Textarea } from "@/components/ui/textarea";
14
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
15
+
16
+ //#region src/components/compact/field.tsx
17
+ const FieldContext = createContext(null);
18
+ /**
19
+ * Field wrapper — provides context (id, disabled, invalid) and
20
+ * top padding so the floating label has room above the border.
21
+ *
22
+ * Emits `data-slot="field"`, `data-disabled`, `data-invalid` to match
23
+ * the shadcn/ui Field primitive so consuming apps can target via CSS.
24
+ */
25
+ const Root = memo(function FieldRoot({ children, className, disabled = false, invalid = false }) {
26
+ const id = useId();
27
+ const value = useMemo(() => ({
28
+ id,
29
+ disabled,
30
+ invalid
31
+ }), [
32
+ id,
33
+ disabled,
34
+ invalid
35
+ ]);
36
+ return /* @__PURE__ */ jsx(FieldContext.Provider, {
37
+ value,
38
+ children: /* @__PURE__ */ jsx("div", {
39
+ "data-slot": "field",
40
+ "data-disabled": disabled || void 0,
41
+ "data-invalid": invalid || void 0,
42
+ className: cn("w-full pt-3", className),
43
+ children
44
+ })
45
+ });
46
+ });
47
+ /**
48
+ * Animated floating label — rests inside the input when empty,
49
+ * floats up to the border on focus or when the field has a value.
50
+ *
51
+ * The parent `div.group` must set `data-floated` when the field
52
+ * has a value so the label stays floated after blur.
53
+ *
54
+ * Emits `data-slot="field-label"` to match shadcn/ui FieldLabel.
55
+ */
56
+ const Label = memo(function FieldLabel({ children, className }) {
57
+ const ctx = useContext(FieldContext);
58
+ return /* @__PURE__ */ jsx("label", {
59
+ htmlFor: ctx?.id,
60
+ "data-slot": "field-label",
61
+ className: cn("absolute left-3 z-10", "pointer-events-none select-none whitespace-nowrap", "origin-[0] transition-all duration-200 ease-out", "top-1/2 -translate-y-1/2", "text-sm text-muted-foreground", "group-focus-within:top-0 group-focus-within:-translate-y-1/2", "group-focus-within:text-xs group-focus-within:font-medium", "group-focus-within:bg-[var(--compact-field-bg,var(--color-card))] group-focus-within:px-1.5", "group-focus-within:text-primary", "group-data-[floated]:top-0 group-data-[floated]:-translate-y-1/2", "group-data-[floated]:text-xs group-data-[floated]:font-medium", "group-data-[floated]:bg-[var(--compact-field-bg,var(--color-card))] group-data-[floated]:px-1.5", ctx?.disabled && "opacity-60", ctx?.invalid && "text-destructive group-focus-within:text-destructive", className),
62
+ children
63
+ });
64
+ });
65
+ const Error = memo(function FieldError({ children, className }) {
66
+ if (!children) return null;
67
+ return /* @__PURE__ */ jsx("p", {
68
+ role: "alert",
69
+ "data-slot": "field-error",
70
+ className: cn("text-[11px] text-destructive mt-1.5 pl-3", className),
71
+ children
72
+ });
73
+ });
74
+ /**
75
+ * Helper / description text below the input control.
76
+ *
77
+ * Emits `data-slot="field-description"` to match shadcn/ui FieldDescription.
78
+ */
79
+ const Description = memo(function FieldDescription({ children, className }) {
80
+ if (!children) return null;
81
+ return /* @__PURE__ */ jsx("p", {
82
+ "data-slot": "field-description",
83
+ className: cn("text-[11px] text-muted-foreground mt-1.5 pl-3", className),
84
+ children
85
+ });
86
+ });
87
+ const Icon = memo(function FieldIcon({ children, className }) {
88
+ return /* @__PURE__ */ jsx("div", {
89
+ className: cn("absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none z-10", "text-muted-foreground", className),
90
+ children
91
+ });
92
+ });
93
+ const Field = {
94
+ Root,
95
+ Label,
96
+ Error,
97
+ Description,
98
+ Icon
99
+ };
100
+ /**
101
+ * Material-style focus: clean border + outline using the `ring` token
102
+ * to stay consistent with the base Input primitive.
103
+ *
104
+ * Use on standalone `<Input>` / `<Textarea>` inside compact fields.
105
+ */
106
+ const COMPACT_FOCUS = "focus-visible:ring-0 focus-visible:border-ring focus-visible:outline focus-visible:outline-1 focus-visible:-outline-offset-1 focus-visible:outline-ring";
107
+ /**
108
+ * Same focus style but scoped to InputGroup container.
109
+ * Targets the inner `[data-slot=input-group-control]` to override
110
+ * InputGroup's default `ring-3` glow with a clean outline.
111
+ */
112
+ const INPUT_GROUP_FOCUS = "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-0 has-[[data-slot=input-group-control]:focus-visible]:outline has-[[data-slot=input-group-control]:focus-visible]:outline-1 has-[[data-slot=input-group-control]:focus-visible]:-outline-offset-1 has-[[data-slot=input-group-control]:focus-visible]:outline-ring";
113
+ /**
114
+ * Error variant of COMPACT_FOCUS — used when field has validation error.
115
+ */
116
+ const COMPACT_FOCUS_ERROR = "border-destructive focus-visible:border-destructive focus-visible:outline-destructive";
117
+ /**
118
+ * Same as COMPACT_FOCUS but using `focus:` instead of `focus-visible:`.
119
+ * SelectTrigger is a button — buttons use `focus:` in shadcn/ui.
120
+ */
121
+ const COMPACT_SELECT_FOCUS = "focus:ring-0 focus:border-ring focus:outline focus:outline-1 focus:-outline-offset-1 focus:outline-ring";
122
+ const COMPACT_SELECT_FOCUS_ERROR = "border-destructive focus:border-destructive focus:outline-destructive";
123
+
124
+ //#endregion
125
+ //#region src/components/compact/compact-input.tsx
126
+ /**
127
+ * CompactInput - Enhanced form input with InputGroup support
128
+ *
129
+ * Features:
130
+ * - Floating label design
131
+ * - InputGroup support with icons, buttons, and text addons
132
+ * - Controller integration for react-hook-form
133
+ * - Direct usage without form
134
+ */
135
+ function CompactInput({ control, name, description, required, label, placeholder, disabled, type = "text", addonLeft, addonRight, className, inputClassName, onValueChange, value, onChange, error, autoComplete, autoFocus, maxLength, minLength, max, min, pattern, readOnly, step, inputMode, enterKeyHint, ref, ...props }) {
136
+ const hasInputGroup = addonLeft || addonRight;
137
+ const renderInput = (fieldValue, fieldOnChange, isDisabled, fieldError) => {
138
+ const inputProps = {
139
+ ref,
140
+ id: name,
141
+ name,
142
+ type,
143
+ disabled: isDisabled,
144
+ placeholder: label ? void 0 : placeholder,
145
+ value: fieldValue || "",
146
+ onChange: (e) => {
147
+ const newValue = e.target.value;
148
+ fieldOnChange?.(newValue);
149
+ onValueChange?.(newValue);
150
+ },
151
+ className: cn("h-11 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldError && COMPACT_FOCUS_ERROR, inputClassName),
152
+ autoComplete,
153
+ autoFocus,
154
+ maxLength,
155
+ minLength,
156
+ max,
157
+ min,
158
+ pattern,
159
+ readOnly,
160
+ step,
161
+ inputMode,
162
+ enterKeyHint,
163
+ ...props
164
+ };
165
+ if (hasInputGroup) return /* @__PURE__ */ jsxs(InputGroup, {
166
+ className: cn("h-11", INPUT_GROUP_FOCUS),
167
+ children: [
168
+ addonLeft && /* @__PURE__ */ jsx(InputGroupAddon, {
169
+ align: "inline-start",
170
+ children: addonLeft
171
+ }),
172
+ /* @__PURE__ */ jsx(InputGroupInput, { ...inputProps }),
173
+ addonRight && /* @__PURE__ */ jsx(InputGroupAddon, {
174
+ align: "inline-end",
175
+ children: addonRight
176
+ })
177
+ ]
178
+ });
179
+ return /* @__PURE__ */ jsx(Input, { ...inputProps });
180
+ };
181
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
182
+ name,
183
+ control,
184
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
185
+ className,
186
+ disabled,
187
+ invalid: !!fieldState?.error,
188
+ children: [
189
+ /* @__PURE__ */ jsxs("div", {
190
+ className: "relative group",
191
+ "data-floated": !!field.value || void 0,
192
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
193
+ className: addonLeft ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
194
+ children: label
195
+ }), renderInput(field.value, field.onChange, disabled, fieldState?.error?.message)]
196
+ }),
197
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
198
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
199
+ ]
200
+ })
201
+ });
202
+ const handleDirectChange = (newValue) => {
203
+ onChange?.({ target: { value: newValue } });
204
+ onValueChange?.(newValue);
205
+ };
206
+ return /* @__PURE__ */ jsxs(Field.Root, {
207
+ className,
208
+ disabled,
209
+ invalid: !!error,
210
+ children: [
211
+ /* @__PURE__ */ jsxs("div", {
212
+ className: "relative group",
213
+ "data-floated": !!value || void 0,
214
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
215
+ className: addonLeft ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
216
+ children: label
217
+ }), renderInput(value, handleDirectChange, disabled, error)]
218
+ }),
219
+ error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
220
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
221
+ ]
222
+ });
223
+ }
224
+
225
+ //#endregion
226
+ //#region src/components/compact/compact-textarea.tsx
227
+ /**
228
+ * CompactTextarea - Enhanced textarea with InputGroup support
229
+ *
230
+ * Features:
231
+ * - Floating label design
232
+ * - InputGroup support with addons
233
+ * - Character counter with maxLength
234
+ * - Controller integration for react-hook-form
235
+ * - Direct usage without form
236
+ */
237
+ function CompactTextarea({ control, name, description, required, label, placeholder, disabled, rows = 3, addonLeft, addonRight, className, inputClassName, onValueChange, value, onChange, error, cols, wrap, autoComplete, autoFocus, maxLength, minLength, readOnly, spellCheck, ref, ...props }) {
238
+ const hasInputGroup = addonLeft || addonRight;
239
+ const renderTextarea = (fieldValue, fieldOnChange, isDisabled, fieldError) => {
240
+ const currentCharCount = fieldValue?.length || 0;
241
+ const textareaProps = {
242
+ ref,
243
+ id: name,
244
+ name,
245
+ disabled: isDisabled,
246
+ placeholder: label ? void 0 : placeholder,
247
+ rows,
248
+ value: fieldValue || "",
249
+ onChange: (e) => {
250
+ const newValue = e.target.value;
251
+ fieldOnChange?.(newValue);
252
+ onValueChange?.(newValue);
253
+ },
254
+ className: cn("resize-none pt-3 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldError && COMPACT_FOCUS_ERROR, inputClassName),
255
+ cols,
256
+ wrap,
257
+ autoComplete,
258
+ autoFocus,
259
+ maxLength,
260
+ minLength,
261
+ readOnly,
262
+ spellCheck,
263
+ ...props
264
+ };
265
+ if (hasInputGroup) return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(InputGroup, {
266
+ className: INPUT_GROUP_FOCUS,
267
+ children: [
268
+ addonLeft && /* @__PURE__ */ jsx(InputGroupAddon, {
269
+ align: "block-start",
270
+ children: addonLeft
271
+ }),
272
+ /* @__PURE__ */ jsx(InputGroupTextarea, { ...textareaProps }),
273
+ addonRight && /* @__PURE__ */ jsx(InputGroupAddon, {
274
+ align: "block-end",
275
+ children: addonRight
276
+ })
277
+ ]
278
+ }), maxLength && currentCharCount > 0 && /* @__PURE__ */ jsxs("div", {
279
+ className: "text-xs text-muted-foreground mt-1.5 text-right",
280
+ children: [
281
+ currentCharCount,
282
+ "/",
283
+ maxLength
284
+ ]
285
+ })] });
286
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Textarea, { ...textareaProps }), maxLength && currentCharCount > 0 && /* @__PURE__ */ jsxs("div", {
287
+ className: "text-xs text-muted-foreground mt-1.5 text-right",
288
+ children: [
289
+ currentCharCount,
290
+ "/",
291
+ maxLength
292
+ ]
293
+ })] });
294
+ };
295
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
296
+ name,
297
+ control,
298
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
299
+ className,
300
+ disabled,
301
+ invalid: !!fieldState?.error,
302
+ children: [
303
+ /* @__PURE__ */ jsxs("div", {
304
+ className: "relative group",
305
+ "data-floated": !!field.value || void 0,
306
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
307
+ className: "top-3 translate-y-0 group-focus-within:top-0 group-focus-within:-translate-y-1/2 group-data-[floated]:top-0 group-data-[floated]:-translate-y-1/2",
308
+ children: label
309
+ }), renderTextarea(field.value, field.onChange, disabled, fieldState?.error?.message)]
310
+ }),
311
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
312
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
313
+ ]
314
+ })
315
+ });
316
+ const handleDirectChange = (newValue) => {
317
+ onChange?.({ target: { value: newValue } });
318
+ onValueChange?.(newValue);
319
+ };
320
+ return /* @__PURE__ */ jsxs(Field.Root, {
321
+ className,
322
+ disabled,
323
+ invalid: !!error,
324
+ children: [
325
+ /* @__PURE__ */ jsxs("div", {
326
+ className: "relative group",
327
+ "data-floated": !!value || void 0,
328
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
329
+ className: "top-3 translate-y-0 group-focus-within:top-0 group-focus-within:-translate-y-1/2 group-data-[floated]:top-0 group-data-[floated]:-translate-y-1/2",
330
+ children: label
331
+ }), renderTextarea(value, handleDirectChange, disabled, error)]
332
+ }),
333
+ error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
334
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
335
+ ]
336
+ });
337
+ }
338
+
339
+ //#endregion
340
+ //#region src/components/compact/compact-select.tsx
341
+ /**
342
+ * CompactSelect - Simple, clean select dropdown
343
+ *
344
+ * @example
345
+ * <CompactSelect
346
+ * label="Status"
347
+ * items={[
348
+ * { value: "active", label: "Active" },
349
+ * { value: "inactive", label: "Inactive" }
350
+ * ]}
351
+ * />
352
+ */
353
+ function CompactSelect({ control, name, description, required, label, placeholder = "Select option", disabled, items = [], className, onValueChange, value, error, ref, ...props }) {
354
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
355
+ name,
356
+ control,
357
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
358
+ disabled,
359
+ invalid: !!fieldState?.error,
360
+ children: [
361
+ /* @__PURE__ */ jsxs("div", {
362
+ className: "relative group",
363
+ "data-floated": !!field.value || void 0,
364
+ children: [label && /* @__PURE__ */ jsx(Field.Label, { children: label }), /* @__PURE__ */ jsxs(Select, {
365
+ value: field.value,
366
+ onValueChange: (val) => {
367
+ field.onChange(val);
368
+ onValueChange?.(val);
369
+ },
370
+ disabled,
371
+ ...props,
372
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
373
+ ref,
374
+ id: name,
375
+ className: cn("w-full data-[size=default]:h-11 text-sm dark:bg-transparent", COMPACT_SELECT_FOCUS, fieldState?.error && COMPACT_SELECT_FOCUS_ERROR, className),
376
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "" })
377
+ }), /* @__PURE__ */ jsx(SelectContent, { children: items.map((item) => /* @__PURE__ */ jsx(SelectItem, {
378
+ value: item.value,
379
+ children: item.label
380
+ }, item.value)) })]
381
+ })]
382
+ }),
383
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
384
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
385
+ ]
386
+ })
387
+ });
388
+ return /* @__PURE__ */ jsxs(Field.Root, {
389
+ disabled,
390
+ invalid: !!error,
391
+ children: [/* @__PURE__ */ jsxs("div", {
392
+ className: "relative group",
393
+ "data-floated": !!value || void 0,
394
+ children: [label && /* @__PURE__ */ jsx(Field.Label, { children: label }), /* @__PURE__ */ jsxs(Select, {
395
+ value,
396
+ onValueChange,
397
+ disabled,
398
+ ...props,
399
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
400
+ ref,
401
+ className: cn("w-full data-[size=default]:h-11 text-sm dark:bg-transparent", COMPACT_SELECT_FOCUS, error && COMPACT_SELECT_FOCUS_ERROR, className),
402
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "" })
403
+ }), /* @__PURE__ */ jsx(SelectContent, { children: items.map((item) => /* @__PURE__ */ jsx(SelectItem, {
404
+ value: item.value,
405
+ children: item.label
406
+ }, item.value)) })]
407
+ })]
408
+ }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
409
+ });
410
+ }
411
+
412
+ //#endregion
413
+ //#region src/components/compact/compact-number-input.tsx
414
+ /**
415
+ * CompactNumberInput - A space-efficient number input with optional increment/decrement buttons
416
+ *
417
+ * Features:
418
+ * - Floating label design
419
+ * - Optional increment/decrement buttons
420
+ * - Support for min/max/step values
421
+ * - Prefix/suffix support
422
+ * - Form integration via control prop
423
+ * - Direct usage without form
424
+ */
425
+ function CompactNumberInput({ control, name, description, required, label, placeholder, disabled, min = 0, max, step = 1, prefix, suffix, showButtons = false, buttonVariant = "ghost", className, inputClassName, labelClassName, containerClassName, onValueChange, value, defaultValue, onChange, error, ref, ...props }) {
426
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
427
+ name,
428
+ control,
429
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(CompactNumberInputInternal, {
430
+ ref,
431
+ label,
432
+ placeholder,
433
+ disabled,
434
+ min,
435
+ max,
436
+ step,
437
+ prefix,
438
+ suffix,
439
+ showButtons,
440
+ buttonVariant,
441
+ className,
442
+ inputClassName,
443
+ labelClassName,
444
+ containerClassName,
445
+ error: fieldState?.error?.message,
446
+ value: field.value ?? "",
447
+ onChange: (val) => {
448
+ field.onChange(val);
449
+ onValueChange?.(val);
450
+ },
451
+ ...props
452
+ })
453
+ });
454
+ return /* @__PURE__ */ jsx(CompactNumberInputInternal, {
455
+ ref,
456
+ label,
457
+ placeholder,
458
+ disabled,
459
+ min,
460
+ max,
461
+ step,
462
+ prefix,
463
+ suffix,
464
+ showButtons,
465
+ buttonVariant,
466
+ className,
467
+ inputClassName,
468
+ labelClassName,
469
+ containerClassName,
470
+ error,
471
+ value: value ?? "",
472
+ defaultValue,
473
+ onChange,
474
+ onValueChange,
475
+ ...props
476
+ });
477
+ }
478
+ function CompactNumberInputInternal({ label, placeholder, min = 0, max, step = 1, prefix, suffix, showButtons, buttonVariant, error, className, inputClassName, labelClassName, containerClassName, disabled, value, defaultValue, onChange, onValueChange, ref, ...props }) {
479
+ const handleIncrement = () => {
480
+ const newValue = (Number(value) || 0) + step;
481
+ if (max === void 0 || newValue <= max) {
482
+ const finalValue = Number(newValue.toFixed(10));
483
+ onChange?.(finalValue);
484
+ onValueChange?.(finalValue);
485
+ }
486
+ };
487
+ const handleDecrement = () => {
488
+ const newValue = (Number(value) || 0) - step;
489
+ if (newValue >= min) {
490
+ const finalValue = Number(newValue.toFixed(10));
491
+ onChange?.(finalValue);
492
+ onValueChange?.(finalValue);
493
+ }
494
+ };
495
+ const handleInputChange = (e) => {
496
+ const val = e.target.value;
497
+ if (val === "") {
498
+ onChange?.("");
499
+ onValueChange?.("");
500
+ return;
501
+ }
502
+ const numVal = Number(val);
503
+ if (!isNaN(numVal)) {
504
+ if (min !== void 0 && numVal < min || max !== void 0 && numVal > max) return;
505
+ onChange?.(numVal);
506
+ onValueChange?.(numVal);
507
+ }
508
+ };
509
+ const hasValue = value !== "" && value != null;
510
+ if (showButtons) return /* @__PURE__ */ jsxs(Field.Root, {
511
+ className: containerClassName,
512
+ disabled,
513
+ invalid: !!error,
514
+ children: [/* @__PURE__ */ jsxs("div", {
515
+ className: "relative group",
516
+ "data-floated": hasValue || void 0,
517
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
518
+ className: cn("left-12 group-focus-within:left-3 group-data-[floated]:left-3", labelClassName),
519
+ children: label
520
+ }), /* @__PURE__ */ jsxs(InputGroup, {
521
+ className: cn("h-11", INPUT_GROUP_FOCUS),
522
+ children: [
523
+ /* @__PURE__ */ jsx(InputGroupAddon, {
524
+ align: "inline-start",
525
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
526
+ type: "button",
527
+ variant: buttonVariant,
528
+ size: "icon-sm",
529
+ onClick: handleDecrement,
530
+ disabled: disabled || Number(value) <= min,
531
+ children: /* @__PURE__ */ jsx(Minus, { className: "h-4 w-4" })
532
+ })
533
+ }),
534
+ /* @__PURE__ */ jsx(InputGroupInput, {
535
+ ref,
536
+ type: "number",
537
+ value,
538
+ defaultValue,
539
+ onChange: handleInputChange,
540
+ min,
541
+ max,
542
+ step,
543
+ disabled,
544
+ placeholder: label ? void 0 : placeholder,
545
+ className: cn("text-center text-sm", inputClassName, className),
546
+ ...props
547
+ }),
548
+ /* @__PURE__ */ jsx(InputGroupAddon, {
549
+ align: "inline-end",
550
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
551
+ type: "button",
552
+ variant: buttonVariant,
553
+ size: "icon-sm",
554
+ onClick: handleIncrement,
555
+ disabled: disabled || max !== void 0 && Number(value) >= max,
556
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" })
557
+ })
558
+ })
559
+ ]
560
+ })]
561
+ }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
562
+ });
563
+ if (prefix || suffix) return /* @__PURE__ */ jsxs(Field.Root, {
564
+ className: containerClassName,
565
+ disabled,
566
+ invalid: !!error,
567
+ children: [/* @__PURE__ */ jsxs("div", {
568
+ className: "relative group",
569
+ "data-floated": hasValue || void 0,
570
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
571
+ className: cn(prefix && "left-9 group-focus-within:left-3 group-data-[floated]:left-3", labelClassName),
572
+ children: label
573
+ }), /* @__PURE__ */ jsxs(InputGroup, {
574
+ className: cn("h-11", INPUT_GROUP_FOCUS),
575
+ children: [
576
+ prefix && /* @__PURE__ */ jsx(InputGroupAddon, {
577
+ align: "inline-start",
578
+ children: typeof prefix === "string" ? /* @__PURE__ */ jsx("span", {
579
+ className: "text-xs",
580
+ children: prefix
581
+ }) : prefix
582
+ }),
583
+ /* @__PURE__ */ jsx(InputGroupInput, {
584
+ ref,
585
+ type: "number",
586
+ value,
587
+ defaultValue,
588
+ onChange: handleInputChange,
589
+ min,
590
+ max,
591
+ step,
592
+ disabled,
593
+ placeholder: label ? void 0 : placeholder,
594
+ className: cn("text-sm", inputClassName, className),
595
+ ...props
596
+ }),
597
+ suffix && /* @__PURE__ */ jsx(InputGroupAddon, {
598
+ align: "inline-end",
599
+ children: typeof suffix === "string" ? /* @__PURE__ */ jsx("span", {
600
+ className: "text-xs",
601
+ children: suffix
602
+ }) : suffix
603
+ })
604
+ ]
605
+ })]
606
+ }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
607
+ });
608
+ return /* @__PURE__ */ jsxs(Field.Root, {
609
+ className: containerClassName,
610
+ disabled,
611
+ invalid: !!error,
612
+ children: [/* @__PURE__ */ jsxs("div", {
613
+ className: "relative group",
614
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
615
+ className: labelClassName,
616
+ children: label
617
+ }), /* @__PURE__ */ jsx(Input, {
618
+ ref,
619
+ type: "number",
620
+ value,
621
+ defaultValue,
622
+ onChange: handleInputChange,
623
+ min,
624
+ max,
625
+ step,
626
+ disabled,
627
+ placeholder,
628
+ className: cn("h-11 text-sm dark:bg-transparent", COMPACT_FOCUS, error && COMPACT_FOCUS_ERROR, inputClassName, className),
629
+ ...props
630
+ })]
631
+ }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
632
+ });
633
+ }
634
+
635
+ //#endregion
636
+ //#region src/components/compact/compact-tag-choice.tsx
637
+ /**
638
+ * CompactTagChoice - A compact tag selection input
639
+ *
640
+ * Features:
641
+ * - Multi-select tag interface
642
+ * - Maximum selection limit
643
+ * - Popover for selecting options
644
+ * - Form integration via control prop
645
+ * - Direct usage without form
646
+ */
647
+ function CompactTagChoice({ control, name, label, description, placeholder = "Select options...", required, disabled, choices = [], maxSelections, className, containerClassName, labelClassName, inputClassName, value: propValue = [], onChange: propOnChange, onValueChange, error, ref }) {
648
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
649
+ name,
650
+ control,
651
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsx(CompactTagChoiceInternal, {
652
+ ref,
653
+ label,
654
+ placeholder,
655
+ disabled,
656
+ error: fieldState?.error?.message,
657
+ value: field.value || [],
658
+ onChange: (vals) => {
659
+ field.onChange(vals);
660
+ onValueChange?.(vals);
661
+ },
662
+ choices,
663
+ maxSelections,
664
+ containerClassName: cn(className, containerClassName),
665
+ labelClassName,
666
+ inputClassName
667
+ })
668
+ });
669
+ return /* @__PURE__ */ jsx(CompactTagChoiceInternal, {
670
+ ref,
671
+ label,
672
+ placeholder,
673
+ disabled,
674
+ value: propValue,
675
+ onChange: (vals) => {
676
+ propOnChange?.(vals);
677
+ onValueChange?.(vals);
678
+ },
679
+ choices,
680
+ maxSelections,
681
+ containerClassName,
682
+ labelClassName,
683
+ inputClassName,
684
+ className,
685
+ error
686
+ });
687
+ }
688
+ function CompactTagChoiceInternal({ label, placeholder, disabled, value = [], onChange, choices = [], maxSelections, error, className, containerClassName, labelClassName, inputClassName, ref }) {
689
+ const [open, setOpen] = useState(false);
690
+ const triggerRef = useRef(null);
691
+ const availableChoices = choices.filter((c) => !value.includes(c.value));
692
+ const addChoice = (choiceValue) => {
693
+ if (value.includes(choiceValue)) return;
694
+ if (maxSelections && value.length >= maxSelections) return;
695
+ const updated = [...value, choiceValue];
696
+ onChange?.(updated);
697
+ };
698
+ const removeChoice = (choiceValue) => {
699
+ const updated = value.filter((v) => v !== choiceValue);
700
+ onChange?.(updated);
701
+ };
702
+ return /* @__PURE__ */ jsxs(Field.Root, {
703
+ className: containerClassName,
704
+ disabled,
705
+ invalid: !!error,
706
+ children: [/* @__PURE__ */ jsxs("div", {
707
+ className: "relative group",
708
+ "data-floated": value.length > 0 || void 0,
709
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
710
+ className: labelClassName,
711
+ children: label
712
+ }), /* @__PURE__ */ jsxs("div", {
713
+ tabIndex: disabled ? void 0 : 0,
714
+ className: cn("min-h-10 w-full rounded-md border border-input bg-card px-3 py-2 text-sm", "focus-within:border-ring focus-within:outline focus-within:outline-1 focus-within:-outline-offset-1 focus-within:outline-ring", "focus:outline-none", "hover:border-ring/50 transition-all duration-200", error && "border-destructive focus-within:border-destructive focus-within:outline-destructive", disabled && "opacity-50 cursor-not-allowed", inputClassName),
715
+ ref,
716
+ children: [
717
+ /* @__PURE__ */ jsx("div", {
718
+ className: cn("flex flex-wrap gap-1", value.length > 0 && "mb-2"),
719
+ children: value.map((val) => {
720
+ return /* @__PURE__ */ jsxs(Badge, {
721
+ variant: "secondary",
722
+ className: "flex items-center gap-1 px-2 py-0.5 text-xs",
723
+ children: [/* @__PURE__ */ jsx("span", { children: choices.find((c) => c.value === val)?.label ?? val }), !disabled && /* @__PURE__ */ jsx("button", {
724
+ type: "button",
725
+ className: "text-muted-foreground hover:text-foreground ml-0.5",
726
+ onClick: (e) => {
727
+ e.preventDefault();
728
+ e.stopPropagation();
729
+ removeChoice(val);
730
+ },
731
+ children: /* @__PURE__ */ jsx(X, { className: "h-3 w-3" })
732
+ })]
733
+ }, val);
734
+ })
735
+ }),
736
+ !disabled && availableChoices.length > 0 && (!maxSelections || value.length < maxSelections) && /* @__PURE__ */ jsxs(Popover, {
737
+ open,
738
+ onOpenChange: setOpen,
739
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsxs(Button, {
740
+ ref: triggerRef,
741
+ type: "button",
742
+ variant: "ghost",
743
+ className: cn("inline-flex items-center justify-center h-auto p-1 rounded-md text-xs font-medium transition-colors", "text-muted-foreground hover:text-foreground hover:bg-accent", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"),
744
+ onClick: (e) => {
745
+ e.preventDefault();
746
+ e.stopPropagation();
747
+ setOpen(true);
748
+ },
749
+ children: [/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3 mr-1" }), value.length === 0 && !label ? placeholder : "Add..."]
750
+ }) }), /* @__PURE__ */ jsx(PopoverContent, {
751
+ className: "w-[220px] p-0",
752
+ align: "start",
753
+ children: /* @__PURE__ */ jsx("div", {
754
+ className: "p-1",
755
+ children: availableChoices.length === 0 ? /* @__PURE__ */ jsx("div", {
756
+ className: "px-2 py-3 text-sm text-muted-foreground text-center",
757
+ children: "No options available"
758
+ }) : /* @__PURE__ */ jsx("div", {
759
+ className: "space-y-0.5",
760
+ children: availableChoices.map((choice) => /* @__PURE__ */ jsxs("div", {
761
+ onClick: (e) => {
762
+ e.preventDefault();
763
+ e.stopPropagation();
764
+ addChoice(choice.value);
765
+ },
766
+ className: "flex items-center justify-between px-2 py-1.5 text-sm rounded-sm hover:bg-accent hover:text-accent-foreground cursor-pointer",
767
+ children: [/* @__PURE__ */ jsx("span", { children: choice.label }), value.includes(choice.value) && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })]
768
+ }, choice.value))
769
+ })
770
+ })
771
+ })]
772
+ }),
773
+ maxSelections && /* @__PURE__ */ jsxs("div", {
774
+ className: "text-[11px] text-muted-foreground mt-1",
775
+ children: [
776
+ value.length,
777
+ "/",
778
+ maxSelections,
779
+ " selected"
780
+ ]
781
+ })
782
+ ]
783
+ })]
784
+ }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
785
+ });
786
+ }
787
+
788
+ //#endregion
789
+ //#region src/components/compact/compact-slug-field.tsx
790
+ /**
791
+ * Generates a URL-friendly slug from a string
792
+ */
793
+ function generateSlug(text) {
794
+ if (!text) return "";
795
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
796
+ }
797
+ /**
798
+ * CompactSlugField - Compact slug input with auto-generation
799
+ *
800
+ * Features:
801
+ * - Compact design with floating label
802
+ * - Auto-generate slug from source field
803
+ * - Manual editing support
804
+ * - InputGroup with generate button
805
+ * - Form integration via control prop
806
+ */
807
+ function CompactSlugField({ control, name, description, required, label, placeholder = "my-page-slug", disabled, icon, sourceValue, onGenerate, className, inputClassName, onValueChange, value, onChange, error, ref, ...props }) {
808
+ const handleGenerate = (currentValue, fieldOnChange) => {
809
+ const newSlug = onGenerate ? onGenerate(sourceValue || "") : generateSlug(sourceValue || "");
810
+ fieldOnChange(newSlug);
811
+ onValueChange?.(newSlug);
812
+ };
813
+ const renderInput = (fieldValue, fieldOnChange, isDisabled, fieldState) => {
814
+ const inputProps = {
815
+ ref,
816
+ id: name,
817
+ type: "text",
818
+ disabled: isDisabled,
819
+ placeholder: label ? void 0 : placeholder,
820
+ value: fieldValue || "",
821
+ onChange: (e) => {
822
+ const newValue = e.target.value;
823
+ fieldOnChange(newValue);
824
+ onValueChange?.(newValue);
825
+ },
826
+ className: cn("h-11 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldState?.error && COMPACT_FOCUS_ERROR, inputClassName),
827
+ ...props
828
+ };
829
+ return /* @__PURE__ */ jsxs(Fragment, { children: [icon && /* @__PURE__ */ jsx(Field.Icon, { children: icon }), /* @__PURE__ */ jsxs(InputGroup, {
830
+ className: cn("h-11", INPUT_GROUP_FOCUS),
831
+ children: [/* @__PURE__ */ jsx(InputGroupInput, {
832
+ ...inputProps,
833
+ className: cn(inputProps.className, icon && "pl-9")
834
+ }), /* @__PURE__ */ jsx(InputGroupAddon, {
835
+ align: "inline-end",
836
+ children: /* @__PURE__ */ jsxs(InputGroupButton, {
837
+ type: "button",
838
+ size: "sm",
839
+ onClick: () => handleGenerate(fieldValue, fieldOnChange),
840
+ disabled: isDisabled || !sourceValue,
841
+ title: "Generate slug from name",
842
+ children: [/* @__PURE__ */ jsx(Wand2, { className: "h-4 w-4" }), "Generate"]
843
+ })
844
+ })]
845
+ })] });
846
+ };
847
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
848
+ name,
849
+ control,
850
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
851
+ className,
852
+ disabled,
853
+ invalid: !!fieldState?.error,
854
+ children: [
855
+ /* @__PURE__ */ jsxs("div", {
856
+ className: "relative group",
857
+ "data-floated": !!field.value || void 0,
858
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
859
+ className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
860
+ children: label
861
+ }), renderInput(field.value, field.onChange, disabled, fieldState)]
862
+ }),
863
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
864
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
865
+ ]
866
+ })
867
+ });
868
+ const handleDirectChange = (newValue) => {
869
+ onChange?.({ target: { value: newValue } });
870
+ onValueChange?.(newValue);
871
+ };
872
+ return /* @__PURE__ */ jsxs(Field.Root, {
873
+ className,
874
+ disabled,
875
+ invalid: !!error,
876
+ children: [
877
+ /* @__PURE__ */ jsxs("div", {
878
+ className: "relative group",
879
+ "data-floated": !!value || void 0,
880
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
881
+ className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
882
+ children: label
883
+ }), renderInput(value, handleDirectChange, disabled, { error: error ? { message: error } : void 0 })]
884
+ }),
885
+ error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
886
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
887
+ ]
888
+ });
889
+ }
890
+
891
+ //#endregion
892
+ export { CompactInput, CompactNumberInput, CompactSelect, CompactSlugField, CompactTagChoice, CompactTextarea, generateSlug };