inputs and data views keyed: search, select, tabs, state blocks, data table
The DataTable's pagination carries the only interpolated strings in this area
('Showing page {{current}} of {{total}}', '{{size}} / page'). No plural forms:
nothing here is a count-varying noun, so inventing one would only add a key
nobody uses.
DataTable.test.tsx now renders inside an I18nProvider with the real en-GB
catalog, so it still asserts the English a user sees rather than a dot-path.
Left/right utilities became logical (ms-/me-/ps-/pe-/start-) per D3.
This commit is contained in:
@@ -3,6 +3,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Search as SearchIcon, X as ClearIcon } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { cn } from 'helpers/cn';
|
||||
import { useTranslation } from '@/components/locales';
|
||||
|
||||
type SearchInputProps = {
|
||||
className?: string;
|
||||
@@ -12,7 +13,8 @@ type SearchInputProps = {
|
||||
};
|
||||
|
||||
export function SearchInput(props: SearchInputProps) {
|
||||
const { className, handleSearch, value = '', placeholder = 'Search' } = props;
|
||||
const { t } = useTranslation();
|
||||
const { className, handleSearch, value = '', placeholder = t('search.placeholder') } = props;
|
||||
const [isMobileSearchOpen, setIsMobileSearchOpen] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
|
||||
@@ -22,14 +24,14 @@ export function SearchInput(props: SearchInputProps) {
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
aria-label={isMobileSearchOpen ? 'Close search' : 'Open search'}
|
||||
aria-label={isMobileSearchOpen ? t('search.close') : t('search.open')}
|
||||
onClick={() => {
|
||||
setIsMobileSearchOpen((v) => !v);
|
||||
if (!isMobileSearchOpen) {
|
||||
setTimeout(() => inputRef.current?.focus(), 160);
|
||||
}
|
||||
}}
|
||||
className="ml-auto shrink-0 md:hidden"
|
||||
className="ms-auto shrink-0 md:hidden"
|
||||
>
|
||||
{isMobileSearchOpen ? <ClearIcon className="h-5 w-5" /> : <SearchIcon className="h-5 w-5" />}
|
||||
</Button>
|
||||
@@ -37,7 +39,7 @@ export function SearchInput(props: SearchInputProps) {
|
||||
className={cn(
|
||||
'relative w-fit max-w-lg',
|
||||
isMobileSearchOpen
|
||||
? 'absolute left-0 mt-4 block w-full md:relative md:mt-0 md:w-fit'
|
||||
? 'absolute start-0 mt-4 block w-full md:relative md:mt-0 md:w-fit'
|
||||
: 'hidden w-auto md:block',
|
||||
className,
|
||||
)}
|
||||
@@ -45,14 +47,14 @@ export function SearchInput(props: SearchInputProps) {
|
||||
{!!value ? (
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Clear search"
|
||||
aria-label={t('search.clear')}
|
||||
onClick={() => handleSearch('')}
|
||||
className="absolute left-3 top-1/2 -translate-y-1/2 z-10 rounded p-1 hover:bg-muted"
|
||||
className="absolute start-3 top-1/2 -translate-y-1/2 z-10 rounded p-1 hover:bg-muted"
|
||||
>
|
||||
<ClearIcon className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
) : (
|
||||
<span className="pointer-events-none absolute left-3 top-1/2 z-10 -translate-y-1/2">
|
||||
<span className="pointer-events-none absolute start-3 top-1/2 z-10 -translate-y-1/2">
|
||||
<SearchIcon className="text-muted-foreground h-4 w-4" />
|
||||
</span>
|
||||
)}
|
||||
@@ -62,7 +64,7 @@ export function SearchInput(props: SearchInputProps) {
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={(ev) => handleSearch(ev.target.value)}
|
||||
className="pl-10 pr-4 text-sm"
|
||||
className="ps-10 pe-4 text-sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user