@underverse-ui/underverse 0.1.31 → 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.cjs CHANGED
@@ -613,583 +613,557 @@ var import_react3 = __toESM(require("react"), 1);
613
613
  var import_next_intl = require("next-intl");
614
614
  var import_lucide_react3 = require("lucide-react");
615
615
  var import_jsx_runtime5 = require("react/jsx-runtime");
616
- var Input = (0, import_react3.forwardRef)(({
617
- label,
618
- error,
619
- description,
620
- className,
621
- required,
622
- variant = "default",
623
- size = "md",
624
- leftIcon: LeftIcon,
625
- rightIcon: RightIcon,
626
- clearable = false,
627
- loading: loading2 = false,
628
- success = false,
629
- onClear,
630
- hint,
631
- counter = false,
632
- type = "text",
633
- value,
634
- maxLength,
635
- ...rest
636
- }, ref) => {
637
- const [localError, setLocalError] = (0, import_react3.useState)(error);
638
- const [showPassword, setShowPassword] = (0, import_react3.useState)(false);
639
- const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
640
- const tv = (0, import_next_intl.useTranslations)("ValidationInput");
641
- const autoId = (0, import_react3.useId)();
642
- const needsId = !!(label || description || hint || error);
643
- const resolvedId = rest.id || (needsId ? `input-${autoId}` : void 0);
644
- const errMsg = error || localError;
645
- const hasValue = value !== void 0 && value !== null && value !== "";
646
- const charCount = typeof value === "string" ? value.length : 0;
647
- const errorId = errMsg && resolvedId ? `${resolvedId}-error` : void 0;
648
- const descId = !errMsg && (description || hint) && resolvedId ? `${resolvedId}-desc` : void 0;
649
- const containerSpacing = size === "sm" ? "space-y-1.5" : "space-y-2";
650
- const variantStyles6 = {
651
- default: {
652
- container: "bg-background border border-input hover:border-accent-foreground/20",
653
- 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",
654
- 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"
655
- },
656
- filled: {
657
- container: "bg-muted/50 border border-transparent hover:bg-muted/70",
658
- 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",
659
- 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"
660
- },
661
- outlined: {
662
- container: "bg-transparent border border-border hover:border-accent-foreground/30",
663
- 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",
664
- 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"
665
- },
666
- minimal: {
667
- container: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30",
668
- focus: "focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none",
669
- error: "border-destructive focus-visible:outline-none focus-visible:border-destructive"
670
- }
671
- };
672
- const sizeStyles8 = {
673
- sm: {
674
- input: "px-3 py-1.5 text-sm h-8 md:h-7 md:py-1 md:text-xs",
675
- icon: "w-4 h-4",
676
- button: "h-7 w-7"
677
- },
678
- md: {
679
- input: "px-4 py-2 text-sm h-10",
680
- icon: "w-5 h-5",
681
- button: "h-8 w-8"
682
- },
683
- lg: {
684
- input: "px-5 py-4 text-base h-12",
685
- icon: "w-6 h-6",
686
- button: "h-10 w-10"
687
- }
688
- };
689
- const getErrorKey = (v) => {
690
- if (v.valueMissing) return "required";
691
- if (v.typeMismatch) return "typeMismatch";
692
- if (v.patternMismatch) return "pattern";
693
- if (v.tooShort) return "tooShort";
694
- if (v.tooLong) return "tooLong";
695
- if (v.rangeUnderflow) return "rangeUnderflow";
696
- if (v.rangeOverflow) return "rangeOverflow";
697
- if (v.stepMismatch) return "stepMismatch";
698
- if (v.badInput) return "badInput";
699
- return "invalid";
700
- };
701
- const getStatusIcon = () => {
702
- if (loading2) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Loader2, { className: cn("animate-spin text-muted-foreground", sizeStyles8[size].icon) });
703
- if (success) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.CheckCircle, { className: cn("text-success", sizeStyles8[size].icon) });
704
- if (errMsg) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: cn("text-destructive", sizeStyles8[size].icon) });
705
- return null;
706
- };
707
- const showPasswordToggle = type === "password";
708
- const actualType = showPasswordToggle && showPassword ? "text" : type;
709
- const { onFocus: onFocusProp, onBlur: onBlurProp, disabled } = rest;
710
- const {
711
- label: _label,
712
- error: _error,
713
- description: _description,
714
- variant: _variant,
715
- size: _size,
716
- leftIcon: _leftIcon,
717
- rightIcon: _rightIcon,
718
- clearable: _clearable,
719
- loading: _loading,
720
- success: _success,
721
- onClear: _onClear,
722
- hint: _hint,
723
- counter: _counter,
724
- ...restInput
725
- } = rest;
726
- const handleFocus = (e) => {
727
- setIsFocused(true);
728
- onFocusProp?.(e);
729
- };
730
- const handleBlur = (e) => {
731
- setIsFocused(false);
732
- onBlurProp?.(e);
733
- };
734
- const radiusClass = size === "sm" ? "rounded-md" : "rounded-lg";
735
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("w-full group", containerSpacing), children: [
736
- label && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
737
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
738
- "label",
739
- {
740
- className: cn(
741
- // Label size follows input size
742
- size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
743
- "font-medium transition-colors duration-200",
744
- // default color and highlight while any descendant focused
745
- disabled ? "text-muted-foreground" : cn(
746
- "text-foreground group-focus-within:text-primary",
747
- success && "text-primary"
616
+ var Input = (0, import_react3.forwardRef)(
617
+ ({
618
+ label,
619
+ error,
620
+ description,
621
+ className,
622
+ required,
623
+ variant = "default",
624
+ size = "md",
625
+ leftIcon: LeftIcon,
626
+ rightIcon: RightIcon,
627
+ clearable = false,
628
+ loading: loading2 = false,
629
+ success = false,
630
+ onClear,
631
+ hint,
632
+ counter = false,
633
+ type = "text",
634
+ value,
635
+ maxLength,
636
+ ...rest
637
+ }, ref) => {
638
+ const [localError, setLocalError] = (0, import_react3.useState)(error);
639
+ const [showPassword, setShowPassword] = (0, import_react3.useState)(false);
640
+ const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
641
+ const tv = (0, import_next_intl.useTranslations)("ValidationInput");
642
+ const autoId = (0, import_react3.useId)();
643
+ const needsId = !!(label || description || hint || error);
644
+ const resolvedId = rest.id || (needsId ? `input-${autoId}` : void 0);
645
+ const errMsg = error || localError;
646
+ const hasValue = value !== void 0 && value !== null && value !== "";
647
+ const charCount = typeof value === "string" ? value.length : 0;
648
+ const errorId = errMsg && resolvedId ? `${resolvedId}-error` : void 0;
649
+ const descId = !errMsg && (description || hint) && resolvedId ? `${resolvedId}-desc` : void 0;
650
+ const containerSpacing = size === "sm" ? "space-y-1.5" : "space-y-2";
651
+ const variantStyles6 = {
652
+ default: {
653
+ container: "bg-background border border-input hover:border-accent-foreground/20",
654
+ 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",
655
+ 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"
656
+ },
657
+ filled: {
658
+ container: "bg-muted/50 border border-transparent hover:bg-muted/70",
659
+ 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",
660
+ 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"
661
+ },
662
+ outlined: {
663
+ container: "bg-transparent border border-border hover:border-accent-foreground/30",
664
+ 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",
665
+ 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"
666
+ },
667
+ minimal: {
668
+ container: "bg-transparent border-0 border-b border-border hover:border-accent-foreground/30",
669
+ focus: "focus-visible:outline-none focus-visible:border-ring focus-visible:ring-0 rounded-none",
670
+ error: "border-destructive focus-visible:outline-none focus-visible:border-destructive"
671
+ }
672
+ };
673
+ const sizeStyles8 = {
674
+ sm: {
675
+ input: "px-3 py-1.5 text-sm h-8 md:h-7 md:py-1 md:text-xs",
676
+ icon: "w-4 h-4",
677
+ button: "h-7 w-7"
678
+ },
679
+ md: {
680
+ input: "px-4 py-2 text-sm h-10",
681
+ icon: "w-5 h-5",
682
+ button: "h-8 w-8"
683
+ },
684
+ lg: {
685
+ input: "px-5 py-4 text-base h-12",
686
+ icon: "w-6 h-6",
687
+ button: "h-10 w-10"
688
+ }
689
+ };
690
+ const getErrorKey = (v) => {
691
+ if (v.valueMissing) return "required";
692
+ if (v.typeMismatch) return "typeMismatch";
693
+ if (v.patternMismatch) return "pattern";
694
+ if (v.tooShort) return "tooShort";
695
+ if (v.tooLong) return "tooLong";
696
+ if (v.rangeUnderflow) return "rangeUnderflow";
697
+ if (v.rangeOverflow) return "rangeOverflow";
698
+ if (v.stepMismatch) return "stepMismatch";
699
+ if (v.badInput) return "badInput";
700
+ return "invalid";
701
+ };
702
+ const getStatusIcon = () => {
703
+ if (loading2) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Loader2, { className: cn("animate-spin text-muted-foreground", sizeStyles8[size].icon) });
704
+ if (success) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.CheckCircle, { className: cn("text-success", sizeStyles8[size].icon) });
705
+ if (errMsg) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: cn("text-destructive", sizeStyles8[size].icon) });
706
+ return null;
707
+ };
708
+ const showPasswordToggle = type === "password";
709
+ const actualType = showPasswordToggle && showPassword ? "text" : type;
710
+ const { onFocus: onFocusProp, onBlur: onBlurProp, disabled } = rest;
711
+ const {
712
+ label: _label,
713
+ error: _error,
714
+ description: _description,
715
+ variant: _variant,
716
+ size: _size,
717
+ leftIcon: _leftIcon,
718
+ rightIcon: _rightIcon,
719
+ clearable: _clearable,
720
+ loading: _loading,
721
+ success: _success,
722
+ onClear: _onClear,
723
+ hint: _hint,
724
+ counter: _counter,
725
+ ...restInput
726
+ } = rest;
727
+ const handleFocus = (e) => {
728
+ setIsFocused(true);
729
+ onFocusProp?.(e);
730
+ };
731
+ const handleBlur = (e) => {
732
+ setIsFocused(false);
733
+ onBlurProp?.(e);
734
+ };
735
+ const radiusClass = size === "sm" ? "rounded-md" : "rounded-lg";
736
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("w-full group", containerSpacing), children: [
737
+ label && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
738
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
739
+ "label",
740
+ {
741
+ className: cn(
742
+ // Label size follows input size
743
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
744
+ "font-medium transition-colors duration-200",
745
+ // default color and highlight while any descendant focused
746
+ disabled ? "text-muted-foreground" : cn("text-foreground group-focus-within:text-primary", success && "text-primary"),
747
+ errMsg && "text-destructive"
748
748
  ),
749
- errMsg && "text-destructive"
750
- ),
751
- children: [
752
- label,
753
- required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-destructive ml-1", children: "*" })
754
- ]
755
- }
756
- ),
757
- counter && maxLength && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: cn(
758
- "text-xs transition-colors duration-200",
759
- charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
760
- charCount >= maxLength && "text-destructive"
761
- ), children: [
762
- charCount,
763
- "/",
764
- maxLength
765
- ] })
766
- ] }),
767
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative group", children: [
768
- LeftIcon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: cn(
769
- "absolute left-3 top-1/2 -translate-y-1/2 z-10",
770
- "text-muted-foreground transition-colors duration-200",
771
- // highlight icon when input (or controls) focused
772
- "group-focus-within:text-primary"
773
- ), children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LeftIcon, { className: sizeStyles8[size].icon }) }),
774
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
775
- "input",
776
- {
777
- ref,
778
- type: actualType,
779
- ...value !== void 0 ? { value } : {},
780
- maxLength,
781
- required,
782
- id: resolvedId,
783
- autoComplete: type === "password" ? "current-password" : type === "email" ? "email" : rest.autoComplete,
784
- onFocus: handleFocus,
785
- onBlur: handleBlur,
786
- onInvalid: (e) => {
787
- e.preventDefault();
788
- const key = getErrorKey(e.currentTarget.validity);
789
- setLocalError(tv(key));
790
- },
791
- onInput: () => setLocalError(void 0),
792
- "aria-invalid": !!errMsg,
793
- "aria-describedby": [errorId, descId].filter(Boolean).join(" ") || void 0,
794
- className: cn(
795
- "w-full text-foreground transition-all duration-200 ease-out",
796
- "placeholder:text-muted-foreground focus:outline-none focus-visible:outline-none",
797
- "disabled:cursor-not-allowed disabled:opacity-50",
798
- // Size styles
799
- sizeStyles8[size].input,
800
- radiusClass,
801
- // Icon padding adjustments
802
- LeftIcon && "pl-10",
803
- (RightIcon || showPasswordToggle || clearable || loading2 || success || errMsg) && "pr-10",
804
- // Variant styles
805
- variantStyles6[variant].container,
806
- errMsg ? variantStyles6[variant].error : variantStyles6[variant].focus,
807
- // Reduce visual weight for sm: no default drop shadows
808
- size !== "sm" && variant !== "minimal" && "shadow-sm",
809
- size !== "sm" && isFocused && "shadow-md",
810
- className
811
- ),
812
- disabled,
813
- ...restInput
814
- }
815
- ),
816
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
817
- getStatusIcon(),
818
- RightIcon && !loading2 && !success && !errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RightIcon, { className: cn("text-muted-foreground", sizeStyles8[size].icon) }),
819
- clearable && hasValue && !loading2 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
820
- "button",
749
+ children: [
750
+ label,
751
+ required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-destructive ml-1", children: "*" })
752
+ ]
753
+ }
754
+ ),
755
+ counter && maxLength && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
756
+ "span",
757
+ {
758
+ className: cn(
759
+ "text-xs transition-colors duration-200",
760
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
761
+ charCount >= maxLength && "text-destructive"
762
+ ),
763
+ children: [
764
+ charCount,
765
+ "/",
766
+ maxLength
767
+ ]
768
+ }
769
+ )
770
+ ] }),
771
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative group", children: [
772
+ LeftIcon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
773
+ "div",
821
774
  {
822
- type: "button",
823
- onClick: () => {
824
- if (onClear) return onClear();
825
- rest.onChange?.({ target: { value: "" } });
826
- },
827
775
  className: cn(
828
- "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-sm",
829
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
830
- "active:bg-accent active:text-accent-foreground",
831
- sizeStyles8[size].button
776
+ "absolute left-3 top-1/2 -translate-y-1/2 z-10",
777
+ "text-muted-foreground transition-colors duration-200",
778
+ // highlight icon when input (or controls) focused
779
+ "group-focus-within:text-primary"
832
780
  ),
833
- tabIndex: 0,
834
- "aria-label": "Clear input",
835
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.X, { className: sizeStyles8[size].icon })
781
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(LeftIcon, { className: sizeStyles8[size].icon })
836
782
  }
837
783
  ),
838
- showPasswordToggle && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
839
- "button",
784
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
785
+ "input",
840
786
  {
841
- type: "button",
842
- onClick: () => setShowPassword(!showPassword),
787
+ ref,
788
+ type: actualType,
789
+ ...value !== void 0 ? { value } : {},
790
+ maxLength,
791
+ required,
792
+ id: resolvedId,
793
+ autoComplete: type === "password" ? "current-password" : type === "email" ? "email" : rest.autoComplete,
794
+ onFocus: handleFocus,
795
+ onBlur: handleBlur,
796
+ onInvalid: (e) => {
797
+ e.preventDefault();
798
+ const key = getErrorKey(e.currentTarget.validity);
799
+ setLocalError(tv(key));
800
+ },
801
+ onInput: () => setLocalError(void 0),
802
+ "aria-invalid": !!errMsg,
803
+ "aria-describedby": [errorId, descId].filter(Boolean).join(" ") || void 0,
843
804
  className: cn(
844
- "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-full",
845
- "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring/40",
846
- "active:bg-accent/50 active:text-accent-foreground",
847
- sizeStyles8[size].button
805
+ "w-full text-foreground transition-all duration-200 ease-out",
806
+ "placeholder:text-muted-foreground focus:outline-none focus-visible:outline-none",
807
+ "disabled:cursor-not-allowed disabled:opacity-50",
808
+ // Size styles
809
+ sizeStyles8[size].input,
810
+ radiusClass,
811
+ // Icon padding adjustments
812
+ LeftIcon && "pl-10",
813
+ (RightIcon || showPasswordToggle || clearable || loading2 || success || errMsg) && "pr-10",
814
+ // Variant styles
815
+ variantStyles6[variant].container,
816
+ errMsg ? variantStyles6[variant].error : variantStyles6[variant].focus,
817
+ // Reduce visual weight for sm: no default drop shadows
818
+ size !== "sm" && variant !== "minimal" && "shadow-sm",
819
+ size !== "sm" && isFocused && "shadow-md",
820
+ className
848
821
  ),
849
- tabIndex: 0,
850
- "aria-label": showPassword ? "Hide password" : "Show password",
851
- children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.EyeOff, { className: sizeStyles8[size].icon }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Eye, { className: sizeStyles8[size].icon })
822
+ disabled,
823
+ ...restInput
824
+ }
825
+ ),
826
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1", children: [
827
+ getStatusIcon(),
828
+ RightIcon && !loading2 && !success && !errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RightIcon, { className: cn("text-muted-foreground", sizeStyles8[size].icon) }),
829
+ clearable && hasValue && !loading2 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
830
+ "button",
831
+ {
832
+ type: "button",
833
+ onClick: () => {
834
+ if (onClear) return onClear();
835
+ rest.onChange?.({ target: { value: "" } });
836
+ },
837
+ className: cn(
838
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-sm",
839
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
840
+ "active:bg-accent active:text-accent-foreground",
841
+ sizeStyles8[size].button
842
+ ),
843
+ tabIndex: 0,
844
+ "aria-label": "Clear input",
845
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.X, { className: sizeStyles8[size].icon })
846
+ }
847
+ ),
848
+ showPasswordToggle && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
849
+ "button",
850
+ {
851
+ type: "button",
852
+ onClick: () => setShowPassword(!showPassword),
853
+ className: cn(
854
+ "flex items-center justify-center text-muted-foreground hover:text-foreground transition-colors rounded-full",
855
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-ring/40",
856
+ "active:bg-accent/50 active:text-accent-foreground",
857
+ sizeStyles8[size].button
858
+ ),
859
+ tabIndex: 0,
860
+ "aria-label": showPassword ? "Hide password" : "Show password",
861
+ children: showPassword ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.EyeOff, { className: sizeStyles8[size].icon }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.Eye, { className: sizeStyles8[size].icon })
862
+ }
863
+ )
864
+ ] }),
865
+ variant === "minimal" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
866
+ "div",
867
+ {
868
+ className: cn(
869
+ "absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-primary/60 transition-all duration-300",
870
+ // default hidden
871
+ "w-0 opacity-0",
872
+ // expand underline when focused within input container
873
+ "group-focus-within:w-full group-focus-within:opacity-100"
874
+ )
852
875
  }
853
876
  )
854
877
  ] }),
855
- variant === "minimal" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
856
- "div",
878
+ errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
879
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
880
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: errMsg })
881
+ ] }),
882
+ (description || hint) && !errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
883
+ "p",
857
884
  {
885
+ id: descId,
858
886
  className: cn(
859
- "absolute bottom-0 left-0 h-0.5 bg-gradient-to-r from-primary to-primary/60 transition-all duration-300",
860
- // default hidden
861
- "w-0 opacity-0",
862
- // expand underline when focused within input container
863
- "group-focus-within:w-full group-focus-within:opacity-100"
864
- )
887
+ "text-xs transition-colors duration-200",
888
+ // follow focus state of the whole field area
889
+ "text-muted-foreground group-focus-within:text-primary/70"
890
+ ),
891
+ children: hint || description
865
892
  }
866
893
  )
867
- ] }),
868
- errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { id: errorId, className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
869
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
870
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: errMsg })
871
- ] }),
872
- (description || hint) && !errMsg && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { id: descId, className: cn(
873
- "text-xs transition-colors duration-200",
874
- // follow focus state of the whole field area
875
- "text-muted-foreground group-focus-within:text-primary/70"
876
- ), children: hint || description })
877
- ] });
878
- });
894
+ ] });
895
+ }
896
+ );
879
897
  Input.displayName = "Input";
880
- var SearchInput = (0, import_react3.forwardRef)(({
881
- onSearch,
882
- searchDelay = 300,
883
- placeholder = "Search...",
884
- ...props
885
- }, ref) => {
886
- const [searchValue, setSearchValue] = (0, import_react3.useState)(props.value || "");
887
- import_react3.default.useEffect(() => {
888
- if (!onSearch) return;
889
- const timer = setTimeout(() => {
890
- onSearch(searchValue);
891
- }, searchDelay);
892
- return () => clearTimeout(timer);
893
- }, [searchValue, onSearch, searchDelay]);
894
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
895
- Input,
896
- {
897
- ref,
898
- type: "search",
899
- leftIcon: import_lucide_react3.Search,
900
- placeholder,
901
- clearable: true,
902
- value: searchValue,
903
- onChange: (e) => setSearchValue(e.target.value),
904
- onClear: () => setSearchValue(""),
905
- ...props
906
- }
907
- );
908
- });
909
- SearchInput.displayName = "SearchInput";
910
- var PasswordInput = (0, import_react3.forwardRef)(({
911
- showStrength = false,
912
- strengthLabels = ["Weak", "Fair", "Good", "Strong"],
913
- ...props
914
- }, ref) => {
915
- const getPasswordStrength = (password) => {
916
- let score = 0;
917
- if (password.length >= 8) score++;
918
- if (/[a-z]/.test(password)) score++;
919
- if (/[A-Z]/.test(password)) score++;
920
- if (/[0-9]/.test(password)) score++;
921
- if (/[^A-Za-z0-9]/.test(password)) score++;
922
- return Math.min(score, 4);
923
- };
924
- const strength = showStrength && typeof props.value === "string" ? getPasswordStrength(props.value) : 0;
925
- const strengthColors = ["bg-destructive", "bg-warning", "bg-warning", "bg-success"];
926
- const strengthLabel = strengthLabels[strength - 1];
927
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
928
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Input, { ref, type: "password", ...props }),
929
- showStrength && props.value && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
930
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((level) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
931
- "div",
932
- {
933
- className: cn(
934
- "h-1 flex-1 rounded-full transition-colors duration-300",
935
- level <= strength ? strengthColors[strength - 1] : "bg-muted"
936
- )
937
- },
938
- level
939
- )) }),
940
- strengthLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: cn(
941
- "text-xs font-medium",
942
- strength <= 1 ? "text-destructive" : strength <= 2 ? "text-warning" : strength <= 3 ? "text-warning" : "text-success"
943
- ), children: strengthLabel })
944
- ] })
945
- ] });
946
- });
947
- PasswordInput.displayName = "PasswordInput";
948
- var NumberInput = (0, import_react3.forwardRef)(({
949
- min,
950
- max,
951
- step = 1,
952
- showSteppers = true,
953
- onIncrement,
954
- onDecrement,
955
- formatThousands = false,
956
- locale = "vi-VN",
957
- value,
958
- onChange,
959
- ...props
960
- }, ref) => {
961
- const toNumber = (v) => {
962
- if (v === "" || v === void 0 || v === null) return 0;
963
- const n = Number(v);
964
- return Number.isFinite(n) ? n : 0;
965
- };
966
- const format = import_react3.default.useCallback(
967
- (n) => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(n),
968
- [locale]
969
- );
970
- const parse = (s) => {
971
- const digits = (s || "").replace(/\D+/g, "");
972
- return digits ? Number(digits) : NaN;
973
- };
974
- const [displayValue, setDisplayValue] = import_react3.default.useState(
975
- formatThousands ? value !== void 0 && value !== null && value !== "" ? format(toNumber(value)) : "" : String(value ?? "")
976
- );
977
- const currentValue = toNumber(value);
978
- import_react3.default.useEffect(() => {
979
- if (formatThousands) {
980
- const next = value === "" || value === void 0 || value === null ? "" : format(toNumber(value));
981
- setDisplayValue((prev) => prev === next ? prev : next);
982
- } else {
983
- const next = String(value ?? "");
984
- setDisplayValue((prev) => prev === next ? prev : next);
985
- }
986
- }, [value, formatThousands, locale, format]);
987
- const handleIncrement = () => {
988
- if (onIncrement) {
989
- onIncrement();
990
- } else if (onChange) {
991
- const newValue = Math.min(currentValue + step, max ?? Infinity);
992
- if (formatThousands) setDisplayValue(format(newValue));
993
- onChange({ target: { value: newValue.toString() } });
994
- }
995
- };
996
- const handleDecrement = () => {
997
- if (onDecrement) {
998
- onDecrement();
999
- } else if (onChange) {
1000
- const newValue = Math.max(currentValue - step, min ?? -Infinity);
1001
- if (formatThousands) setDisplayValue(format(newValue));
1002
- onChange({ target: { value: newValue.toString() } });
1003
- }
1004
- };
1005
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative", children: [
1006
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
898
+ var SearchInput = (0, import_react3.forwardRef)(
899
+ ({ onSearch, searchDelay = 300, placeholder = "Search...", ...props }, ref) => {
900
+ const [searchValue, setSearchValue] = (0, import_react3.useState)(props.value || "");
901
+ import_react3.default.useEffect(() => {
902
+ if (!onSearch) return;
903
+ const timer = setTimeout(() => {
904
+ onSearch(searchValue);
905
+ }, searchDelay);
906
+ return () => clearTimeout(timer);
907
+ }, [searchValue, onSearch, searchDelay]);
908
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1007
909
  Input,
1008
910
  {
1009
911
  ref,
1010
- type: formatThousands ? "text" : "number",
1011
- min,
1012
- max,
1013
- step,
1014
- rightIcon: showSteppers ? void 0 : props.rightIcon,
1015
- value: displayValue,
1016
- onChange: (e) => {
1017
- if (!onChange) return;
1018
- if (!formatThousands) return onChange(e);
1019
- const raw = e.target.value;
1020
- const parsed = parse(raw);
1021
- if (Number.isNaN(parsed)) {
1022
- setDisplayValue("");
1023
- onChange({ target: { value: "" } });
1024
- } else {
1025
- const bounded = Math.min(Math.max(parsed, min ?? -Infinity), max ?? Infinity);
1026
- setDisplayValue(format(bounded));
1027
- onChange({ target: { value: bounded.toString() } });
912
+ type: "search",
913
+ leftIcon: import_lucide_react3.Search,
914
+ placeholder,
915
+ clearable: true,
916
+ value: searchValue,
917
+ onChange: (e) => setSearchValue(e.target.value),
918
+ onClear: () => setSearchValue(""),
919
+ ...props
920
+ }
921
+ );
922
+ }
923
+ );
924
+ SearchInput.displayName = "SearchInput";
925
+ var PasswordInput = (0, import_react3.forwardRef)(
926
+ ({ showStrength = false, strengthLabels = ["Weak", "Fair", "Good", "Strong"], ...props }, ref) => {
927
+ const getPasswordStrength = (password) => {
928
+ let score = 0;
929
+ if (password.length >= 8) score++;
930
+ if (/[a-z]/.test(password)) score++;
931
+ if (/[A-Z]/.test(password)) score++;
932
+ if (/[0-9]/.test(password)) score++;
933
+ if (/[^A-Za-z0-9]/.test(password)) score++;
934
+ return Math.min(score, 4);
935
+ };
936
+ const strength = showStrength && typeof props.value === "string" ? getPasswordStrength(props.value) : 0;
937
+ const strengthColors = ["bg-destructive", "bg-warning", "bg-warning", "bg-success"];
938
+ const strengthLabel = strengthLabels[strength - 1];
939
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-2", children: [
940
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Input, { ref, type: "password", ...props }),
941
+ showStrength && props.value && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "space-y-1", children: [
942
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex gap-1", children: [1, 2, 3, 4].map((level) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
943
+ "div",
944
+ {
945
+ className: cn(
946
+ "h-1 flex-1 rounded-full transition-colors duration-300",
947
+ level <= strength ? strengthColors[strength - 1] : "bg-muted"
948
+ )
949
+ },
950
+ level
951
+ )) }),
952
+ strengthLabel && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
953
+ "p",
954
+ {
955
+ className: cn(
956
+ "text-xs font-medium",
957
+ strength <= 1 ? "text-destructive" : strength <= 2 ? "text-warning" : strength <= 3 ? "text-warning" : "text-success"
958
+ ),
959
+ children: strengthLabel
1028
960
  }
1029
- },
1030
- ...props,
1031
- className: cn(
1032
- showSteppers && [
1033
- "pr-12",
1034
- // Hide native browser steppers
1035
- "[&::-webkit-outer-spin-button]:appearance-none",
1036
- "[&::-webkit-inner-spin-button]:appearance-none",
1037
- "[&::-webkit-inner-spin-button]:m-0",
1038
- "appearance-none"
1039
- ],
1040
- props.className
1041
961
  )
962
+ ] })
963
+ ] });
964
+ }
965
+ );
966
+ PasswordInput.displayName = "PasswordInput";
967
+ var NumberInput = (0, import_react3.forwardRef)(
968
+ ({ min, max, step = 1, showSteppers = true, onIncrement, onDecrement, formatThousands = false, locale = "vi-VN", value, onChange, ...props }, ref) => {
969
+ const toNumber = (v) => {
970
+ if (v === "" || v === void 0 || v === null) return 0;
971
+ const n = Number(v);
972
+ return Number.isFinite(n) ? n : 0;
973
+ };
974
+ const format = import_react3.default.useCallback((n) => new Intl.NumberFormat(locale, { maximumFractionDigits: 0 }).format(n), [locale]);
975
+ const parse = (s) => {
976
+ const digits = (s || "").replace(/\D+/g, "");
977
+ return digits ? Number(digits) : NaN;
978
+ };
979
+ const [displayValue, setDisplayValue] = import_react3.default.useState(
980
+ formatThousands ? value !== void 0 && value !== null && value !== "" ? format(toNumber(value)) : "" : String(value ?? "")
981
+ );
982
+ const currentValue = toNumber(value);
983
+ import_react3.default.useEffect(() => {
984
+ if (formatThousands) {
985
+ const next = value === "" || value === void 0 || value === null ? "" : format(toNumber(value));
986
+ setDisplayValue((prev) => prev === next ? prev : next);
987
+ } else {
988
+ const next = String(value ?? "");
989
+ setDisplayValue((prev) => prev === next ? prev : next);
990
+ }
991
+ }, [value, formatThousands, locale, format]);
992
+ const handleIncrement = () => {
993
+ if (onIncrement) {
994
+ onIncrement();
995
+ } else if (onChange) {
996
+ const newValue = Math.min(currentValue + step, max ?? Infinity);
997
+ if (formatThousands) setDisplayValue(format(newValue));
998
+ onChange({ target: { value: newValue.toString() } });
1042
999
  }
1043
- ),
1044
- showSteppers && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5", children: [
1000
+ };
1001
+ const handleDecrement = () => {
1002
+ if (onDecrement) {
1003
+ onDecrement();
1004
+ } else if (onChange) {
1005
+ const newValue = Math.max(currentValue - step, min ?? -Infinity);
1006
+ if (formatThousands) setDisplayValue(format(newValue));
1007
+ onChange({ target: { value: newValue.toString() } });
1008
+ }
1009
+ };
1010
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative", children: [
1045
1011
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1046
- "button",
1012
+ Input,
1047
1013
  {
1048
- type: "button",
1049
- onClick: handleIncrement,
1050
- disabled: max !== void 0 && currentValue >= max,
1051
- className: cn(
1052
- "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
1053
- "hover:bg-accent focus:outline-none focus:bg-accent",
1054
- "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1055
- "text-muted-foreground hover:text-foreground"
1056
- ),
1057
- "aria-label": "Increase value",
1058
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1059
- "svg",
1060
- {
1061
- width: "8",
1062
- height: "8",
1063
- viewBox: "0 0 8 8",
1064
- fill: "none",
1065
- className: "flex-shrink-0",
1066
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1067
- "path",
1068
- {
1069
- d: "M4 2L6 6H2L4 2Z",
1070
- fill: "currentColor"
1071
- }
1072
- )
1014
+ ref,
1015
+ type: formatThousands ? "text" : "number",
1016
+ min,
1017
+ max,
1018
+ step,
1019
+ rightIcon: showSteppers ? void 0 : props.rightIcon,
1020
+ value: displayValue,
1021
+ onChange: (e) => {
1022
+ if (!onChange) return;
1023
+ if (!formatThousands) return onChange(e);
1024
+ const raw = e.target.value;
1025
+ const parsed = parse(raw);
1026
+ if (Number.isNaN(parsed)) {
1027
+ setDisplayValue("");
1028
+ onChange({ target: { value: "" } });
1029
+ } else {
1030
+ const bounded = Math.min(Math.max(parsed, min ?? -Infinity), max ?? Infinity);
1031
+ setDisplayValue(format(bounded));
1032
+ onChange({ target: { value: bounded.toString() } });
1073
1033
  }
1034
+ },
1035
+ ...props,
1036
+ className: cn(
1037
+ showSteppers && [
1038
+ "pr-12",
1039
+ // Hide native browser steppers
1040
+ "[&::-webkit-outer-spin-button]:appearance-none",
1041
+ "[&::-webkit-inner-spin-button]:appearance-none",
1042
+ "[&::-webkit-inner-spin-button]:m-0",
1043
+ "appearance-none"
1044
+ ],
1045
+ props.className
1074
1046
  )
1075
1047
  }
1076
1048
  ),
1049
+ showSteppers && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 flex flex-col gap-0.5", children: [
1050
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1051
+ "button",
1052
+ {
1053
+ type: "button",
1054
+ onClick: handleIncrement,
1055
+ disabled: max !== void 0 && currentValue >= max,
1056
+ className: cn(
1057
+ "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
1058
+ "hover:bg-accent focus:outline-none focus:bg-accent",
1059
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1060
+ "text-muted-foreground hover:text-foreground"
1061
+ ),
1062
+ "aria-label": "Increase value",
1063
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M4 2L6 6H2L4 2Z", fill: "currentColor" }) })
1064
+ }
1065
+ ),
1066
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1067
+ "button",
1068
+ {
1069
+ type: "button",
1070
+ onClick: handleDecrement,
1071
+ disabled: min !== void 0 && currentValue <= min,
1072
+ className: cn(
1073
+ "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
1074
+ "hover:bg-accent focus:outline-none focus:bg-accent",
1075
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1076
+ "text-muted-foreground hover:text-foreground"
1077
+ ),
1078
+ "aria-label": "Decrease value",
1079
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "8", height: "8", viewBox: "0 0 8 8", fill: "none", className: "flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M4 6L2 2H6L4 6Z", fill: "currentColor" }) })
1080
+ }
1081
+ )
1082
+ ] })
1083
+ ] });
1084
+ }
1085
+ );
1086
+ NumberInput.displayName = "NumberInput";
1087
+ var Textarea = (0, import_react3.forwardRef)(
1088
+ ({ label, error, description, variant = "default", resize = "vertical", counter = false, className, required, value, maxLength, ...props }, ref) => {
1089
+ const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
1090
+ const charCount = typeof value === "string" ? value.length : 0;
1091
+ const variantStyles6 = {
1092
+ 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",
1093
+ 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",
1094
+ 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",
1095
+ 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"
1096
+ };
1097
+ const resizeClasses = {
1098
+ none: "resize-none",
1099
+ vertical: "resize-y",
1100
+ horizontal: "resize-x",
1101
+ both: "resize"
1102
+ };
1103
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-full space-y-2", children: [
1104
+ label && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
1105
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1106
+ "label",
1107
+ {
1108
+ className: cn(
1109
+ "text-sm font-medium transition-colors duration-200",
1110
+ isFocused ? "text-primary" : "text-foreground",
1111
+ error && "text-destructive"
1112
+ ),
1113
+ children: [
1114
+ label,
1115
+ required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-destructive ml-1", children: "*" })
1116
+ ]
1117
+ }
1118
+ ),
1119
+ counter && maxLength && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1120
+ "span",
1121
+ {
1122
+ className: cn(
1123
+ "text-xs transition-colors duration-200",
1124
+ charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
1125
+ charCount >= maxLength && "text-destructive"
1126
+ ),
1127
+ children: [
1128
+ charCount,
1129
+ "/",
1130
+ maxLength
1131
+ ]
1132
+ }
1133
+ )
1134
+ ] }),
1077
1135
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1078
- "button",
1136
+ "textarea",
1079
1137
  {
1080
- type: "button",
1081
- onClick: handleDecrement,
1082
- disabled: min !== void 0 && currentValue <= min,
1138
+ ref,
1139
+ value,
1140
+ maxLength,
1141
+ required,
1142
+ onFocus: () => setIsFocused(true),
1143
+ onBlur: () => setIsFocused(false),
1083
1144
  className: cn(
1084
- "flex items-center justify-center w-4 h-4 rounded-sm transition-colors",
1085
- "hover:bg-accent focus:outline-none focus:bg-accent",
1086
- "disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-transparent",
1087
- "text-muted-foreground hover:text-foreground"
1145
+ "w-full rounded-lg px-4 py-3 text-sm text-foreground transition-all duration-200",
1146
+ "placeholder:text-muted-foreground focus:outline-none min-h-[80px]",
1147
+ "disabled:cursor-not-allowed disabled:opacity-50",
1148
+ variantStyles6[variant],
1149
+ // DÒNG NÀY ĐÃ ĐƯỢC CẬP NHẬT:
1150
+ 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",
1151
+ resizeClasses[resize],
1152
+ isFocused && "shadow-md",
1153
+ variant !== "minimal" && "shadow-sm",
1154
+ className
1088
1155
  ),
1089
- "aria-label": "Decrease value",
1090
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1091
- "svg",
1092
- {
1093
- width: "8",
1094
- height: "8",
1095
- viewBox: "0 0 8 8",
1096
- fill: "none",
1097
- className: "flex-shrink-0",
1098
- children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1099
- "path",
1100
- {
1101
- d: "M4 6L2 2H6L4 6Z",
1102
- fill: "currentColor"
1103
- }
1104
- )
1105
- }
1106
- )
1156
+ ...props
1107
1157
  }
1108
- )
1109
- ] })
1110
- ] });
1111
- });
1112
- NumberInput.displayName = "NumberInput";
1113
- var Textarea = (0, import_react3.forwardRef)(({
1114
- label,
1115
- error,
1116
- description,
1117
- variant = "default",
1118
- resize = "vertical",
1119
- counter = false,
1120
- className,
1121
- required,
1122
- value,
1123
- maxLength,
1124
- ...props
1125
- }, ref) => {
1126
- const [isFocused, setIsFocused] = (0, import_react3.useState)(false);
1127
- const charCount = typeof value === "string" ? value.length : 0;
1128
- const variantStyles6 = {
1129
- 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",
1130
- 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",
1131
- 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",
1132
- 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"
1133
- };
1134
- const resizeClasses = {
1135
- none: "resize-none",
1136
- vertical: "resize-y",
1137
- horizontal: "resize-x",
1138
- both: "resize"
1139
- };
1140
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-full space-y-2", children: [
1141
- label && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between", children: [
1142
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: cn(
1143
- "text-sm font-medium transition-colors duration-200",
1144
- isFocused ? "text-primary" : "text-foreground",
1145
- error && "text-destructive"
1146
- ), children: [
1147
- label,
1148
- required && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-destructive ml-1", children: "*" })
1158
+ ),
1159
+ error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1160
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
1161
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: error })
1149
1162
  ] }),
1150
- counter && maxLength && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: cn(
1151
- "text-xs transition-colors duration-200",
1152
- charCount > maxLength * 0.9 ? "text-warning" : "text-muted-foreground",
1153
- charCount >= maxLength && "text-destructive"
1154
- ), children: [
1155
- charCount,
1156
- "/",
1157
- maxLength
1158
- ] })
1159
- ] }),
1160
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1161
- "textarea",
1162
- {
1163
- ref,
1164
- value,
1165
- maxLength,
1166
- required,
1167
- onFocus: () => setIsFocused(true),
1168
- onBlur: () => setIsFocused(false),
1169
- className: cn(
1170
- "w-full rounded-lg px-4 py-3 text-sm text-foreground transition-all duration-200",
1171
- "placeholder:text-muted-foreground focus:outline-none min-h-[80px]",
1172
- "disabled:cursor-not-allowed disabled:opacity-50",
1173
- variantStyles6[variant],
1174
- 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",
1175
- resizeClasses[resize],
1176
- isFocused && "shadow-md",
1177
- variant !== "minimal" && "shadow-sm",
1178
- className
1179
- ),
1180
- ...props
1181
- }
1182
- ),
1183
- error && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-2 text-sm text-destructive animate-in slide-in-from-top-1 duration-200", children: [
1184
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react3.AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
1185
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: error })
1186
- ] }),
1187
- description && !error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: cn(
1188
- "text-xs transition-colors duration-200",
1189
- isFocused ? "text-primary/70" : "text-muted-foreground"
1190
- ), children: description })
1191
- ] });
1192
- });
1163
+ description && !error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: cn("text-xs transition-colors duration-200", isFocused ? "text-primary/70" : "text-muted-foreground"), children: description })
1164
+ ] });
1165
+ }
1166
+ );
1193
1167
  Textarea.displayName = "Textarea";
1194
1168
  var Input_default = Input;
1195
1169
 
@@ -6182,8 +6156,10 @@ function OverlayControls({
6182
6156
  break;
6183
6157
  case "f":
6184
6158
  case "F":
6185
- e.preventDefault();
6186
- onToggleFullscreen?.();
6159
+ if (!e.ctrlKey && !e.metaKey) {
6160
+ e.preventDefault();
6161
+ onToggleFullscreen?.();
6162
+ }
6187
6163
  break;
6188
6164
  case "m":
6189
6165
  case "M":