chore: overhaul infrastructure and integrate @mintel packages
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- Restructure to pnpm monorepo (site moved to apps/web) - Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config - Implement Docker service architecture (Varnish, Directus, Gatekeeper) - Setup environment-aware Gitea Actions deployment
This commit is contained in:
78
apps/web/src/components/SearchBar.tsx
Normal file
78
apps/web/src/components/SearchBar.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
interface SearchBarProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
size?: 'small' | 'large';
|
||||
}
|
||||
|
||||
export const SearchBar: React.FC<SearchBarProps> = ({ value: propValue, onChange, size = 'small' }) => {
|
||||
const [internalValue, setInternalValue] = useState('');
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const value = propValue !== undefined ? propValue : internalValue;
|
||||
|
||||
const handleInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
if (onChange) {
|
||||
onChange(newValue);
|
||||
} else {
|
||||
setInternalValue(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSearch = () => {
|
||||
if (onChange) {
|
||||
onChange('');
|
||||
} else {
|
||||
setInternalValue('');
|
||||
}
|
||||
if (inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Escape') {
|
||||
clearSearch();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-full">
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
placeholder="Suchen..."
|
||||
value={value}
|
||||
className={`w-full px-8 py-4 font-bold text-slate-900 bg-white rounded-2xl border transition-all focus:outline-none placeholder:text-slate-300 ${
|
||||
size === 'large' ? 'text-2xl md:text-4xl py-6 rounded-3xl' : 'text-lg'
|
||||
} ${
|
||||
isFocused ? 'border-slate-400' : 'border-slate-200'
|
||||
}`}
|
||||
onChange={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
aria-label="Search blog posts"
|
||||
/>
|
||||
|
||||
{value && (
|
||||
<button
|
||||
onClick={clearSearch}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1 border border-slate-200 rounded-full text-[10px] font-bold uppercase tracking-widest text-slate-400 hover:text-slate-900 hover:border-slate-400 hover:bg-slate-50 transition-all duration-500 ease-[cubic-bezier(0.23,1,0.32,1)] hover:-translate-y-[calc(50%+2px)] hover:shadow-lg hover:shadow-slate-100"
|
||||
aria-label="Clear search"
|
||||
type="button"
|
||||
>
|
||||
Löschen
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user