prod setup
This commit is contained in:
95
src/components/admin/fields/ChipsField.tsx
Normal file
95
src/components/admin/fields/ChipsField.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { useState, type KeyboardEvent } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { FieldError } from './FieldError';
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
label: string;
|
||||
hint?: string;
|
||||
placeholder?: string;
|
||||
};
|
||||
|
||||
export function ChipsField({ name, label, hint, placeholder = 'Ajouter puis Entrée…' }: Props) {
|
||||
const { control } = useFormContext();
|
||||
const [draft, setDraft] = useState('');
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
const items: string[] = Array.isArray(field.value) ? field.value : [];
|
||||
|
||||
const add = () => {
|
||||
const value = draft.trim();
|
||||
if (!value) return;
|
||||
if (items.includes(value)) {
|
||||
setDraft('');
|
||||
return;
|
||||
}
|
||||
field.onChange([...items, value]);
|
||||
setDraft('');
|
||||
};
|
||||
|
||||
const remove = (idx: number) => {
|
||||
field.onChange(items.filter((_, i) => i !== idx));
|
||||
};
|
||||
|
||||
const onKey = (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' || e.key === ',') {
|
||||
e.preventDefault();
|
||||
add();
|
||||
} else if (e.key === 'Backspace' && draft === '' && items.length > 0) {
|
||||
field.onChange(items.slice(0, -1));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<span className="block font-mono text-xs uppercase tracking-widest text-[color:var(--color-cyan)] mb-1.5">
|
||||
{label}
|
||||
</span>
|
||||
{hint && (
|
||||
<span className="block font-mono text-[10px] text-[color:var(--color-text-dim)] mb-1.5">
|
||||
{hint}
|
||||
</span>
|
||||
)}
|
||||
<div className="flex flex-wrap gap-2 mb-2">
|
||||
{items.map((item, idx) => (
|
||||
<span
|
||||
key={`${item}-${idx}`}
|
||||
className="chip group cursor-pointer"
|
||||
onClick={() => remove(idx)}
|
||||
title="Cliquer pour retirer"
|
||||
>
|
||||
{item}
|
||||
<span className="ml-1 opacity-50 group-hover:opacity-100 group-hover:text-[color:var(--color-magenta)]">
|
||||
×
|
||||
</span>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="text"
|
||||
value={draft}
|
||||
onChange={(e) => setDraft(e.target.value)}
|
||||
onKeyDown={onKey}
|
||||
placeholder={placeholder}
|
||||
className="flex-1 bg-[color:var(--color-surface-2)] border border-[color:var(--color-border)] focus:border-[color:var(--color-cyan)] focus:outline-none p-2 rounded text-sm font-mono text-[color:var(--color-text)] transition-colors"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={add}
|
||||
className="px-3 py-1.5 font-mono text-xs uppercase tracking-widest border border-[color:var(--color-cyan)] text-[color:var(--color-cyan)] rounded hover:bg-[color:var(--color-cyan)] hover:text-[color:var(--color-bg)] transition-colors"
|
||||
>
|
||||
+ add
|
||||
</button>
|
||||
</div>
|
||||
<FieldError name={name} />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user