- Add TagInput component for equipment tags - Add ExerciseList component for workout exercises - Both components include comprehensive test suites - Add data-testid attributes for testability
101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
"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 }
|