80 lines
2.7 KiB
TypeScript
80 lines
2.7 KiB
TypeScript
/**
|
|
* Left-rail field-type palette for the form-widget drag-drop builder
|
|
* (Phase 05 #58 follow-up). Each tile supports two interaction modes:
|
|
*
|
|
* 1. Click → calls `onAdd(type)`, which the parent <FormBuilder/>
|
|
* appends to the canvas using the palette's default field shape.
|
|
* 2. Drag → exposes a `@dnd-kit` draggable so the user can drop the
|
|
* tile directly onto a position on the sortable canvas.
|
|
*
|
|
* Keeping click+drag both functional is an explicit a11y requirement —
|
|
* the keyboard sensor on the canvas handles reorder, and the click
|
|
* path is the no-pointer "add field" affordance.
|
|
*/
|
|
import type * as React from "react";
|
|
import { useDraggable } from "@dnd-kit/core";
|
|
import { useTranslation } from "react-i18next";
|
|
import { paletteItems } from "./palette";
|
|
import type { BuilderFieldType } from "./types";
|
|
|
|
export type FieldPaletteProps = {
|
|
onAdd: (type: BuilderFieldType) => void;
|
|
};
|
|
|
|
function DraggablePaletteTile({
|
|
type,
|
|
labelKey,
|
|
helpKey,
|
|
onAdd,
|
|
}: {
|
|
type: BuilderFieldType;
|
|
labelKey: string;
|
|
helpKey: string;
|
|
onAdd: (type: BuilderFieldType) => void;
|
|
}): React.JSX.Element {
|
|
const { t } = useTranslation();
|
|
const label = t(labelKey);
|
|
const help = t(helpKey);
|
|
const { attributes, listeners, setNodeRef, isDragging } = useDraggable({
|
|
id: `palette:${type}`,
|
|
data: { source: "palette", type },
|
|
});
|
|
return (
|
|
<button
|
|
ref={setNodeRef}
|
|
type="button"
|
|
className={`text-left border rounded p-2 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 ${isDragging ? "opacity-50" : ""}`}
|
|
aria-label={t("settings.forms.builder.palette.addField", { label })}
|
|
onClick={() => onAdd(type)}
|
|
{...listeners}
|
|
{...attributes}
|
|
>
|
|
<div className="font-medium text-sm">{label}</div>
|
|
<div className="text-xs text-gray-500">{help}</div>
|
|
</button>
|
|
);
|
|
}
|
|
|
|
export function FieldPalette({ onAdd }: FieldPaletteProps): React.JSX.Element {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<aside className="w-full shrink-0 border rounded p-3 space-y-2 bg-white lg:w-56" aria-label={t("settings.forms.builder.palette.ariaLabel")}>
|
|
<h3 className="text-sm font-semibold uppercase tracking-wide text-gray-600">{t("settings.forms.builder.palette.title")}</h3>
|
|
<div className="grid gap-2">
|
|
{paletteItems.map((item) => (
|
|
<DraggablePaletteTile
|
|
key={item.type}
|
|
type={item.type}
|
|
labelKey={item.labelKey}
|
|
helpKey={item.helpKey}
|
|
onAdd={onAdd}
|
|
/>
|
|
))}
|
|
</div>
|
|
<p className="text-[11px] text-gray-500 leading-snug pt-2 border-t">
|
|
{t("settings.forms.builder.palette.instructions")}
|
|
</p>
|
|
</aside>
|
|
);
|
|
}
|