feat: add form input components with tests

- Add TagInput component for equipment tags
- Add ExerciseList component for workout exercises
- Both components include comprehensive test suites
- Add data-testid attributes for testability
This commit is contained in:
Millian Lamiaux
2026-03-14 20:42:36 +01:00
parent 71e9a9bdb5
commit 3df7dd4a47
4 changed files with 748 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
"use client"
import * as React from "react"
import { X, Plus } from "lucide-react"
import { cn } from "@/lib/utils"
import { Input } from "@/components/ui/input"
interface TagInputProps {
value: string[]
onChange: (value: string[]) => void
placeholder?: string
className?: string
disabled?: boolean
id?: string
}
function TagInput({
value,
onChange,
placeholder = "Add tag...",
className,
disabled = false,
}: TagInputProps) {
const [inputValue, setInputValue] = React.useState("")
const inputRef = React.useRef<HTMLInputElement>(null)
const handleAddTag = () => {
const trimmed = inputValue.trim()
if (trimmed && !value.includes(trimmed)) {
onChange([...value, trimmed])
setInputValue("")
}
}
const handleRemoveTag = (tagToRemove: string) => {
onChange(value.filter((tag) => tag !== tagToRemove))
}
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
e.preventDefault()
handleAddTag()
} else if (e.key === "Backspace" && !inputValue && value.length > 0) {
handleRemoveTag(value[value.length - 1])
}
}
return (
<div
className={cn(
"flex flex-wrap items-center gap-2 rounded-md border border-neutral-700 bg-neutral-900 p-2 focus-within:border-orange-500 focus-within:ring-2 focus-within:ring-orange-500 focus-within:ring-offset-2",
className
)}
>
{value.map((tag) => (
<span
key={tag}
className="inline-flex items-center gap-1 rounded-md bg-orange-500/20 px-2 py-1 text-sm text-orange-500"
>
{tag}
{!disabled && (
<button
type="button"
onClick={() => handleRemoveTag(tag)}
className="rounded-full p-0.5 hover:bg-orange-500/30"
>
<X className="h-3 w-3" />
</button>
)}
</span>
))}
<div className="flex flex-1 items-center">
<Input
ref={inputRef}
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={handleAddTag}
placeholder={value.length === 0 ? placeholder : ""}
disabled={disabled}
className="flex-1 border-0 bg-transparent px-0 py-1 text-white placeholder:text-neutral-500 focus-visible:ring-0 focus-visible:ring-offset-0"
/>
{inputValue.trim() && (
<button
type="button"
onClick={handleAddTag}
disabled={disabled}
className="ml-2 rounded-md p-1 text-neutral-500 hover:bg-neutral-800 hover:text-white"
>
<Plus className="h-4 w-4" />
</button>
)}
</div>
</div>
)
}
export { TagInput }