@classytic/fluid 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +21 -1
  2. package/dist/client/calendar.d.mts +1 -2
  3. package/dist/client/calendar.mjs +4 -4
  4. package/dist/client/color-picker.d.mts +94 -0
  5. package/dist/client/color-picker.mjs +392 -0
  6. package/dist/client/core.d.mts +243 -557
  7. package/dist/client/core.mjs +351 -1462
  8. package/dist/client/error.d.mts +41 -41
  9. package/dist/client/error.mjs +35 -35
  10. package/dist/client/gallery.d.mts +175 -0
  11. package/dist/client/gallery.mjs +546 -0
  12. package/dist/client/hooks.d.mts +57 -39
  13. package/dist/client/hooks.mjs +29 -7
  14. package/dist/client/spreadsheet.d.mts +30 -27
  15. package/dist/client/spreadsheet.mjs +80 -80
  16. package/dist/client/table.d.mts +66 -33
  17. package/dist/client/table.mjs +87 -54
  18. package/dist/client/theme.mjs +1 -1
  19. package/dist/command.d.mts +6 -4
  20. package/dist/command.mjs +3 -3
  21. package/dist/compact.d.mts +97 -95
  22. package/dist/compact.mjs +336 -322
  23. package/dist/dashboard.d.mts +614 -422
  24. package/dist/dashboard.mjs +1051 -762
  25. package/dist/{dropdown-wrapper-B86u9Fri.mjs → dropdown-wrapper-B9nRDUlz.mjs} +25 -35
  26. package/dist/forms.d.mts +1037 -972
  27. package/dist/forms.mjs +2849 -2721
  28. package/dist/index.d.mts +218 -152
  29. package/dist/index.mjs +357 -264
  30. package/dist/layouts.d.mts +94 -94
  31. package/dist/layouts.mjs +115 -110
  32. package/dist/phone-input-B9_XPNvv.mjs +429 -0
  33. package/dist/phone-input-CLH_UjQZ.d.mts +31 -0
  34. package/dist/{search-context-DR7DBs7S.mjs → search-context-1g3ZmOvx.mjs} +1 -1
  35. package/dist/search.d.mts +168 -164
  36. package/dist/search.mjs +305 -301
  37. package/dist/{sheet-wrapper-C13Y-Q6w.mjs → sheet-wrapper-B2uxookb.mjs} +1 -1
  38. package/dist/timeline-Bgu1mIe9.d.mts +373 -0
  39. package/dist/timeline-HJtWf4Op.mjs +804 -0
  40. package/dist/{use-base-search-BGgWnWaF.d.mts → use-base-search-DFC4QKYU.d.mts} +1 -1
  41. package/dist/{use-media-query-BnVNIKT4.mjs → use-media-query-ChLfFChU.mjs} +6 -7
  42. package/package.json +10 -2
  43. /package/dist/{api-pagination-CJ0vR_w6.d.mts → api-pagination-C30ser2L.d.mts} +0 -0
  44. /package/dist/{filter-utils-DqMmy_v-.mjs → filter-utils-BGIvtq1R.mjs} +0 -0
  45. /package/dist/{filter-utils-IZ0GtuPo.d.mts → filter-utils-DOFTBWm1.d.mts} +0 -0
  46. /package/dist/{use-debounce-xmZucz5e.mjs → use-debounce-BNoNiEon.mjs} +0 -0
  47. /package/dist/{use-keyboard-shortcut-Bl6YM5Q7.mjs → use-keyboard-shortcut-C_Vk-36P.mjs} +0 -0
  48. /package/dist/{use-keyboard-shortcut-_mRCh3QO.d.mts → use-keyboard-shortcut-Q4CSPzSI.d.mts} +0 -0
  49. /package/dist/{use-mobile-BX3SQVo2.mjs → use-mobile-CnEmFiQx.mjs} +0 -0
  50. /package/dist/{use-scroll-detection-CsgsQYvy.mjs → use-scroll-detection-BKfqkmEC.mjs} +0 -0
  51. /package/dist/{utils-CDue7cEt.d.mts → utils-rqvYP1by.d.mts} +0 -0
package/dist/compact.mjs CHANGED
@@ -1,17 +1,16 @@
1
1
  "use client";
2
2
 
3
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";
4
+ import React, { createContext, memo, useContext, useId, useMemo, useState } from "react";
5
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
+ import { Check, ChevronsUpDown, Minus, Plus, Wand2, X } from "lucide-react";
7
+ import { Badge } from "@/components/ui/badge";
8
8
  import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput, InputGroupTextarea } from "@/components/ui/input-group";
9
9
  import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
10
- import { Badge } from "@/components/ui/badge";
11
10
  import { Input } from "@/components/ui/input";
12
11
  import { Controller } from "react-hook-form";
13
- import { Textarea } from "@/components/ui/textarea";
14
12
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
13
+ import { Textarea } from "@/components/ui/textarea";
15
14
 
16
15
  //#region src/components/compact/field.tsx
17
16
  const FieldContext = createContext(null);
@@ -222,193 +221,6 @@ function CompactInput({ control, name, description, required, label, placeholder
222
221
  });
223
222
  }
224
223
 
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
224
  //#endregion
413
225
  //#region src/components/compact/compact-number-input.tsx
414
226
  /**
@@ -633,16 +445,206 @@ function CompactNumberInputInternal({ label, placeholder, min = 0, max, step = 1
633
445
  }
634
446
 
635
447
  //#endregion
636
- //#region src/components/compact/compact-tag-choice.tsx
448
+ //#region src/components/compact/compact-select.tsx
449
+ const CLEAR_VALUE = "__CLEAR__";
450
+ /**
451
+ * CompactSelect - Floating-label select dropdown
452
+ *
453
+ * @example
454
+ * <CompactSelect
455
+ * label="Status"
456
+ * items={[
457
+ * { value: "active", label: "Active" },
458
+ * { value: "inactive", label: "Inactive" }
459
+ * ]}
460
+ * clearable
461
+ * />
462
+ */
463
+ function CompactSelect({ control, name, description, required, label, placeholder = "Select option", disabled, items = [], clearable, className, onValueChange, value, error, ref, ...props }) {
464
+ const displayItems = useMemo(() => {
465
+ if (clearable) return [{
466
+ value: CLEAR_VALUE,
467
+ label: placeholder
468
+ }, ...items];
469
+ return items;
470
+ }, [
471
+ items,
472
+ clearable,
473
+ placeholder
474
+ ]);
475
+ const handleValueChange = (newValue, fieldOnChange) => {
476
+ const actualValue = newValue === CLEAR_VALUE ? "" : newValue;
477
+ if (fieldOnChange) fieldOnChange(actualValue);
478
+ onValueChange?.(actualValue);
479
+ };
480
+ const toSelectValue = (val) => {
481
+ if (val === "") return CLEAR_VALUE;
482
+ return val;
483
+ };
484
+ const renderSelect = (selectValue, onChange, isDisabled, isInvalid, fieldId) => /* @__PURE__ */ jsxs(Select, {
485
+ value: toSelectValue(selectValue),
486
+ onValueChange: (val) => handleValueChange(val, onChange),
487
+ disabled: isDisabled,
488
+ ...props,
489
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
490
+ ref,
491
+ id: fieldId,
492
+ className: cn("w-full data-[size=default]:h-11 text-sm dark:bg-transparent", COMPACT_SELECT_FOCUS, isInvalid && COMPACT_SELECT_FOCUS_ERROR, className),
493
+ "aria-invalid": isInvalid || void 0,
494
+ "aria-describedby": [description ? `${fieldId ?? name}-description` : void 0, isInvalid ? `${fieldId ?? name}-error` : void 0].filter(Boolean).join(" ") || void 0,
495
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "" })
496
+ }), /* @__PURE__ */ jsx(SelectContent, { children: displayItems.length === 0 ? /* @__PURE__ */ jsx("div", {
497
+ className: "py-6 text-center text-sm text-muted-foreground",
498
+ children: "No options available"
499
+ }) : displayItems.map((item) => /* @__PURE__ */ jsx(SelectItem, {
500
+ value: item.value,
501
+ disabled: item.disabled,
502
+ children: item.label
503
+ }, item.value)) })]
504
+ });
505
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
506
+ name,
507
+ control,
508
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
509
+ disabled,
510
+ invalid: !!fieldState?.error,
511
+ children: [
512
+ /* @__PURE__ */ jsxs("div", {
513
+ className: "relative group",
514
+ "data-floated": !!field.value || void 0,
515
+ children: [label && /* @__PURE__ */ jsx(Field.Label, { children: label }), renderSelect(field.value, field.onChange, disabled, !!fieldState?.error, name)]
516
+ }),
517
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
518
+ description && /* @__PURE__ */ jsx(Field.Description, { children: description })
519
+ ]
520
+ })
521
+ });
522
+ return /* @__PURE__ */ jsxs(Field.Root, {
523
+ disabled,
524
+ invalid: !!error,
525
+ children: [
526
+ /* @__PURE__ */ jsxs("div", {
527
+ className: "relative group",
528
+ "data-floated": !!value || void 0,
529
+ children: [label && /* @__PURE__ */ jsx(Field.Label, { children: label }), renderSelect(value, () => {}, disabled, !!error)]
530
+ }),
531
+ error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
532
+ description && /* @__PURE__ */ jsx(Field.Description, { children: description })
533
+ ]
534
+ });
535
+ }
536
+
537
+ //#endregion
538
+ //#region src/components/compact/compact-slug-field.tsx
539
+ /**
540
+ * Generates a URL-friendly slug from a string
541
+ */
542
+ function generateSlug(text) {
543
+ if (!text) return "";
544
+ return text.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
545
+ }
637
546
  /**
638
- * CompactTagChoice - A compact tag selection input
547
+ * CompactSlugField - Compact slug input with auto-generation
639
548
  *
640
549
  * Features:
641
- * - Multi-select tag interface
642
- * - Maximum selection limit
643
- * - Popover for selecting options
550
+ * - Compact design with floating label
551
+ * - Auto-generate slug from source field
552
+ * - Manual editing support
553
+ * - InputGroup with generate button
644
554
  * - Form integration via control prop
645
- * - Direct usage without form
555
+ */
556
+ function CompactSlugField({ control, name, description, required, label, placeholder = "my-page-slug", disabled, icon, sourceValue, onGenerate, className, inputClassName, onValueChange, value, onChange, error, ref, ...props }) {
557
+ const handleGenerate = (currentValue, fieldOnChange) => {
558
+ const newSlug = onGenerate ? onGenerate(sourceValue || "") : generateSlug(sourceValue || "");
559
+ fieldOnChange(newSlug);
560
+ onValueChange?.(newSlug);
561
+ };
562
+ const renderInput = (fieldValue, fieldOnChange, isDisabled, fieldState) => {
563
+ const inputProps = {
564
+ ref,
565
+ id: name,
566
+ type: "text",
567
+ disabled: isDisabled,
568
+ placeholder: label ? void 0 : placeholder,
569
+ value: fieldValue || "",
570
+ onChange: (e) => {
571
+ const newValue = e.target.value;
572
+ fieldOnChange(newValue);
573
+ onValueChange?.(newValue);
574
+ },
575
+ className: cn("h-11 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldState?.error && COMPACT_FOCUS_ERROR, inputClassName),
576
+ ...props
577
+ };
578
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [icon && /* @__PURE__ */ jsx(Field.Icon, { children: icon }), /* @__PURE__ */ jsxs(InputGroup, {
579
+ className: cn("h-11", INPUT_GROUP_FOCUS),
580
+ children: [/* @__PURE__ */ jsx(InputGroupInput, {
581
+ ...inputProps,
582
+ className: cn(inputProps.className, icon && "pl-9")
583
+ }), /* @__PURE__ */ jsx(InputGroupAddon, {
584
+ align: "inline-end",
585
+ children: /* @__PURE__ */ jsxs(InputGroupButton, {
586
+ type: "button",
587
+ size: "sm",
588
+ onClick: () => handleGenerate(fieldValue, fieldOnChange),
589
+ disabled: isDisabled || !sourceValue,
590
+ title: "Generate slug from name",
591
+ children: [/* @__PURE__ */ jsx(Wand2, { className: "h-4 w-4" }), "Generate"]
592
+ })
593
+ })]
594
+ })] });
595
+ };
596
+ if (control && name) return /* @__PURE__ */ jsx(Controller, {
597
+ name,
598
+ control,
599
+ render: ({ field, fieldState }) => /* @__PURE__ */ jsxs(Field.Root, {
600
+ className,
601
+ disabled,
602
+ invalid: !!fieldState?.error,
603
+ children: [
604
+ /* @__PURE__ */ jsxs("div", {
605
+ className: "relative group",
606
+ "data-floated": !!field.value || void 0,
607
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
608
+ className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
609
+ children: label
610
+ }), renderInput(field.value, field.onChange, disabled, fieldState)]
611
+ }),
612
+ fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
613
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
614
+ ]
615
+ })
616
+ });
617
+ const handleDirectChange = (newValue) => {
618
+ onChange?.({ target: { value: newValue } });
619
+ onValueChange?.(newValue);
620
+ };
621
+ return /* @__PURE__ */ jsxs(Field.Root, {
622
+ className,
623
+ disabled,
624
+ invalid: !!error,
625
+ children: [
626
+ /* @__PURE__ */ jsxs("div", {
627
+ className: "relative group",
628
+ "data-floated": !!value || void 0,
629
+ children: [label && /* @__PURE__ */ jsx(Field.Label, {
630
+ className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
631
+ children: label
632
+ }), renderInput(value, handleDirectChange, disabled, { error: error ? { message: error } : void 0 })]
633
+ }),
634
+ error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
635
+ /* @__PURE__ */ jsx(Field.Description, { children: description })
636
+ ]
637
+ });
638
+ }
639
+
640
+ //#endregion
641
+ //#region src/components/compact/compact-tag-choice.tsx
642
+ /**
643
+ * CompactTagChoice - A compact tag selection input with floating label
644
+ *
645
+ * Uses a trigger button that shows selected count or placeholder,
646
+ * with a popover dropdown listing available choices.
647
+ * Selected items display as inline badges below the trigger.
646
648
  */
647
649
  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
650
  if (control && name) return /* @__PURE__ */ jsx(Controller, {
@@ -687,17 +689,20 @@ function CompactTagChoice({ control, name, label, description, placeholder = "Se
687
689
  }
688
690
  function CompactTagChoiceInternal({ label, placeholder, disabled, value = [], onChange, choices = [], maxSelections, error, className, containerClassName, labelClassName, inputClassName, ref }) {
689
691
  const [open, setOpen] = useState(false);
690
- const triggerRef = useRef(null);
691
- const availableChoices = choices.filter((c) => !value.includes(c.value));
692
+ const hasValue = value.length > 0;
693
+ const canAdd = !maxSelections || value.length < maxSelections;
694
+ choices.filter((c) => !value.includes(c.value));
692
695
  const addChoice = (choiceValue) => {
693
696
  if (value.includes(choiceValue)) return;
694
697
  if (maxSelections && value.length >= maxSelections) return;
695
- const updated = [...value, choiceValue];
696
- onChange?.(updated);
698
+ onChange?.([...value, choiceValue]);
697
699
  };
698
700
  const removeChoice = (choiceValue) => {
699
- const updated = value.filter((v) => v !== choiceValue);
700
- onChange?.(updated);
701
+ onChange?.(value.filter((v) => v !== choiceValue));
702
+ };
703
+ const toggleChoice = (choiceValue) => {
704
+ if (value.includes(choiceValue)) removeChoice(choiceValue);
705
+ else addChoice(choiceValue);
701
706
  };
702
707
  return /* @__PURE__ */ jsxs(Field.Root, {
703
708
  className: containerClassName,
@@ -705,143 +710,152 @@ function CompactTagChoiceInternal({ label, placeholder, disabled, value = [], on
705
710
  invalid: !!error,
706
711
  children: [/* @__PURE__ */ jsxs("div", {
707
712
  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", {
713
+ "data-floated": hasValue || open || void 0,
714
+ ref,
715
+ children: [
716
+ label && /* @__PURE__ */ jsx(Field.Label, {
717
+ className: labelClassName,
718
+ children: label
719
+ }),
720
+ /* @__PURE__ */ jsxs(Popover, {
721
+ open,
722
+ onOpenChange: disabled ? void 0 : setOpen,
723
+ children: [/* @__PURE__ */ jsxs(PopoverTrigger, {
724
+ render: /* @__PURE__ */ jsx("button", {
725
+ type: "button",
726
+ disabled,
727
+ className: cn("flex h-11 w-full items-center justify-between rounded-md border border-input bg-card px-3 text-sm", "transition-all duration-200 hover:border-ring/50", COMPACT_FOCUS, error && "border-destructive focus-visible:border-destructive focus-visible:outline-destructive", disabled && "opacity-50 cursor-not-allowed", inputClassName)
728
+ }),
729
+ children: [/* @__PURE__ */ jsx("span", {
730
+ className: cn("truncate", !hasValue && "text-transparent"),
731
+ children: hasValue ? `${value.length} selected` : label || placeholder
732
+ }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "size-3.5 shrink-0 opacity-50" })]
733
+ }), /* @__PURE__ */ jsx(PopoverContent, {
734
+ className: "w-[var(--popover-trigger-width)] p-1",
735
+ align: "start",
736
+ children: choices.length === 0 ? /* @__PURE__ */ jsx("div", {
737
+ className: "px-2 py-3 text-sm text-muted-foreground text-center",
738
+ children: "No options available"
739
+ }) : /* @__PURE__ */ jsxs("div", {
740
+ className: "space-y-0.5",
741
+ children: [choices.map((choice) => {
742
+ const isSelected = value.includes(choice.value);
743
+ const isDisabledChoice = !isSelected && !canAdd;
744
+ return /* @__PURE__ */ jsxs("button", {
724
745
  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);
746
+ disabled: isDisabledChoice,
747
+ onClick: () => toggleChoice(choice.value),
748
+ className: cn("flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm text-left", "hover:bg-accent hover:text-accent-foreground cursor-pointer", "focus-visible:outline-none focus-visible:bg-accent", isSelected && "bg-accent/50", isDisabledChoice && "opacity-40 cursor-not-allowed"),
749
+ children: [/* @__PURE__ */ jsx("div", {
750
+ className: cn("flex size-4 shrink-0 items-center justify-center rounded-sm border", isSelected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30"),
751
+ children: isSelected && /* @__PURE__ */ jsx(Check, { className: "size-3" })
752
+ }), /* @__PURE__ */ jsx("span", {
753
+ className: "flex-1",
754
+ children: choice.label
755
+ })]
756
+ }, choice.value);
757
+ }), maxSelections && /* @__PURE__ */ jsxs("div", {
758
+ className: "px-2 pt-1 pb-0.5 text-[11px] text-muted-foreground border-t mt-1",
759
+ children: [
760
+ value.length,
761
+ "/",
762
+ maxSelections,
763
+ " selected"
764
+ ]
765
+ })]
734
766
  })
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
- ]
767
+ })]
768
+ }),
769
+ hasValue && /* @__PURE__ */ jsx("div", {
770
+ className: "flex flex-wrap gap-1 mt-1.5 px-0.5",
771
+ children: value.map((val) => {
772
+ const choice = choices.find((c) => c.value === val);
773
+ return /* @__PURE__ */ jsxs(Badge, {
774
+ variant: "secondary",
775
+ className: "gap-1 px-2 py-0.5 text-xs",
776
+ children: [choice?.label ?? val, !disabled && /* @__PURE__ */ jsx("button", {
777
+ type: "button",
778
+ className: "text-muted-foreground hover:text-foreground -mr-0.5",
779
+ onClick: () => removeChoice(val),
780
+ "aria-label": `Remove ${choice?.label ?? val}`,
781
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
782
+ })]
783
+ }, val);
781
784
  })
782
- ]
783
- })]
785
+ })
786
+ ]
784
787
  }), error && /* @__PURE__ */ jsx(Field.Error, { children: error })]
785
788
  });
786
789
  }
787
790
 
788
791
  //#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
- }
792
+ //#region src/components/compact/compact-textarea.tsx
797
793
  /**
798
- * CompactSlugField - Compact slug input with auto-generation
794
+ * CompactTextarea - Enhanced textarea with InputGroup support
799
795
  *
800
796
  * 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
797
+ * - Floating label design
798
+ * - InputGroup support with addons
799
+ * - Character counter with maxLength
800
+ * - Controller integration for react-hook-form
801
+ * - Direct usage without form
806
802
  */
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 = {
803
+ 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 }) {
804
+ const hasInputGroup = addonLeft || addonRight;
805
+ const renderTextarea = (fieldValue, fieldOnChange, isDisabled, fieldError) => {
806
+ const currentCharCount = fieldValue?.length || 0;
807
+ const textareaProps = {
815
808
  ref,
816
809
  id: name,
817
- type: "text",
810
+ name,
818
811
  disabled: isDisabled,
819
812
  placeholder: label ? void 0 : placeholder,
813
+ rows,
820
814
  value: fieldValue || "",
821
815
  onChange: (e) => {
822
816
  const newValue = e.target.value;
823
- fieldOnChange(newValue);
817
+ fieldOnChange?.(newValue);
824
818
  onValueChange?.(newValue);
825
819
  },
826
- className: cn("h-11 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldState?.error && COMPACT_FOCUS_ERROR, inputClassName),
820
+ className: cn("resize-none pt-3 text-sm dark:bg-transparent", COMPACT_FOCUS, fieldError && COMPACT_FOCUS_ERROR, inputClassName),
821
+ cols,
822
+ wrap,
823
+ autoComplete,
824
+ autoFocus,
825
+ maxLength,
826
+ minLength,
827
+ readOnly,
828
+ spellCheck,
827
829
  ...props
828
830
  };
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"]
831
+ if (hasInputGroup) return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(InputGroup, {
832
+ className: INPUT_GROUP_FOCUS,
833
+ children: [
834
+ addonLeft && /* @__PURE__ */ jsx(InputGroupAddon, {
835
+ align: "block-start",
836
+ children: addonLeft
837
+ }),
838
+ /* @__PURE__ */ jsx(InputGroupTextarea, { ...textareaProps }),
839
+ addonRight && /* @__PURE__ */ jsx(InputGroupAddon, {
840
+ align: "block-end",
841
+ children: addonRight
843
842
  })
844
- })]
843
+ ]
844
+ }), maxLength && currentCharCount > 0 && /* @__PURE__ */ jsxs("div", {
845
+ className: "text-xs text-muted-foreground mt-1.5 text-right",
846
+ children: [
847
+ currentCharCount,
848
+ "/",
849
+ maxLength
850
+ ]
851
+ })] });
852
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Textarea, { ...textareaProps }), maxLength && currentCharCount > 0 && /* @__PURE__ */ jsxs("div", {
853
+ className: "text-xs text-muted-foreground mt-1.5 text-right",
854
+ children: [
855
+ currentCharCount,
856
+ "/",
857
+ maxLength
858
+ ]
845
859
  })] });
846
860
  };
847
861
  if (control && name) return /* @__PURE__ */ jsx(Controller, {
@@ -856,9 +870,9 @@ function CompactSlugField({ control, name, description, required, label, placeho
856
870
  className: "relative group",
857
871
  "data-floated": !!field.value || void 0,
858
872
  children: [label && /* @__PURE__ */ jsx(Field.Label, {
859
- className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
873
+ 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",
860
874
  children: label
861
- }), renderInput(field.value, field.onChange, disabled, fieldState)]
875
+ }), renderTextarea(field.value, field.onChange, disabled, fieldState?.error?.message)]
862
876
  }),
863
877
  fieldState?.error && /* @__PURE__ */ jsx(Field.Error, { children: fieldState.error.message }),
864
878
  /* @__PURE__ */ jsx(Field.Description, { children: description })
@@ -878,9 +892,9 @@ function CompactSlugField({ control, name, description, required, label, placeho
878
892
  className: "relative group",
879
893
  "data-floated": !!value || void 0,
880
894
  children: [label && /* @__PURE__ */ jsx(Field.Label, {
881
- className: icon ? "left-9 group-focus-within:left-3 group-data-[floated]:left-3" : void 0,
895
+ 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",
882
896
  children: label
883
- }), renderInput(value, handleDirectChange, disabled, { error: error ? { message: error } : void 0 })]
897
+ }), renderTextarea(value, handleDirectChange, disabled, error)]
884
898
  }),
885
899
  error && /* @__PURE__ */ jsx(Field.Error, { children: error }),
886
900
  /* @__PURE__ */ jsx(Field.Description, { children: description })