@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
102 lines (101 loc) • 4.06 kB
JavaScript
"use client";
import ActionIcon from "../ActionIcon/ActionIcon.mjs";
import { useTranslation } from "../../i18n/useTranslation.mjs";
import form_default from "../../i18n/resources/en/form.mjs";
import { useFormKitContext } from "./context.mjs";
import { getInternals } from "./instance.mjs";
import { listStyles } from "./style.mjs";
import { useStoreSelector } from "./useStoreSelector.mjs";
import FormField from "./Field.mjs";
import { cloneElement, memo, useCallback, useRef } from "react";
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
import { PlusIcon, XIcon } from "lucide-react";
//#region src/base-ui/FormKit/List.tsx
let rowSeed = 0;
const nextRowKey = () => `row-${rowSeed += 1}`;
const createItem = (newItem) => typeof newItem === "function" ? newItem() : newItem && typeof newItem === "object" ? structuredClone(newItem) : newItem ?? {};
const labelFor = (column, index) => column.children.props["aria-label"] ?? (typeof column.title === "string" ? `${column.title} ${index + 1}` : void 0);
const FormList = memo(({ addText, children, columns, emptyText, name, newItem }) => {
const { form } = useFormKitContext();
const { engine } = getInternals(form);
const { t } = useTranslation(form_default);
const length = useStoreSelector(engine, (e) => {
const value = e.getValue(name);
return Array.isArray(value) ? value.length : 0;
});
const keysRef = useRef([]);
const keys = keysRef.current;
if (keys.length > length) keys.length = length;
while (keys.length < length) keys.push(nextRowKey());
const add = useCallback((item) => {
keysRef.current.push(nextRowKey());
engine.pushItem(name, item);
}, [engine, name]);
const remove = useCallback((index) => {
keysRef.current.splice(index, 1);
engine.removeItem(name, index);
}, [engine, name]);
const move = useCallback((from, to) => {
const [moved] = keysRef.current.splice(from, 1);
keysRef.current.splice(to, 0, moved);
engine.moveItem(name, from, to);
}, [engine, name]);
const fields = keys.map((key, index) => ({
index,
key,
name: `${name}.${index}`
}));
if (children || !columns) return /* @__PURE__ */ jsx(Fragment$1, { children: children?.({
add,
fields,
move,
remove
}) });
const gridTemplateColumns = `${columns.map((c) => `minmax(0, ${c.flex ?? 1}fr)`).join(" ")} 36px`;
const addButton = /* @__PURE__ */ jsxs("button", {
className: listStyles.add,
type: "button",
onClick: () => add(createItem(newItem)),
children: [/* @__PURE__ */ jsx(PlusIcon, { size: 14 }), addText ?? t("form.list.add")]
});
return /* @__PURE__ */ jsxs("div", {
className: listStyles.table,
children: [/* @__PURE__ */ jsx("div", {
className: listStyles.head,
style: { gridTemplateColumns },
children: columns.map((column) => /* @__PURE__ */ jsx("div", {
className: listStyles.title,
children: column.title
}, column.name))
}), fields.length === 0 ? /* @__PURE__ */ jsxs("div", {
className: listStyles.empty,
children: [/* @__PURE__ */ jsx("span", { children: emptyText ?? t("form.list.empty") }), addButton]
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [fields.map((field) => /* @__PURE__ */ jsxs("div", {
className: listStyles.row,
style: { gridTemplateColumns },
children: [columns.map((column) => /* @__PURE__ */ jsx("div", {
className: listStyles.cell,
children: /* @__PURE__ */ jsx(FormField, {
bare: true,
name: `${field.name}.${column.name}`,
required: column.required,
validate: column.validate,
children: cloneElement(column.children, { "aria-label": labelFor(column, field.index) })
})
}, column.name)), /* @__PURE__ */ jsx(ActionIcon, {
"aria-label": t("form.list.remove"),
icon: XIcon,
size: "small",
title: t("form.list.remove"),
onClick: () => remove(field.index)
})]
}, field.key)), /* @__PURE__ */ jsx("div", {
className: listStyles.foot,
children: addButton
})] })]
});
});
FormList.displayName = "FormList";
//#endregion
export { FormList as default };
//# sourceMappingURL=List.mjs.map