diff options
| author | Maze <167211895+mazeincoding@users.noreply.github.com> | 2026-01-31 00:20:04 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 00:20:04 +0100 |
| commit | 7bf0984698e43b9aa2dd879f65951318ed410390 (patch) | |
| tree | ccf2cd590a47d550152afc07129cb55c71a9ec35 /apps/web/src/components/ui/input.tsx | |
| parent | 0173db99448d1c4c6415dcf15272e17551b48b04 (diff) | |
| download | opencut-7bf0984698e43b9aa2dd879f65951318ed410390.tar.gz | |
codebase overhaul (#697)
Diffstat (limited to 'apps/web/src/components/ui/input.tsx')
| -rw-r--r-- | apps/web/src/components/ui/input.tsx | 225 |
1 files changed, 127 insertions, 98 deletions
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx index 8f195a50..9be4bca2 100644 --- a/apps/web/src/components/ui/input.tsx +++ b/apps/web/src/components/ui/input.tsx @@ -1,113 +1,142 @@ "use client"; import { Eye, EyeOff, X } from "lucide-react"; -import { cn } from "../../lib/utils"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/utils/ui"; import { Button } from "./button"; -import { forwardRef, ComponentProps } from "react"; +import { forwardRef, type ComponentProps } from "react"; import { useState } from "react"; -interface InputProps extends ComponentProps<"input"> { - showPassword?: boolean; - onShowPasswordChange?: (show: boolean) => void; - showClearIcon?: boolean; - onClear?: () => void; - containerClassName?: string; +const inputVariants = cva( + "file:text-foreground placeholder:text-muted-foreground border-border bg-background flex w-full min-w-0 rounded-md border shadow-xs outline-none file:inline-flex file:border-0 file:bg-transparent file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-4 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", + { + variants: { + variant: { + default: "selection:bg-primary selection:text-primary-foreground focus-visible:border-primary focus-visible:ring-primary/10", + destructive: "selection:bg-destructive selection:text-destructive-foreground focus-visible:border-destructive focus-visible:ring-destructive/10", + }, + size: { + default: + "h-9 px-3 py-1 text-base file:h-7 file:text-sm md:text-sm", + sm: "h-8 px-3 text-xs file:h-6 file:text-xs", + lg: "h-10 px-4 text-base file:h-8 file:text-sm md:text-sm", + }, + }, + defaultVariants: { + size: "default", + variant: "default", + }, + }, +); + +interface InputProps + extends Omit<ComponentProps<"input">, "size">, + VariantProps<typeof inputVariants> { + showPassword?: boolean; + onShowPasswordChange?: (show: boolean) => void; + showClearIcon?: boolean; + onClear?: () => void; + containerClassName?: string; } const Input = forwardRef<HTMLInputElement, InputProps>( - ( - { - className, - type, - containerClassName, - showPassword, - onShowPasswordChange, - showClearIcon, - onClear, - value, - onFocus, - onBlur, - ...props - }, - ref - ) => { - const [isFocused, setIsFocused] = useState(false); + ( + { + className, + type, + size, + variant, + containerClassName, + showPassword, + onShowPasswordChange, + showClearIcon, + onClear, + value, + onFocus, + onBlur, + ...props + }, + ref, + ) => { + const [isFocused, setIsFocused] = useState(false); - const isPassword = type === "password"; - const showPasswordToggle = isPassword && onShowPasswordChange; - const showClear = - showClearIcon && - onClear && - value && - String(value).length > 0 && - isFocused; - const inputType = isPassword && showPassword ? "text" : type; + const isPassword = type === "password"; + const showPasswordToggle = isPassword && onShowPasswordChange; + const showClear = + showClearIcon && + onClear && + value && + String(value).length > 0 && + isFocused; + const inputType = isPassword && showPassword ? "text" : type; - const hasIcons = showPasswordToggle || showClear; - const iconCount = Number(showPasswordToggle) + Number(showClear); - const paddingRight = - iconCount === 2 ? "pr-20" : iconCount === 1 ? "pr-10" : ""; + const hasIcons = showPasswordToggle || showClear; + const iconCount = Number(showPasswordToggle) + Number(showClear); + const paddingRight = + iconCount === 2 ? "pr-20" : iconCount === 1 ? "pr-10" : ""; - return ( - <div className={cn(hasIcons ? "relative w-full" : "", containerClassName)}> - <input - type={inputType} - className={cn( - "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-accent/50 px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", - "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[2px]", - "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", - paddingRight, - className - )} - ref={ref} - value={value} - onFocus={(e) => { - setIsFocused(true); - onFocus?.(e); - }} - onBlur={(e) => { - setIsFocused(false); - onBlur?.(e); - }} - {...props} - /> - {showClear && ( - <Button - type="button" - variant="text" - size="icon" - onMouseDown={(e) => { - e.preventDefault(); - onClear?.(); - }} - className="absolute right-0 top-0 h-full px-3 text-muted-foreground !opacity-100" - aria-label="Clear input" - > - <X className="!size-[0.85]" /> - </Button> - )} - {showPasswordToggle && ( - <Button - type="button" - variant="text" - size="icon" - onClick={() => onShowPasswordChange?.(!showPassword)} - className={cn( - "absolute top-0 h-full px-3 text-muted-foreground hover:text-foreground", - showClear ? "right-10" : "right-0" - )} - aria-label={showPassword ? "Hide password" : "Show password"} - > - {showPassword ? ( - <Eye className="h-4 w-4" /> - ) : ( - <EyeOff className="h-4 w-4" /> - )} - </Button> - )} - </div> - ); - } + return ( + <div + className={cn(hasIcons ? "relative w-full" : "", containerClassName)} + > + <input + type={inputType} + className={cn( + inputVariants({ + size, + className: cn(paddingRight, className), + variant, + }), + )} + ref={ref} + value={value} + onFocus={(e) => { + setIsFocused(true); + onFocus?.(e); + }} + onBlur={(e) => { + setIsFocused(false); + onBlur?.(e); + }} + {...props} + /> + {showClear && ( + <Button + type="button" + variant="text" + size="icon" + onMouseDown={(e) => { + e.preventDefault(); + onClear?.(); + }} + className="text-muted-foreground absolute top-0 right-0 h-full px-3 !opacity-100" + aria-label="Clear input" + > + <X className="!size-[0.85]" /> + </Button> + )} + {showPasswordToggle && ( + <Button + type="button" + variant="text" + size="icon" + onClick={() => onShowPasswordChange?.(!showPassword)} + className={cn( + "text-muted-foreground hover:text-foreground absolute top-0 h-full px-3", + showClear ? "right-10" : "right-0", + )} + aria-label={showPassword ? "Hide password" : "Show password"} + > + {showPassword ? ( + <Eye className="size-4" /> + ) : ( + <EyeOff className="size-4" /> + )} + </Button> + )} + </div> + ); + }, ); Input.displayName = "Input"; |
