prod setup
This commit is contained in:
99
src/components/admin/fields/ArrayEditor.tsx
Normal file
99
src/components/admin/fields/ArrayEditor.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
||||
|
||||
type Props<T> = {
|
||||
name: string;
|
||||
label: string;
|
||||
hint?: string;
|
||||
newItem: () => T;
|
||||
itemLabel?: (index: number) => string;
|
||||
children: (params: { index: number; namePrefix: string }) => ReactNode;
|
||||
};
|
||||
|
||||
export function ArrayEditor<T>({
|
||||
name,
|
||||
label,
|
||||
hint,
|
||||
newItem,
|
||||
itemLabel,
|
||||
children,
|
||||
}: Props<T>) {
|
||||
const { control } = useFormContext();
|
||||
const { fields, append, remove, move } = useFieldArray({ control, name });
|
||||
|
||||
return (
|
||||
<section className="space-y-3">
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h3 className="font-display uppercase tracking-widest text-base text-[color:var(--color-acid)] glow-text-acid">
|
||||
{label}
|
||||
</h3>
|
||||
{hint && (
|
||||
<p className="font-mono text-[10px] text-[color:var(--color-text-dim)] mt-0.5">
|
||||
{hint}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => append(newItem() as never)}
|
||||
className="px-3 py-1.5 font-mono text-xs uppercase tracking-widest text-[color:var(--color-bg)] bg-[color:var(--color-acid)] rounded hover:opacity-90 transition-opacity"
|
||||
>
|
||||
+ ajouter
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{fields.length === 0 && (
|
||||
<div className="font-mono text-xs text-[color:var(--color-text-dim)] italic border border-dashed border-[color:var(--color-border)] rounded p-4 text-center">
|
||||
(aucun élément — clique « ajouter » pour commencer)
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ol className="space-y-3">
|
||||
{fields.map((field, idx) => (
|
||||
<li
|
||||
key={field.id}
|
||||
className="border border-[color:var(--color-border)] rounded bg-[color:var(--color-surface)] p-4 relative"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-3 pb-2 border-b border-[color:var(--color-border)]">
|
||||
<span className="font-mono text-[10px] uppercase tracking-widest text-[color:var(--color-cyan)]">
|
||||
{itemLabel ? itemLabel(idx) : `# ${String(idx + 1).padStart(2, '0')}`}
|
||||
</span>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => idx > 0 && move(idx, idx - 1)}
|
||||
disabled={idx === 0}
|
||||
className="px-2 py-1 font-mono text-xs text-[color:var(--color-text-muted)] hover:text-[color:var(--color-cyan)] disabled:opacity-30"
|
||||
aria-label="monter"
|
||||
>
|
||||
↑
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => idx < fields.length - 1 && move(idx, idx + 1)}
|
||||
disabled={idx === fields.length - 1}
|
||||
className="px-2 py-1 font-mono text-xs text-[color:var(--color-text-muted)] hover:text-[color:var(--color-cyan)] disabled:opacity-30"
|
||||
aria-label="descendre"
|
||||
>
|
||||
↓
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => remove(idx)}
|
||||
className="px-2 py-1 font-mono text-xs text-[color:var(--color-magenta)] hover:text-[color:var(--color-bg)] hover:bg-[color:var(--color-magenta)] rounded transition-colors"
|
||||
aria-label="supprimer"
|
||||
>
|
||||
supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{children({ index: idx, namePrefix: `${name}.${idx}` })}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user