Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/modules/formWidget/builder/FieldPalette.tsx
T

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>
);
}