@underverse-ui/underverse 0.1.30 → 0.1.32

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/index.js CHANGED
@@ -469,583 +469,557 @@ import React4, { forwardRef as forwardRef3, useId, useState as useState4 } from
469
469
  import { useTranslations } from "next-intl";
470
470
  import { Eye, EyeOff, Search, X, AlertCircle, CheckCircle, Loader2 } from "lucide-react";
471
471
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
472
- var Input = forwardRef3(({
473
- label,
474
- error,
475
- description,
476
- className,
477
- required,
478
- variant = "default",
479
- size = "md",
480
- leftIcon: LeftIcon,
481
- rightIcon: RightIcon,
482
- clearable = false,
483
- loading: loading2 = false,
484
- success = false,
485
- onClear,
486
- hint,
487
- counter = false,
488
- type = "text",
489
- value,
490
- maxLength,
491
- ...rest
492
- }, ref) => {
493
- const [localError, setLocalError] = useState4(error);
494
- const [showPassword, setShowPassword] = useState4(false);
495
- const [isFocused, setIsFocused] = useState4(false);
496
- const tv = useTranslations("ValidationInput");
497
- const autoId = useId();
498
- const needsId = !!(label || description || hint || error);
499
- const resolvedId = rest.id || (needsId ? `input-${autoId}` : void 0);
500
- const errMsg = error || localError;
501
- const hasValue = value !== void 0 && value !== null && value !== "";
502
- const charCount = typeof value === "string" ? value.length : 0;
503
- const errorId = errMsg && resolvedId ? `${resolvedId}-error` : void 0;
504
- const descId = !errMsg && (description || hint) && resolvedId ? `${resolvedId}-desc` : void 0;
505
- const containerSpacing = size === "sm" ? "space-y-1.5" : "space-y-2";
506
- const variantStyles6 = {
507
- default: {
508
- container: "bg-background border border-input hover:border-accent-foreground/20",
509
- focus: "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
510
- error: "border-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent"
511
- },
512
- filled: {
513
- container: "bg-muted/50 border border-transparent hover:bg-muted/70",
514
- focus: "focus-visible:outline-none focus-visible:bg-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
515
- error: "bg-destructive/10 border-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent"
516
- },
517
- outlined: {
518
- container: "bg-transparent border border-border hover:border-accent-foreground/30",
519
- focus: "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
520
- error: "border-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent"
521
- },
522
- minimal: {
523
- container: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30",
524
- focus: "focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none",
525
- error: "border-destructive focus-visible:outline-none focus-visible:border-destructive"
526
- }
527
- };
528
- const sizeStyles8 = {
529
- sm: {
530
- input: "px-3 py-1.5 text-sm h-8 md:h-7 md:py-1 md:text-xs",
531
- icon: "w-4 h-4",
532
- button: "h-7 w-7"
533
- },
534
- md: {
535
- input: "px-4 py-2 text-sm h-10",
536
- icon: "w-5 h-5",
537
- button: "h-8 w-8"
538
- },
539
- lg: {
540
- input: "px-5 py-4 text-base h-12",
541
- icon: "w-6 h-6",
542
- button: "h-10 w-10"
543
- }
544
- };
545
- const getErrorKey = (v) => {
546
- if (v.valueMissing) return "required";
547
- if (v.typeMismatch) return "typeMismatch";
548
- if (v.patternMismatch) return "pattern";
549
- if (v.tooShort) return "tooShort";
550
- if (v.tooLong) return "tooLong";
551
- if (v.rangeUnderflow) return "rangeUnderflow";
552
- if (v.rangeOverflow) return "rangeOverflow";
553
- if (v.stepMismatch) return "stepMismatch";
554
- if (v.badInput) return "badInput";
555
- return "invalid";
556
- };
557
- const getStatusIcon = () => {
558
- if (loading2) return /* @__PURE__ */ jsx5(Loader2, { className: cn("animate-spin text-muted-foreground", sizeStyles8[size].icon) });
559
- if (success) return /* @__PURE__ */ jsx5(CheckCircle, { className: cn("text-success", sizeStyles8[size].icon) });
560
- if (errMsg) return /* @__PURE__ */ jsx5(AlertCircle, { className: cn("text-destructive", sizeStyles8[size].icon) });
561
- return null;
562
- };
563
- const showPasswordToggle = type === "password";
564
- const actualType = showPasswordToggle && showPassword ? "text" : type;
565
- const { onFocus: onFocusProp, onBlur: onBlurProp, disabled } = rest;
566
- const {
567
- label: _label,
568
- error: _error,
569
- description: _description,
570
- variant: _variant,
571
- size: _size,
572
- leftIcon: _leftIcon,
573
- rightIcon: _rightIcon,
574
- clearable: _clearable,
575
- loading: _loading,
576
- success: _success,
577
- onClear: _onClear,
578
- hint: _hint,
579
- counter: _counter,
580
- ...restInput
581
- } = rest;
582
- const handleFocus = (e) => {
583
- setIsFocused(true);
584
- onFocusProp?.(e);
585
- };
586
- const handleBlur = (e) => {
587
- setIsFocused(false);
588
- onBlurProp?.(e);
589
- };
590
- const radiusClass = size === "sm" ? "rounded-md" : "rounded-lg";
591
- return /* @__PURE__ */ jsxs5("div", { className: cn("w-full group", containerSpacing), children: [
592
- label && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
593
- /* @__PURE__ */ jsxs5(
594
- "label",
595
- {
596
- className: cn(
597
- // Label size follows input size
598
- size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
599
- "font-medium transition-colors duration-200",
600
- // default color and highlight while any descendant focused
601
- disabled ? "text-muted-foreground" : cn(
602
- "text-foreground group-focus-within:text-primary",
603
- success && "text-primary"
472
+ var Input = forwardRef3(
473
+ ({
474
+ label,
475
+ error,
476
+ description,
477
+ className,
478
+ required,
479
+ variant = "default",
480
+ size = "md",
481
+ leftIcon: LeftIcon,
482
+ rightIcon: RightIcon,
483
+ clearable = false,
484
+ loading: loading2 = false,
485
+ success = false,
486
+ onClear,
487
+ hint,
488
+ counter = false,
489
+ type = "text",
490
+ value,
491
+ maxLength,
492
+ ...rest
493
+ }, ref) => {
494
+ const [localError, setLocalError] = useState4(error);
495
+ const [showPassword, setShowPassword] = useState4(false);
496
+ const [isFocused, setIsFocused] = useState4(false);
497
+ const tv = useTranslations("ValidationInput");
498
+ const autoId = useId();
499
+ const needsId = !!(label || description || hint || error);
500
+ const resolvedId = rest.id || (needsId ? `input-${autoId}` : void 0);
501
+ const errMsg = error || localError;
502
+ const hasValue = value !== void 0 && value !== null && value !== "";
503
+ const charCount = typeof value === "string" ? value.length : 0;
504
+ const errorId = errMsg && resolvedId ? `${resolvedId}-error` : void 0;
505
+ const descId = !errMsg && (description || hint) && resolvedId ? `${resolvedId}-desc` : void 0;
506
+ const containerSpacing = size === "sm" ? "space-y-1.5" : "space-y-2";
507
+ const variantStyles6 = {
508
+ default: {
509
+ container: "bg-background border border-input hover:border-accent-foreground/20",
510
+ 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",
511
+ 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"
512
+ },
513
+ filled: {
514
+ container: "bg-muted/50 border border-transparent hover:bg-muted/70",
515
+ 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",
516
+ 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"
517
+ },
518
+ outlined: {
519
+ container: "bg-transparent border border-border hover:border-accent-foreground/30",
520
+ 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",
521
+ 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"
522
+ },
523
+ minimal: {
524
+ container: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30",
525
+ focus: "focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none",
526
+ error: "border-destructive focus-visible:outline-none focus-visible:border-destructive"
527
+ }
528
+ };
529
+ const sizeStyles8 = {
530
+ sm: {
531
+ input: "px-3 py-1.5 text-sm h-8 md:h-7 md:py-1 md:text-xs",
532
+ icon: "w-4 h-4",
533
+ button: "h-7 w-7"
534
+ },
535
+ md: {
536
+ input: "px-4 py-2 text-sm h-10",
537
+ icon: "w-5 h-5",
538
+ button: "h-8 w-8"
539
+ },
540
+ lg: {
541
+ input: "px-5 py-4 text-base h-12",
542
+ icon: "w-6 h-6",
543
+ button: "h-10 w-10"
544
+ }
545
+ };
546
+ const getErrorKey = (v) => {
547
+ if (v.valueMissing) return "required";
548
+ if (v.typeMismatch) return "typeMismatch";
549
+ if (v.patternMismatch) return "pattern";
550
+ if (v.tooShort) return "tooShort";
551
+ if (v.tooLong) return "tooLong";
552
+ if (v.rangeUnderflow) return "rangeUnderflow";
553
+ if (v.rangeOverflow) return "rangeOverflow";
554
+ if (v.stepMismatch) return "stepMismatch";
555
+ if (v.badInput) return "badInput";
556
+ return "invalid";
557
+ };
558
+ const getStatusIcon = () => {
559
+ if (loading2) return /* @__PURE__ */ jsx5(Loader2, { className: cn("animate-spin text-muted-foreground", sizeStyles8[size].icon) });
560
+ if (success) return /* @__PURE__ */ jsx5(CheckCircle, { className: cn("text-success", sizeStyles8[size].icon) });
561
+ if (errMsg) return /* @__PURE__ */ jsx5(AlertCircle, { className: cn("text-destructive", sizeStyles8[size].icon) });
562
+ return null;
563
+ };
564
+ const showPasswordToggle = type === "password";
565
+ const actualType = showPasswordToggle && showPassword ? "text" : type;
566
+ const { onFocus: onFocusProp, onBlur: onBlurProp, disabled } = rest;
567
+ const {
568
+ label: _label,
569
+ error: _error,
570
+ description: _description,
571
+ variant: _variant,
572
+ size: _size,
573
+ leftIcon: _leftIcon,
574
+ rightIcon: _rightIcon,
575
+ clearable: _clearable,
576
+ loading: _loading,
577
+ success: _success,
578
+ onClear: _onClear,
579
+ hint: _hint,
580
+ counter: _counter,
581
+ ...restInput
582
+ } = rest;
583
+ const handleFocus = (e) => {
584
+ setIsFocused(true);
585
+ onFocusProp?.(e);
586
+ };
587
+ const handleBlur = (e) => {
588
+ setIsFocused(false);
589
+ onBlurProp?.(e);
590
+ };
591
+ const radiusClass = size === "sm" ? "rounded-md" : "rounded-lg";
592
+ return /* @__PURE__ */ jsxs5("div", { className: cn("w-full group", containerSpacing), children: [
593
+ label && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
594
+ /* @__PURE__ */ jsxs5(
595
+ "label",
596
+ {
597
+ className: cn(
598
+ // Label size follows input size
599
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
600
+ "font-medium transition-colors duration-200",
601
+ // default color and highlight while any descendant focused
602
+ disabled ? "text-muted-foreground" : cn("text-foreground group-focus-within:text-primary", success && "text-primary"),
603
+ errMsg && "text-destructive"
604
604
  ),
605
- errMsg && "text-destructive"
606
- ),
607
- children: [
608
- label,
609
- required && /* @__PURE__ */ jsx5("span", { className: "text-destructive ml-1", children: "*" })
610
- ]
611
- }
612
- ),
613
- counter && maxLength && /* @__PURE__ */ jsxs5("span", { className: cn(
614
- "text-xs transition-colors duration-200",
615
- charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
616
- charCount >= maxLength && "text-destructive"
617
- ), children: [
618
- charCount,
619
- "/",
620
- maxLength
621
- ] })
622
- ] }),
623
- /* @__PURE__ */ jsxs5("div", { className: "relative group", children: [
624
- LeftIcon && /* @__PURE__ */ jsx5("div", { className: cn(
625
- "absolute left-3 top-1/2 -translate-y-1/2 z-10",
626
- "text-muted-foreground transition-colors duration-200",
627
- // highlight icon when input (or controls) focused
628
- "group-focus-within:text-primary"
629
- ), children: /* @__PURE__ */ jsx5(LeftIcon, { className: sizeStyles8[size].icon }) }),
630
- /* @__PURE__ */ jsx5(
631
- "input",
632
- {
633
- ref,
634
- type: actualType,
635
- ...value !== void 0 ? { value } : {},
636
- maxLength,
637
- required,
638
- id: resolvedId,
639
- autoComplete: type === "password" ? "current-password" : type === "email" ? "email" : rest.autoComplete,
640
- onFocus: handleFocus,
641
- onBlur: handleBlur,
642
- onInvalid: (e) => {
643
- e.preventDefault();
644
- const key = getErrorKey(e.currentTarget.validity);
645
- setLocalError(tv(key));
646
- },
647
- onInput: () => setLocalError(void 0),
648
- "aria-invalid": !!errMsg,
649
- "aria-describedby": [errorId, descId].filter(Boolean).join(" ") || void 0,
650
- className: cn(
651
- "w-full text-foreground transition-all duration-200 ease-out",
652
- "placeholder:text-muted-foreground focus:outline-none focus-visible:outline-none",
653
- "disabled:cursor-not-allowed disabled:opacity-50",
654
- // Size styles
655
- sizeStyles8[size].input,
656
- radiusClass,
657
- // Icon padding adjustments
658
- LeftIcon && "pl-10",
659
- (RightIcon || showPasswordToggle || clearable || loading2 || success || errMsg) && "pr-10",
660
- // Variant styles
661
- variantStyles6[variant].container,
662
- errMsg ? variantStyles6[variant].error : variantStyles6[variant].focus,
663
- // Reduce visual weight for sm: no default drop shadows
664
- size !== "sm" && variant !== "minimal" && "shadow-sm",
665
- size !== "sm" && isFocused && "shadow-md",
666
- className
667
- ),
668
- disabled,
669
- ...restInput
670
- }
671
- ),
672
- /* @__PURE__ */ jsxs5("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
673
- getStatusIcon(),
674
- RightIcon && !loading2 && !success && !errMsg && /* @__PURE__ */ jsx5(RightIcon, { className: cn("text-muted-foreground", sizeStyles8[size].icon) }),
675
- clearable && hasValue && !loading2 && /* @__PURE__ */ jsx5(
676
- "button",
605
+ children: [
606
+ label,
607
+ required && /* @__PURE__ */ jsx5("span", { className: "text-destructive ml-1", children: "*" })
608
+ ]
609
+ }
610
+ ),
611
+ counter && maxLength && /* @__PURE__ */ jsxs5(
612
+ "span",
613
+ {
614
+ className: cn(
615
+ "text-xs transition-colors duration-200",
616
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
617
+ charCount >= maxLength && "text-destructive"
618
+ ),
619
+ children: [
620
+ charCount,
621
+ "/",
622
+ maxLength
623
+ ]
624
+ }
625
+ )
626
+ ] }),
627
+ /* @__PURE__ */ jsxs5("div", { className: "relative group", children: [
628
+ LeftIcon && /* @__PURE__ */ jsx5(
629
+ "div",
677
630
  {
678
- type: "button",
679
- onClick: () => {
680
- if (onClear) return onClear();
681
- rest.onChange?.({ target: { value: "" } });
682
- },
683
631
  className: cn(
684
- "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-sm",
685
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
686
- "active:bg-accent active:text-accent-foreground",
687
- sizeStyles8[size].button
632
+ "absolute left-3 top-1/2 -translate-y-1/2 z-10",
633
+ "text-muted-foreground transition-colors duration-200",
634
+ // highlight icon when input (or controls) focused
635
+ "group-focus-within:text-primary"
688
636
  ),
689
- tabIndex: 0,
690
- "aria-label": "Clear input",
691
- children: /* @__PURE__ */ jsx5(X, { className: sizeStyles8[size].icon })
637
+ children: /* @__PURE__ */ jsx5(LeftIcon, { className: sizeStyles8[size].icon })
692
638
  }
693
639
  ),
694
- showPasswordToggle && /* @__PURE__ */ jsx5(
695
- "button",
640
+ /* @__PURE__ */ jsx5(
641
+ "input",
696
642
  {
697
- type: "button",
698
- onClick: () => setShowPassword(!showPassword),
643
+ ref,
644
+ type: actualType,
645
+ ...value !== void 0 ? { value } : {},
646
+ maxLength,
647
+ required,
648
+ id: resolvedId,
649
+ autoComplete: type === "password" ? "current-password" : type === "email" ? "email" : rest.autoComplete,
650
+ onFocus: handleFocus,
651
+ onBlur: handleBlur,
652
+ onInvalid: (e) => {
653
+ e.preventDefault();
654
+ const key = getErrorKey(e.currentTarget.validity);
655
+ setLocalError(tv(key));
656
+ },
657
+ onInput: () => setLocalError(void 0),
658
+ "aria-invalid": !!errMsg,
659
+ "aria-describedby": [errorId, descId].filter(Boolean).join(" ") || void 0,
699
660
  className: cn(
700
- "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-full",
701
- "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring/40",
702
- "active:bg-accent/50 active:text-accent-foreground",
703
- sizeStyles8[size].button
661
+ "w-full text-foreground transition-all duration-200 ease-out",
662
+ "placeholder:text-muted-foreground focus:outline-none focus-visible:outline-none",
663
+ "disabled:cursor-not-allowed disabled:opacity-50",
664
+ // Size styles
665
+ sizeStyles8[size].input,
666
+ radiusClass,
667
+ // Icon padding adjustments
668
+ LeftIcon && "pl-10",
669
+ (RightIcon || showPasswordToggle || clearable || loading2 || success || errMsg) && "pr-10",
670
+ // Variant styles
671
+ variantStyles6[variant].container,
672
+ errMsg ? variantStyles6[variant].error : variantStyles6[variant].focus,
673
+ // Reduce visual weight for sm: no default drop shadows
674
+ size !== "sm" && variant !== "minimal" && "shadow-sm",
675
+ size !== "sm" && isFocused && "shadow-md",
676
+ className
704
677
  ),
705
- tabIndex: 0,
706
- "aria-label": showPassword ? "Hide password" : "Show password",
707
- children: showPassword ? /* @__PURE__ */ jsx5(EyeOff, { className: sizeStyles8[size].icon }) : /* @__PURE__ */ jsx5(Eye, { className: sizeStyles8[size].icon })
678
+ disabled,
679
+ ...restInput
680
+ }
681
+ ),
682
+ /* @__PURE__ */ jsxs5("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
683
+ getStatusIcon(),
684
+ RightIcon && !loading2 && !success && !errMsg && /* @__PURE__ */ jsx5(RightIcon, { className: cn("text-muted-foreground", sizeStyles8[size].icon) }),
685
+ clearable && hasValue && !loading2 && /* @__PURE__ */ jsx5(
686
+ "button",
687
+ {
688
+ type: "button",
689
+ onClick: () => {
690
+ if (onClear) return onClear();
691
+ rest.onChange?.({ target: { value: "" } });
692
+ },
693
+ className: cn(
694
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-sm",
695
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
696
+ "active:bg-accent active:text-accent-foreground",
697
+ sizeStyles8[size].button
698
+ ),
699
+ tabIndex: 0,
700
+ "aria-label": "Clear input",
701
+ children: /* @__PURE__ */ jsx5(X, { className: sizeStyles8[size].icon })
702
+ }
703
+ ),
704
+ showPasswordToggle && /* @__PURE__ */ jsx5(
705
+ "button",
706
+ {
707
+ type: "button",
708
+ onClick: () => setShowPassword(!showPassword),
709
+ className: cn(
710
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-full",
711
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring/40",
712
+ "active:bg-accent/50 active:text-accent-foreground",
713
+ sizeStyles8[size].button
714
+ ),
715
+ tabIndex: 0,
716
+ "aria-label": showPassword ? "Hide password" : "Show password",
717
+ children: showPassword ? /* @__PURE__ */ jsx5(EyeOff, { className: sizeStyles8[size].icon }) : /* @__PURE__ */ jsx5(Eye, { className: sizeStyles8[size].icon })
718
+ }
719
+ )
720
+ ] }),
721
+ variant === "minimal" && /* @__PURE__ */ jsx5(
722
+ "div",
723
+ {
724
+ className: cn(
725
+ "absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-primary/60 transition-all duration-300",
726
+ // default hidden
727
+ "w-0 opacity-0",
728
+ // expand underline when focused within input container
729
+ "group-focus-within:w-full group-focus-within:opacity-100"
730
+ )
708
731
  }
709
732
  )
710
733
  ] }),
711
- variant === "minimal" && /* @__PURE__ */ jsx5(
712
- "div",
734
+ errMsg && /* @__PURE__ */ jsxs5("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
735
+ /* @__PURE__ */ jsx5(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
736
+ /* @__PURE__ */ jsx5("span", { children: errMsg })
737
+ ] }),
738
+ (description || hint) && !errMsg && /* @__PURE__ */ jsx5(
739
+ "p",
713
740
  {
741
+ id: descId,
714
742
  className: cn(
715
- "absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-primary/60 transition-all duration-300",
716
- // default hidden
717
- "w-0 opacity-0",
718
- // expand underline when focused within input container
719
- "group-focus-within:w-full group-focus-within:opacity-100"
720
- )
743
+ "text-xs transition-colors duration-200",
744
+ // follow focus state of the whole field area
745
+ "text-muted-foreground group-focus-within:text-primary/70"
746
+ ),
747
+ children: hint || description
721
748
  }
722
749
  )
723
- ] }),
724
- errMsg && /* @__PURE__ */ jsxs5("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
725
- /* @__PURE__ */ jsx5(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
726
- /* @__PURE__ */ jsx5("span", { children: errMsg })
727
- ] }),
728
- (description || hint) && !errMsg && /* @__PURE__ */ jsx5("p", { id: descId, className: cn(
729
- "text-xs transition-colors duration-200",
730
- // follow focus state of the whole field area
731
- "text-muted-foreground group-focus-within:text-primary/70"
732
- ), children: hint || description })
733
- ] });
734
- });
750
+ ] });
751
+ }
752
+ );
735
753
  Input.displayName = "Input";
736
- var SearchInput = forwardRef3(({
737
- onSearch,
738
- searchDelay = 300,
739
- placeholder = "Search...",
740
- ...props
741
- }, ref) => {
742
- const [searchValue, setSearchValue] = useState4(props.value || "");
743
- React4.useEffect(() => {
744
- if (!onSearch) return;
745
- const timer = setTimeout(() => {
746
- onSearch(searchValue);
747
- }, searchDelay);
748
- return () => clearTimeout(timer);
749
- }, [searchValue, onSearch, searchDelay]);
750
- return /* @__PURE__ */ jsx5(
751
- Input,
752
- {
753
- ref,
754
- type: "search",
755
- leftIcon: Search,
756
- placeholder,
757
- clearable: true,
758
- value: searchValue,
759
- onChange: (e) => setSearchValue(e.target.value),
760
- onClear: () => setSearchValue(""),
761
- ...props
762
- }
763
- );
764
- });
765
- SearchInput.displayName = "SearchInput";
766
- var PasswordInput = forwardRef3(({
767
- showStrength = false,
768
- strengthLabels = ["Weak", "Fair", "Good", "Strong"],
769
- ...props
770
- }, ref) => {
771
- const getPasswordStrength = (password) => {
772
- let score = 0;
773
- if (password.length >= 8) score++;
774
- if (/[a-z]/.test(password)) score++;
775
- if (/[A-Z]/.test(password)) score++;
776
- if (/[0-9]/.test(password)) score++;
777
- if (/[^A-Za-z0-9]/.test(password)) score++;
778
- return Math.min(score, 4);
779
- };
780
- const strength = showStrength && typeof props.value === "string" ? getPasswordStrength(props.value) : 0;
781
- const strengthColors = ["bg-destructive", "bg-warning", "bg-warning", "bg-success"];
782
- const strengthLabel = strengthLabels[strength - 1];
783
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
784
- /* @__PURE__ */ jsx5(Input, { ref, type: "password", ...props }),
785
- showStrength && props.value && /* @__PURE__ */ jsxs5("div", { className: "space-y-1", children: [
786
- /* @__PURE__ */ jsx5("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx5(
787
- "div",
788
- {
789
- className: cn(
790
- "h-1 flex-1 rounded-full transition-colors duration-300",
791
- level <= strength ? strengthColors[strength - 1] : "bg-muted"
792
- )
793
- },
794
- level
795
- )) }),
796
- strengthLabel && /* @__PURE__ */ jsx5("p", { className: cn(
797
- "text-xs font-medium",
798
- strength <= 1 ? "text-destructive" : strength <= 2 ? "text-warning" : strength <= 3 ? "text-warning" : "text-success"
799
- ), children: strengthLabel })
800
- ] })
801
- ] });
802
- });
803
- PasswordInput.displayName = "PasswordInput";
804
- var NumberInput = forwardRef3(({
805
- min,
806
- max,
807
- step = 1,
808
- showSteppers = true,
809
- onIncrement,
810
- onDecrement,
811
- formatThousands = false,
812
- locale = "vi-VN",
813
- value,
814
- onChange,
815
- ...props
816
- }, ref) => {
817
- const toNumber = (v) => {
818
- if (v === "" || v === void 0 || v === null) return 0;
819
- const n = Number(v);
820
- return Number.isFinite(n) ? n : 0;
821
- };
822
- const format = React4.useCallback(
823
- (n) => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(n),
824
- [locale]
825
- );
826
- const parse = (s) => {
827
- const digits = (s || "").replace(/\D+/g, "");
828
- return digits ? Number(digits) : NaN;
829
- };
830
- const [displayValue, setDisplayValue] = React4.useState(
831
- formatThousands ? value !== void 0 && value !== null && value !== "" ? format(toNumber(value)) : "" : String(value ?? "")
832
- );
833
- const currentValue = toNumber(value);
834
- React4.useEffect(() => {
835
- if (formatThousands) {
836
- const next = value === "" || value === void 0 || value === null ? "" : format(toNumber(value));
837
- setDisplayValue((prev) => prev === next ? prev : next);
838
- } else {
839
- const next = String(value ?? "");
840
- setDisplayValue((prev) => prev === next ? prev : next);
841
- }
842
- }, [value, formatThousands, locale, format]);
843
- const handleIncrement = () => {
844
- if (onIncrement) {
845
- onIncrement();
846
- } else if (onChange) {
847
- const newValue = Math.min(currentValue + step, max ?? Infinity);
848
- if (formatThousands) setDisplayValue(format(newValue));
849
- onChange({ target: { value: newValue.toString() } });
850
- }
851
- };
852
- const handleDecrement = () => {
853
- if (onDecrement) {
854
- onDecrement();
855
- } else if (onChange) {
856
- const newValue = Math.max(currentValue - step, min ?? -Infinity);
857
- if (formatThousands) setDisplayValue(format(newValue));
858
- onChange({ target: { value: newValue.toString() } });
859
- }
860
- };
861
- return /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
862
- /* @__PURE__ */ jsx5(
754
+ var SearchInput = forwardRef3(
755
+ ({ onSearch, searchDelay = 300, placeholder = "Search...", ...props }, ref) => {
756
+ const [searchValue, setSearchValue] = useState4(props.value || "");
757
+ React4.useEffect(() => {
758
+ if (!onSearch) return;
759
+ const timer = setTimeout(() => {
760
+ onSearch(searchValue);
761
+ }, searchDelay);
762
+ return () => clearTimeout(timer);
763
+ }, [searchValue, onSearch, searchDelay]);
764
+ return /* @__PURE__ */ jsx5(
863
765
  Input,
864
766
  {
865
767
  ref,
866
- type: formatThousands ? "text" : "number",
867
- min,
868
- max,
869
- step,
870
- rightIcon: showSteppers ? void 0 : props.rightIcon,
871
- value: displayValue,
872
- onChange: (e) => {
873
- if (!onChange) return;
874
- if (!formatThousands) return onChange(e);
875
- const raw = e.target.value;
876
- const parsed = parse(raw);
877
- if (Number.isNaN(parsed)) {
878
- setDisplayValue("");
879
- onChange({ target: { value: "" } });
880
- } else {
881
- const bounded = Math.min(Math.max(parsed, min ?? -Infinity), max ?? Infinity);
882
- setDisplayValue(format(bounded));
883
- onChange({ target: { value: bounded.toString() } });
768
+ type: "search",
769
+ leftIcon: Search,
770
+ placeholder,
771
+ clearable: true,
772
+ value: searchValue,
773
+ onChange: (e) => setSearchValue(e.target.value),
774
+ onClear: () => setSearchValue(""),
775
+ ...props
776
+ }
777
+ );
778
+ }
779
+ );
780
+ SearchInput.displayName = "SearchInput";
781
+ var PasswordInput = forwardRef3(
782
+ ({ showStrength = false, strengthLabels = ["Weak", "Fair", "Good", "Strong"], ...props }, ref) => {
783
+ const getPasswordStrength = (password) => {
784
+ let score = 0;
785
+ if (password.length >= 8) score++;
786
+ if (/[a-z]/.test(password)) score++;
787
+ if (/[A-Z]/.test(password)) score++;
788
+ if (/[0-9]/.test(password)) score++;
789
+ if (/[^A-Za-z0-9]/.test(password)) score++;
790
+ return Math.min(score, 4);
791
+ };
792
+ const strength = showStrength && typeof props.value === "string" ? getPasswordStrength(props.value) : 0;
793
+ const strengthColors = ["bg-destructive", "bg-warning", "bg-warning", "bg-success"];
794
+ const strengthLabel = strengthLabels[strength - 1];
795
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
796
+ /* @__PURE__ */ jsx5(Input, { ref, type: "password", ...props }),
797
+ showStrength && props.value && /* @__PURE__ */ jsxs5("div", { className: "space-y-1", children: [
798
+ /* @__PURE__ */ jsx5("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx5(
799
+ "div",
800
+ {
801
+ className: cn(
802
+ "h-1 flex-1 rounded-full transition-colors duration-300",
803
+ level <= strength ? strengthColors[strength - 1] : "bg-muted"
804
+ )
805
+ },
806
+ level
807
+ )) }),
808
+ strengthLabel && /* @__PURE__ */ jsx5(
809
+ "p",
810
+ {
811
+ className: cn(
812
+ "text-xs font-medium",
813
+ strength <= 1 ? "text-destructive" : strength <= 2 ? "text-warning" : strength <= 3 ? "text-warning" : "text-success"
814
+ ),
815
+ children: strengthLabel
884
816
  }
885
- },
886
- ...props,
887
- className: cn(
888
- showSteppers && [
889
- "pr-12",
890
- // Hide native browser steppers
891
- "[&::-webkit-outer-spin-button]:appearance-none",
892
- "[&::-webkit-inner-spin-button]:appearance-none",
893
- "[&::-webkit-inner-spin-button]:m-0",
894
- "appearance-none"
895
- ],
896
- props.className
897
817
  )
818
+ ] })
819
+ ] });
820
+ }
821
+ );
822
+ PasswordInput.displayName = "PasswordInput";
823
+ var NumberInput = forwardRef3(
824
+ ({ min, max, step = 1, showSteppers = true, onIncrement, onDecrement, formatThousands = false, locale = "vi-VN", value, onChange, ...props }, ref) => {
825
+ const toNumber = (v) => {
826
+ if (v === "" || v === void 0 || v === null) return 0;
827
+ const n = Number(v);
828
+ return Number.isFinite(n) ? n : 0;
829
+ };
830
+ const format = React4.useCallback((n) => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(n), [locale]);
831
+ const parse = (s) => {
832
+ const digits = (s || "").replace(/\D+/g, "");
833
+ return digits ? Number(digits) : NaN;
834
+ };
835
+ const [displayValue, setDisplayValue] = React4.useState(
836
+ formatThousands ? value !== void 0 && value !== null && value !== "" ? format(toNumber(value)) : "" : String(value ?? "")
837
+ );
838
+ const currentValue = toNumber(value);
839
+ React4.useEffect(() => {
840
+ if (formatThousands) {
841
+ const next = value === "" || value === void 0 || value === null ? "" : format(toNumber(value));
842
+ setDisplayValue((prev) => prev === next ? prev : next);
843
+ } else {
844
+ const next = String(value ?? "");
845
+ setDisplayValue((prev) => prev === next ? prev : next);
846
+ }
847
+ }, [value, formatThousands, locale, format]);
848
+ const handleIncrement = () => {
849
+ if (onIncrement) {
850
+ onIncrement();
851
+ } else if (onChange) {
852
+ const newValue = Math.min(currentValue + step, max ?? Infinity);
853
+ if (formatThousands) setDisplayValue(format(newValue));
854
+ onChange({ target: { value: newValue.toString() } });
898
855
  }
899
- ),
900
- showSteppers && /* @__PURE__ */ jsxs5("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5", children: [
856
+ };
857
+ const handleDecrement = () => {
858
+ if (onDecrement) {
859
+ onDecrement();
860
+ } else if (onChange) {
861
+ const newValue = Math.max(currentValue - step, min ?? -Infinity);
862
+ if (formatThousands) setDisplayValue(format(newValue));
863
+ onChange({ target: { value: newValue.toString() } });
864
+ }
865
+ };
866
+ return /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
901
867
  /* @__PURE__ */ jsx5(
902
- "button",
868
+ Input,
903
869
  {
904
- type: "button",
905
- onClick: handleIncrement,
906
- disabled: max !== void 0 && currentValue >= max,
907
- className: cn(
908
- "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
909
- "hover:bg-accent focus:outline-none focus:bg-accent",
910
- "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
911
- "text-muted-foreground hover:text-foreground"
912
- ),
913
- "aria-label": "Increase value",
914
- children: /* @__PURE__ */ jsx5(
915
- "svg",
916
- {
917
- width: "8",
918
- height: "8",
919
- viewBox: "0 0 8 8",
920
- fill: "none",
921
- className: "flex-shrink-0",
922
- children: /* @__PURE__ */ jsx5(
923
- "path",
924
- {
925
- d: "M4 2L6 6H2L4 2Z",
926
- fill: "currentColor"
927
- }
928
- )
870
+ ref,
871
+ type: formatThousands ? "text" : "number",
872
+ min,
873
+ max,
874
+ step,
875
+ rightIcon: showSteppers ? void 0 : props.rightIcon,
876
+ value: displayValue,
877
+ onChange: (e) => {
878
+ if (!onChange) return;
879
+ if (!formatThousands) return onChange(e);
880
+ const raw = e.target.value;
881
+ const parsed = parse(raw);
882
+ if (Number.isNaN(parsed)) {
883
+ setDisplayValue("");
884
+ onChange({ target: { value: "" } });
885
+ } else {
886
+ const bounded = Math.min(Math.max(parsed, min ?? -Infinity), max ?? Infinity);
887
+ setDisplayValue(format(bounded));
888
+ onChange({ target: { value: bounded.toString() } });
929
889
  }
890
+ },
891
+ ...props,
892
+ className: cn(
893
+ showSteppers && [
894
+ "pr-12",
895
+ // Hide native browser steppers
896
+ "[&::-webkit-outer-spin-button]:appearance-none",
897
+ "[&::-webkit-inner-spin-button]:appearance-none",
898
+ "[&::-webkit-inner-spin-button]:m-0",
899
+ "appearance-none"
900
+ ],
901
+ props.className
930
902
  )
931
903
  }
932
904
  ),
905
+ showSteppers && /* @__PURE__ */ jsxs5("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5", children: [
906
+ /* @__PURE__ */ jsx5(
907
+ "button",
908
+ {
909
+ type: "button",
910
+ onClick: handleIncrement,
911
+ disabled: max !== void 0 && currentValue >= max,
912
+ className: cn(
913
+ "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
914
+ "hover:bg-accent focus:outline-none focus:bg-accent",
915
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
916
+ "text-muted-foreground hover:text-foreground"
917
+ ),
918
+ "aria-label": "Increase value",
919
+ children: /* @__PURE__ */ jsx5("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "flex-shrink-0", children: /* @__PURE__ */ jsx5("path", { d: "M4 2L6 6H2L4 2Z", fill: "currentColor" }) })
920
+ }
921
+ ),
922
+ /* @__PURE__ */ jsx5(
923
+ "button",
924
+ {
925
+ type: "button",
926
+ onClick: handleDecrement,
927
+ disabled: min !== void 0 && currentValue <= min,
928
+ className: cn(
929
+ "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
930
+ "hover:bg-accent focus:outline-none focus:bg-accent",
931
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
932
+ "text-muted-foreground hover:text-foreground"
933
+ ),
934
+ "aria-label": "Decrease value",
935
+ children: /* @__PURE__ */ jsx5("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "flex-shrink-0", children: /* @__PURE__ */ jsx5("path", { d: "M4 6L2 2H6L4 6Z", fill: "currentColor" }) })
936
+ }
937
+ )
938
+ ] })
939
+ ] });
940
+ }
941
+ );
942
+ NumberInput.displayName = "NumberInput";
943
+ var Textarea = forwardRef3(
944
+ ({ label, error, description, variant = "default", resize = "vertical", counter = false, className, required, value, maxLength, ...props }, ref) => {
945
+ const [isFocused, setIsFocused] = useState4(false);
946
+ const charCount = typeof value === "string" ? value.length : 0;
947
+ const variantStyles6 = {
948
+ 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",
949
+ 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",
950
+ 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",
951
+ 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"
952
+ };
953
+ const resizeClasses = {
954
+ none: "resize-none",
955
+ vertical: "resize-y",
956
+ horizontal: "resize-x",
957
+ both: "resize"
958
+ };
959
+ return /* @__PURE__ */ jsxs5("div", { className: "w-full space-y-2", children: [
960
+ label && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
961
+ /* @__PURE__ */ jsxs5(
962
+ "label",
963
+ {
964
+ className: cn(
965
+ "text-sm font-medium transition-colors duration-200",
966
+ isFocused ? "text-primary" : "text-foreground",
967
+ error && "text-destructive"
968
+ ),
969
+ children: [
970
+ label,
971
+ required && /* @__PURE__ */ jsx5("span", { className: "text-destructive ml-1", children: "*" })
972
+ ]
973
+ }
974
+ ),
975
+ counter && maxLength && /* @__PURE__ */ jsxs5(
976
+ "span",
977
+ {
978
+ className: cn(
979
+ "text-xs transition-colors duration-200",
980
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
981
+ charCount >= maxLength && "text-destructive"
982
+ ),
983
+ children: [
984
+ charCount,
985
+ "/",
986
+ maxLength
987
+ ]
988
+ }
989
+ )
990
+ ] }),
933
991
  /* @__PURE__ */ jsx5(
934
- "button",
992
+ "textarea",
935
993
  {
936
- type: "button",
937
- onClick: handleDecrement,
938
- disabled: min !== void 0 && currentValue <= min,
994
+ ref,
995
+ value,
996
+ maxLength,
997
+ required,
998
+ onFocus: () => setIsFocused(true),
999
+ onBlur: () => setIsFocused(false),
939
1000
  className: cn(
940
- "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
941
- "hover:bg-accent focus:outline-none focus:bg-accent",
942
- "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
943
- "text-muted-foreground hover:text-foreground"
1001
+ "w-full rounded-lg px-4 py-3 text-sm text-foreground transition-all duration-200",
1002
+ "placeholder:text-muted-foreground focus:outline-none min-h-[80px]",
1003
+ "disabled:cursor-not-allowed disabled:opacity-50",
1004
+ variantStyles6[variant],
1005
+ // DÒNG NÀY ĐÃ ĐƯỢC CẬP NHẬT:
1006
+ 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",
1007
+ resizeClasses[resize],
1008
+ isFocused && "shadow-md",
1009
+ variant !== "minimal" && "shadow-sm",
1010
+ className
944
1011
  ),
945
- "aria-label": "Decrease value",
946
- children: /* @__PURE__ */ jsx5(
947
- "svg",
948
- {
949
- width: "8",
950
- height: "8",
951
- viewBox: "0 0 8 8",
952
- fill: "none",
953
- className: "flex-shrink-0",
954
- children: /* @__PURE__ */ jsx5(
955
- "path",
956
- {
957
- d: "M4 6L2 2H6L4 6Z",
958
- fill: "currentColor"
959
- }
960
- )
961
- }
962
- )
1012
+ ...props
963
1013
  }
964
- )
965
- ] })
966
- ] });
967
- });
968
- NumberInput.displayName = "NumberInput";
969
- var Textarea = forwardRef3(({
970
- label,
971
- error,
972
- description,
973
- variant = "default",
974
- resize = "vertical",
975
- counter = false,
976
- className,
977
- required,
978
- value,
979
- maxLength,
980
- ...props
981
- }, ref) => {
982
- const [isFocused, setIsFocused] = useState4(false);
983
- const charCount = typeof value === "string" ? value.length : 0;
984
- const variantStyles6 = {
985
- default: "bg-background border border-input hover:border-accent-foreground/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
986
- filled: "bg-muted/50 border border-transparent hover:bg-muted/70 focus-visible:outline-none focus-visible:bg-background focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
987
- outlined: "bg-transparent border border-border hover:border-accent-foreground/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
988
- 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"
989
- };
990
- const resizeClasses = {
991
- none: "resize-none",
992
- vertical: "resize-y",
993
- horizontal: "resize-x",
994
- both: "resize"
995
- };
996
- return /* @__PURE__ */ jsxs5("div", { className: "w-full space-y-2", children: [
997
- label && /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
998
- /* @__PURE__ */ jsxs5("label", { className: cn(
999
- "text-sm font-medium transition-colors duration-200",
1000
- isFocused ? "text-primary" : "text-foreground",
1001
- error && "text-destructive"
1002
- ), children: [
1003
- label,
1004
- required && /* @__PURE__ */ jsx5("span", { className: "text-destructive ml-1", children: "*" })
1014
+ ),
1015
+ error && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1016
+ /* @__PURE__ */ jsx5(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
1017
+ /* @__PURE__ */ jsx5("span", { children: error })
1005
1018
  ] }),
1006
- counter && maxLength && /* @__PURE__ */ jsxs5("span", { className: cn(
1007
- "text-xs transition-colors duration-200",
1008
- charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
1009
- charCount >= maxLength && "text-destructive"
1010
- ), children: [
1011
- charCount,
1012
- "/",
1013
- maxLength
1014
- ] })
1015
- ] }),
1016
- /* @__PURE__ */ jsx5(
1017
- "textarea",
1018
- {
1019
- ref,
1020
- value,
1021
- maxLength,
1022
- required,
1023
- onFocus: () => setIsFocused(true),
1024
- onBlur: () => setIsFocused(false),
1025
- className: cn(
1026
- "w-full rounded-lg px-4 py-3 text-sm text-foreground transition-all duration-200",
1027
- "placeholder:text-muted-foreground focus:outline-none min-h-[80px]",
1028
- "disabled:cursor-not-allowed disabled:opacity-50",
1029
- variantStyles6[variant],
1030
- error && "border-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-destructive focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:border-transparent",
1031
- resizeClasses[resize],
1032
- isFocused && "shadow-md",
1033
- variant !== "minimal" && "shadow-sm",
1034
- className
1035
- ),
1036
- ...props
1037
- }
1038
- ),
1039
- error && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1040
- /* @__PURE__ */ jsx5(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
1041
- /* @__PURE__ */ jsx5("span", { children: error })
1042
- ] }),
1043
- description && !error && /* @__PURE__ */ jsx5("p", { className: cn(
1044
- "text-xs transition-colors duration-200",
1045
- isFocused ? "text-primary/70" : "text-muted-foreground"
1046
- ), children: description })
1047
- ] });
1048
- });
1019
+ description && !error && /* @__PURE__ */ jsx5("p", { className: cn("text-xs transition-colors duration-200", isFocused ? "text-primary/70" : "text-muted-foreground"), children: description })
1020
+ ] });
1021
+ }
1022
+ );
1049
1023
  Textarea.displayName = "Textarea";
1050
1024
  var Input_default = Input;
1051
1025
 
@@ -6038,8 +6012,10 @@ function OverlayControls({
6038
6012
  break;
6039
6013
  case "f":
6040
6014
  case "F":
6041
- e.preventDefault();
6042
- onToggleFullscreen?.();
6015
+ if (!e.ctrlKey && !e.metaKey) {
6016
+ e.preventDefault();
6017
+ onToggleFullscreen?.();
6018
+ }
6043
6019
  break;
6044
6020
  case "m":
6045
6021
  case "M":