- Add dark:* classes to 10 common components - Create ThemeProvider with Zustand persistence - Create ThemeToggle component (simple and full modes) - Implement Toast notification system (toastStore, useToast, ToastContainer) - Support success, error, warning, info toast types - Integrate ToastContainer in App.tsx Components updated: - Modal, EmptyState, StatusBadge, SearchInput - ConfirmDialog, PageHeader, FormField, ActionButtons - DataTable, LoadingSpinner Closes: G-005, G-008 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
71 lines
1.9 KiB
TypeScript
71 lines
1.9 KiB
TypeScript
/**
|
|
* SearchInput - Reusable search input with debounce
|
|
*/
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
|
import { Search, X } from 'lucide-react';
|
|
import clsx from 'clsx';
|
|
import { SEARCH_DEBOUNCE_MS } from '../../utils';
|
|
|
|
interface SearchInputProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
placeholder?: string;
|
|
debounceMs?: number;
|
|
className?: string;
|
|
autoFocus?: boolean;
|
|
}
|
|
|
|
export function SearchInput({
|
|
value,
|
|
onChange,
|
|
placeholder = 'Buscar...',
|
|
debounceMs = SEARCH_DEBOUNCE_MS,
|
|
className,
|
|
autoFocus = false,
|
|
}: SearchInputProps) {
|
|
const [localValue, setLocalValue] = useState(value);
|
|
|
|
useEffect(() => {
|
|
setLocalValue(value);
|
|
}, [value]);
|
|
|
|
useEffect(() => {
|
|
const timer = setTimeout(() => {
|
|
if (localValue !== value) {
|
|
onChange(localValue);
|
|
}
|
|
}, debounceMs);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [localValue, debounceMs, onChange, value]);
|
|
|
|
const handleClear = useCallback(() => {
|
|
setLocalValue('');
|
|
onChange('');
|
|
}, [onChange]);
|
|
|
|
return (
|
|
<div className={clsx('relative', className)}>
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-foreground-muted" />
|
|
<input
|
|
type="text"
|
|
placeholder={placeholder}
|
|
className="w-full pl-10 pr-10 py-2 border border-border dark:border-border rounded-lg bg-surface-card dark:bg-surface-card text-foreground placeholder:text-foreground-muted focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
|
|
value={localValue}
|
|
onChange={(e) => setLocalValue(e.target.value)}
|
|
autoFocus={autoFocus}
|
|
/>
|
|
{localValue && (
|
|
<button
|
|
type="button"
|
|
onClick={handleClear}
|
|
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 text-foreground-muted hover:text-foreground transition-colors"
|
|
>
|
|
<X className="w-4 h-4" />
|
|
</button>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|