Initial Switch to V2. Completely Overhauled Backend, Frontend and General Structure.

This commit is contained in:
2026-04-17 14:37:36 +03:00
parent eb773c5531
commit 0a8a42d69b
447 changed files with 70696 additions and 492 deletions

View File

@@ -0,0 +1,101 @@
// src/components/ui/SearchBar.jsx
// Debounced search input with a magnifier icon and animated clear button.
//
// Props:
// value — string — controlled value (optional; uncontrolled if omitted)
// onChange — (value: string) => void — called after debounce delay
// placeholder — string (default: 'Search…')
// debounce — number — ms delay before onChange fires (default: 300)
// disabled — boolean
// className — extra classes on the wrapper
import { useState, useEffect, useRef } from 'react'
export default function SearchBar({
value: controlledValue,
onChange,
placeholder = 'Search…',
debounce = 300,
disabled = false,
className = '',
}) {
const isControlled = controlledValue !== undefined
const [localValue, setLocalValue] = useState(isControlled ? controlledValue : '')
const timerRef = useRef(null)
const inputRef = useRef(null)
const prevControlledRef = useRef(controlledValue)
// Only sync from outside when the parent *explicitly* changes the value
// (e.g. "Clear filters" resets it to ''). Do NOT sync on every re-render —
// that's what caused the keystroke lag: the debounced parent setState was
// writing back into this input and overwriting what the user just typed.
useEffect(() => {
if (isControlled && controlledValue !== prevControlledRef.current) {
prevControlledRef.current = controlledValue
setLocalValue(controlledValue)
}
}, [controlledValue, isControlled])
function handleChange(e) {
const v = e.target.value
setLocalValue(v)
prevControlledRef.current = v // keep ref in sync so the effect doesn't clobber it
if (debounce > 0) {
clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => onChange?.(v), debounce)
} else {
onChange?.(v)
}
}
function handleClear() {
setLocalValue('')
prevControlledRef.current = ''
clearTimeout(timerRef.current)
onChange?.('')
inputRef.current?.focus()
}
const displayValue = localValue
return (
<div className={`searchbar ${className}`}>
{/* Search icon */}
<span className="searchbar-icon" aria-hidden="true">
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round">
<circle cx="6.5" cy="6.5" r="4.5" />
<path d="M10 10l3.5 3.5" />
</svg>
</span>
<input
ref={inputRef}
type="search"
className="searchbar-input"
value={displayValue}
onChange={handleChange}
placeholder={placeholder}
disabled={disabled}
aria-label={placeholder}
autoComplete="off"
autoCorrect="off"
spellCheck="false"
/>
{/* Clear button — only when there's text */}
{displayValue && !disabled && (
<button
type="button"
className="searchbar-clear"
onClick={handleClear}
aria-label="Clear search"
tabIndex={-1}
>
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true">
<path d="M1 1l8 8M9 1L1 9" />
</svg>
</button>
)}
</div>
)
}