diff options
| author | Maze Winther <mazewinther@gmail.com> | 2025-08-04 01:30:33 +0200 |
|---|---|---|
| committer | Maze Winther <mazewinther@gmail.com> | 2025-08-04 01:30:33 +0200 |
| commit | 48b694bbd875740bbdb350001bb688c2fbf4ad1f (patch) | |
| tree | a86e73777d4800b6c5ebf7ab094a8410c51ba7f0 /apps/web/src/components/ui/input.tsx | |
| parent | c0651fec193bcea9a1764ca8435d7191c5d3980d (diff) | |
| download | opencut-48b694bbd875740bbdb350001bb688c2fbf4ad1f.tar.gz | |
feat: sound effects functionality with freesound's api
Diffstat (limited to 'apps/web/src/components/ui/input.tsx')
| -rw-r--r-- | apps/web/src/components/ui/input.tsx | 62 |
1 files changed, 57 insertions, 5 deletions
diff --git a/apps/web/src/components/ui/input.tsx b/apps/web/src/components/ui/input.tsx index d4cd57a1..4fb0f5b4 100644 --- a/apps/web/src/components/ui/input.tsx +++ b/apps/web/src/components/ui/input.tsx @@ -1,5 +1,5 @@ import * as React from "react"; -import { Eye, EyeOff } from "lucide-react"; +import { Eye, EyeOff, X } from "lucide-react"; import { cn } from "../../lib/utils"; import { Button } from "./button"; @@ -7,39 +7,91 @@ import { Button } from "./button"; interface InputProps extends React.ComponentProps<"input"> { showPassword?: boolean; onShowPasswordChange?: (show: boolean) => void; + showClearIcon?: boolean; + onClear?: () => void; } const Input = React.forwardRef<HTMLInputElement, InputProps>( ( - { className, type, showPassword, onShowPasswordChange, value, ...props }, + { + className, + type, + showPassword, + onShowPasswordChange, + showClearIcon, + onClear, + value, + onFocus, + onBlur, + ...props + }, ref ) => { + const [isFocused, setIsFocused] = React.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 hasIcons = showPasswordToggle || showClear; + const iconCount = Number(showPasswordToggle) + Number(showClear); + const paddingRight = + iconCount === 2 ? "pr-20" : iconCount === 1 ? "pr-10" : ""; + return ( - <div className={showPassword ? "relative w-full" : ""}> + <div className={hasIcons ? "relative w-full" : ""}> <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-transparent 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", - showPasswordToggle && "pr-10", + 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="absolute right-0 top-0 h-full px-3 text-muted-foreground hover:text-foreground" + 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 ? ( |
