@protobase/ui 0.21.0 → 0.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/create/create-page.js +12 -4
- package/dist/app/create/create-record-form.js +3 -1
- package/dist/app/create/form.d.ts +3 -2
- package/dist/app/create/form.js +3 -1
- package/dist/app/field-editor.js +5 -1
- package/dist/app/field-errors.js +70 -0
- package/dist/app/hand-order/hand-order.js +21 -0
- package/dist/app/hand-order/hand-sorted-list.js +98 -0
- package/dist/app/hand-order/sort-button.js +20 -0
- package/dist/app/hand-order/use-hand-order.js +105 -0
- package/dist/app/list-page.js +34 -9
- package/dist/app/pages/blocks/modal-form.js +13 -4
- package/dist/app/record-page.js +9 -2
- package/dist/app/related-section.js +37 -8
- package/dist/app/use-focus-first-invalid.js +16 -0
- package/dist/app/use-live-save.js +4 -2
- package/dist/app/use-record-editor.js +11 -3
- package/dist/data-grid/data-grid.js +49 -36
- package/dist/data-grid/grid-header.js +2 -1
- package/dist/data-grid/grid-row.js +16 -4
- package/dist/data-grid/row-reorder.js +68 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/package.js +1 -1
- package/dist/primitives/checkbox.js +2 -1
- package/dist/primitives/field-control.d.ts +19 -0
- package/dist/primitives/field-control.js +21 -0
- package/dist/primitives/input.js +17 -11
- package/dist/primitives/switch.js +15 -9
- package/dist/primitives/textarea.js +4 -0
- package/dist/record-view/field.d.ts +7 -1
- package/dist/record-view/field.js +83 -56
- package/dist/shell/assets/app-BnuJWZWd.js +69 -0
- package/dist/shell/assets/{index-iXIDNHEm.js → index-7lQIOMrK.js} +1 -1
- package/dist/shell/assets/{index-Dc6xgHsz.css → index-B48ty05p.css} +1 -1
- package/dist/shell/assets/{maplibre-DxWag0Wp.js → maplibre-DEejTZ7I.js} +2 -2
- package/dist/shell/assets/{markdown-content-Bco0XDqw.js → markdown-content-ZzP9i4q4.js} +1 -1
- package/dist/shell/assets/{record-map-Ca1fz02Q.js → record-map-B-zsmXL2.js} +1 -1
- package/dist/shell/assets/{records-map-JooppeKw.js → records-map-Lj1MQgsW.js} +1 -1
- package/dist/shell/assets/shared-protobase-ui-Bnd7T1j4.js +1 -0
- package/dist/shell/index.html +4 -4
- package/dist/shell/protobase.shell.json +1 -1
- package/package.json +4 -4
- package/dist/shell/assets/app-BBx2Bkop.js +0 -69
- package/dist/shell/assets/shared-protobase-ui-BZs06oKf.js +0 -1
package/dist/app/record-page.js
CHANGED
|
@@ -34,6 +34,8 @@ import { can, fieldAccess } from "./permissions.js";
|
|
|
34
34
|
import { toneFor } from "../live/columns-from-model.js";
|
|
35
35
|
import { ValueBadge } from "./value-badge.js";
|
|
36
36
|
import { FieldValue } from "./pages/field-value.js";
|
|
37
|
+
import { fieldMessage } from "./field-errors.js";
|
|
38
|
+
import { useFocusFirstInvalid } from "./use-focus-first-invalid.js";
|
|
37
39
|
import { namedIcon } from "./named-icon.js";
|
|
38
40
|
import { useDeleteFlow } from "./delete/use-delete-flow.js";
|
|
39
41
|
import { useRestore } from "./delete/use-restore.js";
|
|
@@ -130,6 +132,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
130
132
|
});
|
|
131
133
|
const queryClient = useQueryClient();
|
|
132
134
|
const client = useClient();
|
|
135
|
+
const fieldsRef = useFocusFirstInvalid(editor.rejections);
|
|
133
136
|
const permissions = usePermissions(model.name);
|
|
134
137
|
const restore = useRestore(model);
|
|
135
138
|
const canUpdate = can(permissions, "update") && stored.permissions?.update !== false;
|
|
@@ -192,11 +195,14 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
192
195
|
if (!field || access === "hidden") return null;
|
|
193
196
|
const conflict = editor.conflicts[name];
|
|
194
197
|
const target = pickTarget(name);
|
|
198
|
+
const label = hint(name)?.label ?? humanize(name);
|
|
199
|
+
const problem = editor.errors[name];
|
|
195
200
|
return /* @__PURE__ */ jsx(Field, {
|
|
196
|
-
|
|
201
|
+
name,
|
|
202
|
+
label,
|
|
197
203
|
required: shown === "form" && access !== "read" && isRequiredOnEdit(field),
|
|
198
204
|
help: fieldHelp(hint(name), view, editing),
|
|
199
|
-
error:
|
|
205
|
+
error: problem && fieldMessage(field, label, problem),
|
|
200
206
|
wide: Boolean(hint(name)?.multiline || hint(name)?.markdown),
|
|
201
207
|
conflict: conflict === void 0 ? void 0 : {
|
|
202
208
|
user: {
|
|
@@ -323,6 +329,7 @@ const LoadedRecord = ({ model, recordKey, stored, preview }) => {
|
|
|
323
329
|
})
|
|
324
330
|
] });
|
|
325
331
|
return /* @__PURE__ */ jsxs("div", {
|
|
332
|
+
ref: fieldsRef,
|
|
326
333
|
className: "flex flex-1 flex-col",
|
|
327
334
|
inert: preview,
|
|
328
335
|
children: [
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { Skeleton } from "../primitives/skeleton.js";
|
|
2
2
|
import { keys } from "../data/query-keys.js";
|
|
3
3
|
import { useClient } from "../data/api-provider.js";
|
|
4
|
-
import { useAdminMeta } from "./meta-gate.js";
|
|
4
|
+
import { useAdminMeta, usePermissions } from "./meta-gate.js";
|
|
5
5
|
import { Section } from "../record-view/section.js";
|
|
6
6
|
import { ErrorBanner } from "./error-banner.js";
|
|
7
7
|
import { listColumnNames } from "../live/columns-from-model.js";
|
|
8
8
|
import { EmbeddedDisplaySwitch, EmbeddedList } from "./embedded-list/embedded-list.js";
|
|
9
9
|
import { useEmbeddedDisplay } from "./embedded-list/use-embedded-display.js";
|
|
10
|
-
import {
|
|
10
|
+
import { relatedHandOrder } from "./hand-order/hand-order.js";
|
|
11
|
+
import { HandSortedList } from "./hand-order/hand-sorted-list.js";
|
|
12
|
+
import { SortButton } from "./hand-order/sort-button.js";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
15
|
import { useQuery } from "@tanstack/react-query";
|
|
12
16
|
import { createWhere, printFilter } from "@protobase/schema";
|
|
13
17
|
//#region src/app/related-section.tsx
|
|
@@ -67,7 +71,8 @@ const Empty = ({ text }) => /* @__PURE__ */ jsx("p", {
|
|
|
67
71
|
});
|
|
68
72
|
/**
|
|
69
73
|
* A record page section listing the first records of another resource that point at this record, directly or through
|
|
70
|
-
* a join resource, in the list page's table or display, with a link to them all in the list.
|
|
74
|
+
* a join resource, in the list page's table or display, with a link to them all in the list. When they are ordered by
|
|
75
|
+
* hand within this record, **Sort** shows them all in their order, to be moved by their drag handles.
|
|
71
76
|
*/
|
|
72
77
|
const RelatedSection = ({ item, recordKey, owner }) => {
|
|
73
78
|
const { resources, views } = useAdminMeta();
|
|
@@ -75,16 +80,40 @@ const RelatedSection = ({ item, recordKey, owner }) => {
|
|
|
75
80
|
const view = model && views[model.name];
|
|
76
81
|
const match = useMatch(item, recordKey);
|
|
77
82
|
const [display, pick] = useEmbeddedDisplay(view, `related:${owner}:${item.title}`, item.display);
|
|
83
|
+
const permissions = usePermissions(item.resource);
|
|
84
|
+
const [sorting, setSorting] = useState(false);
|
|
78
85
|
if (!model) return null;
|
|
79
86
|
const columns = item.columns ?? listColumnNames(model, view).filter((name) => name !== item.field);
|
|
80
|
-
|
|
87
|
+
const handOrder = relatedHandOrder(item, model, permissions);
|
|
88
|
+
const displaySwitch = /* @__PURE__ */ jsx(EmbeddedDisplaySwitch, {
|
|
89
|
+
view,
|
|
90
|
+
value: display,
|
|
91
|
+
onChange: pick
|
|
92
|
+
});
|
|
93
|
+
const actions = handOrder ? /* @__PURE__ */ jsxs("div", {
|
|
94
|
+
className: "flex items-center gap-2",
|
|
95
|
+
children: [!sorting && displaySwitch, /* @__PURE__ */ jsx(SortButton, {
|
|
96
|
+
size: "sm",
|
|
97
|
+
sorting,
|
|
98
|
+
onChange: setSorting
|
|
99
|
+
})]
|
|
100
|
+
}) : displaySwitch;
|
|
101
|
+
if (sorting && handOrder) return /* @__PURE__ */ jsx(Section, {
|
|
81
102
|
title: item.title,
|
|
82
103
|
help: item.help,
|
|
83
|
-
actions
|
|
104
|
+
actions,
|
|
105
|
+
children: /* @__PURE__ */ jsx(HandSortedList, {
|
|
106
|
+
model,
|
|
84
107
|
view,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
field: handOrder,
|
|
109
|
+
filter: printFilter(where$1.eq(item.field, recordKey)),
|
|
110
|
+
columns
|
|
111
|
+
})
|
|
112
|
+
});
|
|
113
|
+
return /* @__PURE__ */ jsx(Section, {
|
|
114
|
+
title: item.title,
|
|
115
|
+
help: item.help,
|
|
116
|
+
actions,
|
|
88
117
|
children: match.isPending ? /* @__PURE__ */ jsx(Loading, {}) : match.error ? /* @__PURE__ */ jsx(ErrorBanner, {
|
|
89
118
|
title: "Could not load",
|
|
90
119
|
error: match.error
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
//#region src/app/use-focus-first-invalid.ts
|
|
3
|
+
/**
|
|
4
|
+
* Puts focus on the first control marked invalid inside the element the ref is on, each time `rejections` counts
|
|
5
|
+
* another save the server turned down, once the form shows why.
|
|
6
|
+
*/
|
|
7
|
+
const useFocusFirstInvalid = (rejections) => {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (rejections === 0) return;
|
|
11
|
+
ref.current?.querySelector("[aria-invalid=\"true\"]")?.focus();
|
|
12
|
+
}, [rejections]);
|
|
13
|
+
return ref;
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { useFocusFirstInvalid };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useToast } from "../toasts/toast-provider.js";
|
|
2
|
+
import { saveFailureDescription } from "./field-errors.js";
|
|
2
3
|
import { useEffect, useRef, useState } from "react";
|
|
3
4
|
import { isApiError } from "@protobase/client";
|
|
4
5
|
//#region src/app/use-live-save.ts
|
|
@@ -35,14 +36,15 @@ const useLiveSave = (mode, subject) => {
|
|
|
35
36
|
} catch (error) {
|
|
36
37
|
if (!isApiError(error)) throw error;
|
|
37
38
|
const title = error.status === 412 ? "Changed by someone else" : "Could not save";
|
|
39
|
+
const description = saveFailureDescription(error);
|
|
38
40
|
if (id !== void 0) toast.update(id, {
|
|
39
41
|
state: "error",
|
|
40
42
|
title,
|
|
41
|
-
description
|
|
43
|
+
description
|
|
42
44
|
});
|
|
43
45
|
if (mode === "button") {
|
|
44
46
|
setPhase("idle");
|
|
45
|
-
setMessage(`${title}: ${
|
|
47
|
+
setMessage(`${title}: ${description}`);
|
|
46
48
|
}
|
|
47
49
|
return false;
|
|
48
50
|
}
|
|
@@ -2,6 +2,7 @@ import { keys } from "../data/query-keys.js";
|
|
|
2
2
|
import { useClient } from "../data/api-provider.js";
|
|
3
3
|
import { differs, toDraft, toPatchValue } from "../live/field-values.js";
|
|
4
4
|
import { displayValue } from "../live/display-value.js";
|
|
5
|
+
import { fieldErrors } from "./field-errors.js";
|
|
5
6
|
import { useLiveSave } from "./use-live-save.js";
|
|
6
7
|
import { readDraft, writeDraft } from "./draft-storage.js";
|
|
7
8
|
import { isConflict, useUpdateRecord } from "../data/use-update-record.js";
|
|
@@ -27,6 +28,7 @@ const useRecordEditor = ({ model, stored, recordKey, mode, subject, draftKey, li
|
|
|
27
28
|
const [linesConflict, setLinesConflict] = useState(false);
|
|
28
29
|
const [conflicts, setConflicts] = useState({});
|
|
29
30
|
const [errors, setErrors] = useState({});
|
|
31
|
+
const [rejections, setRejections] = useState(0);
|
|
30
32
|
const changed = Object.entries(draft).filter(([name, entry]) => differs(model.fields[name], entry, stored.record[name]));
|
|
31
33
|
const fieldsDirty = changed.length > 0;
|
|
32
34
|
const dirty = fieldsDirty || Boolean(lines?.dirty);
|
|
@@ -74,7 +76,11 @@ const useRecordEditor = ({ model, stored, recordKey, mode, subject, draftKey, li
|
|
|
74
76
|
setConflicts(theirs);
|
|
75
77
|
};
|
|
76
78
|
const patchOf = () => Object.fromEntries(changed.map(([name, entry]) => [name, toPatchValue(model.fields[name], entry)]));
|
|
77
|
-
const
|
|
79
|
+
const reject = (error) => {
|
|
80
|
+
const found = fieldErrors(error);
|
|
81
|
+
setErrors(found);
|
|
82
|
+
if (Object.keys(found).length > 0) setRejections((count) => count + 1);
|
|
83
|
+
};
|
|
78
84
|
const saveRecordOnly = async () => {
|
|
79
85
|
try {
|
|
80
86
|
await update.mutateAsync({
|
|
@@ -83,7 +89,7 @@ const useRecordEditor = ({ model, stored, recordKey, mode, subject, draftKey, li
|
|
|
83
89
|
});
|
|
84
90
|
} catch (error) {
|
|
85
91
|
if (isConflict(error)) await adoptLatest();
|
|
86
|
-
else if (isApiError(error))
|
|
92
|
+
else if (isApiError(error)) reject(error);
|
|
87
93
|
throw error;
|
|
88
94
|
}
|
|
89
95
|
};
|
|
@@ -102,7 +108,7 @@ const useRecordEditor = ({ model, stored, recordKey, mode, subject, draftKey, li
|
|
|
102
108
|
await adoptLatest();
|
|
103
109
|
await queryClient.invalidateQueries({ queryKey: ["list"] });
|
|
104
110
|
setLinesConflict(true);
|
|
105
|
-
} else if (isApiError(error))
|
|
111
|
+
} else if (isApiError(error)) reject(error);
|
|
106
112
|
throw isApiError(error) ? nameFailingOperation(error, ops) : error;
|
|
107
113
|
}
|
|
108
114
|
await Promise.all([
|
|
@@ -146,7 +152,9 @@ const useRecordEditor = ({ model, stored, recordKey, mode, subject, draftKey, li
|
|
|
146
152
|
phase: feedback.phase,
|
|
147
153
|
message: feedback.message,
|
|
148
154
|
conflicts,
|
|
155
|
+
/** The server's problem with each field, from the last save. */
|
|
149
156
|
errors,
|
|
157
|
+
rejections,
|
|
150
158
|
keepMine: (name) => setConflicts((current) => without(current, name)),
|
|
151
159
|
useTheirs: (name) => {
|
|
152
160
|
setConflicts((current) => without(current, name));
|
|
@@ -11,12 +11,14 @@ import { DataRow, SkeletonRow } from "./grid-row.js";
|
|
|
11
11
|
import { usePagedRows } from "./use-paged-rows.js";
|
|
12
12
|
import { useVirtualRows } from "./use-virtual-rows.js";
|
|
13
13
|
import { useCellEditing } from "./use-cell-editing.js";
|
|
14
|
+
import { ReorderRows, SortableRow } from "./row-reorder.js";
|
|
14
15
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
15
16
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
17
|
import { useTable } from "@tanstack/react-table";
|
|
17
18
|
//#region src/data-grid/data-grid.tsx
|
|
18
19
|
const headerHeight = 32;
|
|
19
|
-
const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0, onScrollIndex, latencyMs = 150, sort: controlledSort, onSortChange, onOpenRow, rowActions, cellEditor, rowPatches, bulkActions, pinKey, defaultPinned, rowNumbers = true, compact = false, selectable = true, fit = false, scroll = "own", className }) => {
|
|
20
|
+
const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0, onScrollIndex, latencyMs = 150, sort: controlledSort, onSortChange, onOpenRow, rowActions, cellEditor, rowPatches, bulkActions, pinKey, defaultPinned, rowNumbers = true, compact = false, selectable: selecting = true, reorder, fit = false, scroll = "own", className }) => {
|
|
21
|
+
const selectable = selecting && !reorder;
|
|
20
22
|
const rowHeight = compact ? 28 : 36;
|
|
21
23
|
const [pinned, setPinned] = usePinnedColumns(pinKey, defaultPinned);
|
|
22
24
|
const columns = useMemo(() => {
|
|
@@ -166,6 +168,46 @@ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0,
|
|
|
166
168
|
})
|
|
167
169
|
})
|
|
168
170
|
});
|
|
171
|
+
const rowsInView = loaded.map(({ index, offset, row }) => {
|
|
172
|
+
const frame = {
|
|
173
|
+
specs: columns,
|
|
174
|
+
gutter,
|
|
175
|
+
index,
|
|
176
|
+
offset,
|
|
177
|
+
height: rowHeight,
|
|
178
|
+
selectable,
|
|
179
|
+
trailing,
|
|
180
|
+
minScale,
|
|
181
|
+
scrolled: horizontal.scrolled,
|
|
182
|
+
pins,
|
|
183
|
+
lastPinned
|
|
184
|
+
};
|
|
185
|
+
const tableRow = row ? tableRows.get(row.id) : void 0;
|
|
186
|
+
if (!tableRow || !row) return /* @__PURE__ */ jsx(SkeletonRow, { ...frame }, `skeleton-${index}`);
|
|
187
|
+
const actions = trailing ? availableActions(rowActions, row) : [];
|
|
188
|
+
const props = {
|
|
189
|
+
...frame,
|
|
190
|
+
selected: tableRow.getIsSelected(),
|
|
191
|
+
onToggle: () => tableRow.toggleSelected(),
|
|
192
|
+
onOpen: onOpenRow ? () => onOpenRow(row) : void 0,
|
|
193
|
+
menu: actions.length > 0 ? /* @__PURE__ */ jsx(RowMenu, {
|
|
194
|
+
row,
|
|
195
|
+
actions
|
|
196
|
+
}) : void 0,
|
|
197
|
+
cells: tableRow.getAllCells().map((cell) => {
|
|
198
|
+
const content = /* @__PURE__ */ jsx(table.FlexRender, { cell });
|
|
199
|
+
return {
|
|
200
|
+
spec: columns.find((spec) => spec.id === cell.column.id),
|
|
201
|
+
...cellEditing ? cellEditing.cell(index, row, cell.column.id, content) : { content }
|
|
202
|
+
};
|
|
203
|
+
})
|
|
204
|
+
};
|
|
205
|
+
return reorder ? /* @__PURE__ */ jsx(SortableRow, {
|
|
206
|
+
...props,
|
|
207
|
+
row,
|
|
208
|
+
label: reorder.label(row)
|
|
209
|
+
}, row.id) : /* @__PURE__ */ jsx(DataRow, { ...props }, row.id);
|
|
210
|
+
});
|
|
169
211
|
const body = /* @__PURE__ */ jsx("div", {
|
|
170
212
|
ref: horizontal.attach,
|
|
171
213
|
onScroll: onBodyScroll,
|
|
@@ -177,41 +219,12 @@ const DataGrid = ({ columns: given, source, naturalSort, initialScrollIndex = 0,
|
|
|
177
219
|
minWidth,
|
|
178
220
|
height: geometry.contentHeight
|
|
179
221
|
},
|
|
180
|
-
children:
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
height: rowHeight,
|
|
187
|
-
selectable,
|
|
188
|
-
trailing,
|
|
189
|
-
minScale,
|
|
190
|
-
scrolled: horizontal.scrolled,
|
|
191
|
-
pins,
|
|
192
|
-
lastPinned
|
|
193
|
-
};
|
|
194
|
-
const tableRow = row ? tableRows.get(row.id) : void 0;
|
|
195
|
-
if (!tableRow) return /* @__PURE__ */ jsx(SkeletonRow, { ...frame }, `skeleton-${index}`);
|
|
196
|
-
const actions = trailing && row ? availableActions(rowActions, row) : [];
|
|
197
|
-
return /* @__PURE__ */ jsx(DataRow, {
|
|
198
|
-
...frame,
|
|
199
|
-
selected: tableRow.getIsSelected(),
|
|
200
|
-
onToggle: () => tableRow.toggleSelected(),
|
|
201
|
-
onOpen: onOpenRow && row ? () => onOpenRow(row) : void 0,
|
|
202
|
-
menu: row && actions.length > 0 ? /* @__PURE__ */ jsx(RowMenu, {
|
|
203
|
-
row,
|
|
204
|
-
actions
|
|
205
|
-
}) : void 0,
|
|
206
|
-
cells: tableRow.getAllCells().map((cell) => {
|
|
207
|
-
const content = /* @__PURE__ */ jsx(table.FlexRender, { cell });
|
|
208
|
-
return {
|
|
209
|
-
spec: columns.find((spec) => spec.id === cell.column.id),
|
|
210
|
-
...cellEditing && row ? cellEditing.cell(index, row, cell.column.id, content) : { content }
|
|
211
|
-
};
|
|
212
|
-
})
|
|
213
|
-
}, row?.id);
|
|
214
|
-
})
|
|
222
|
+
children: reorder ? /* @__PURE__ */ jsx(ReorderRows, {
|
|
223
|
+
ids: Array.from({ length: total }, (_, index) => patchedRow(index)?.id ?? `missing-${index}`),
|
|
224
|
+
rowAt: patchedRow,
|
|
225
|
+
reorder,
|
|
226
|
+
children: rowsInView
|
|
227
|
+
}) : rowsInView
|
|
215
228
|
})
|
|
216
229
|
});
|
|
217
230
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -22,7 +22,8 @@ const GridHeader = ({ cells, specs, gutter, selectedCount, selectable, trailing,
|
|
|
22
22
|
children: [selectable && /* @__PURE__ */ jsx(Checkbox, {
|
|
23
23
|
checked: false,
|
|
24
24
|
indeterminate: selectedCount > 0,
|
|
25
|
-
label: "Clear selection",
|
|
25
|
+
label: selectedCount > 0 ? "Clear selection" : "No rows selected",
|
|
26
|
+
disabled: selectedCount === 0,
|
|
26
27
|
onChange: onClearSelection,
|
|
27
28
|
className: cn(selectedCount === 0 && "opacity-60")
|
|
28
29
|
}), gutter > 40 && /* @__PURE__ */ jsx("span", {
|
|
@@ -33,13 +33,25 @@ const Gutter = ({ width, index, selectable, selected, onToggle, edge }) => /* @_
|
|
|
33
33
|
children: formatInt(index + 1)
|
|
34
34
|
})]
|
|
35
35
|
});
|
|
36
|
-
const
|
|
36
|
+
const dragStyle = (frame, dragY, transition) => ({
|
|
37
|
+
gridTemplateColumns: gridTemplate(frame.specs, frame.gutter, frame.trailing, frame.minScale),
|
|
38
|
+
height: frame.height,
|
|
39
|
+
top: frame.offset,
|
|
40
|
+
transform: dragY === 0 ? void 0 : `translateY(${dragY}px)`,
|
|
41
|
+
transition
|
|
42
|
+
});
|
|
43
|
+
const DataRow = ({ cells, selected, onToggle, onOpen, menu, handle, rowRef, dragY, transition, dragging, ...frame }) => /* @__PURE__ */ jsxs("div", {
|
|
44
|
+
ref: rowRef,
|
|
37
45
|
role: "row",
|
|
38
46
|
onClick: onOpen,
|
|
39
|
-
className: cn("absolute inset-x-0 top-0 grid items-center border-b", selected ? "bg-primary-soft" : "bg-background hover:bg-surface", onOpen && "cursor-pointer"),
|
|
40
|
-
style: frameStyle(frame),
|
|
47
|
+
className: cn("absolute inset-x-0 top-0 grid items-center border-b", selected ? "bg-primary-soft" : "bg-background hover:bg-surface", onOpen && "cursor-pointer", dragging && "z-30 bg-surface shadow-pop"),
|
|
48
|
+
style: dragY === void 0 ? frameStyle(frame) : dragStyle(frame, dragY, transition),
|
|
41
49
|
children: [
|
|
42
|
-
/* @__PURE__ */ jsx(
|
|
50
|
+
handle ? /* @__PURE__ */ jsx("div", {
|
|
51
|
+
className: cn("sticky left-0 z-10 flex h-full items-center bg-inherit pl-1.5", frame.scrolled && !frame.lastPinned && "shadow-[1px_0_0_var(--border),4px_0_6px_-2px_rgb(0_0_0/0.12)]"),
|
|
52
|
+
style: { width: frame.gutter },
|
|
53
|
+
children: handle
|
|
54
|
+
}) : /* @__PURE__ */ jsx(Gutter, {
|
|
43
55
|
width: frame.gutter,
|
|
44
56
|
index: frame.index,
|
|
45
57
|
selectable: frame.selectable,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { DataRow } from "./grid-row.js";
|
|
2
|
+
import { GripVertical } from "lucide-react";
|
|
3
|
+
import { jsx } from "react/jsx-runtime";
|
|
4
|
+
import { DndContext, KeyboardSensor, MouseSensor, TouchSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
5
|
+
import { SortableContext, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
6
|
+
//#region src/data-grid/row-reorder.tsx
|
|
7
|
+
const instructions = "To move a row, press Space on its handle, move it with the up and down arrow keys, and press Space again to drop it there. Escape puts it back.";
|
|
8
|
+
/** The rows of a grid that are moved by hand, `ids` in their order, each loaded or not, so the drop's index is the row's place in the whole list. */
|
|
9
|
+
const ReorderRows = ({ ids, rowAt, reorder, children }) => {
|
|
10
|
+
const sensors = useSensors(useSensor(MouseSensor, { activationConstraint: { distance: 4 } }), useSensor(TouchSensor, { activationConstraint: {
|
|
11
|
+
delay: 250,
|
|
12
|
+
tolerance: 5
|
|
13
|
+
} }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
|
|
14
|
+
const place = (id) => ids.indexOf(String(id));
|
|
15
|
+
const name = (id) => {
|
|
16
|
+
const row = rowAt(place(id));
|
|
17
|
+
return row ? reorder.label(row) : "row";
|
|
18
|
+
};
|
|
19
|
+
const announcements = {
|
|
20
|
+
onDragStart: ({ active }) => `Picked up ${name(active.id)}, row ${place(active.id) + 1} of ${ids.length}.`,
|
|
21
|
+
onDragOver: ({ active, over }) => over ? `${name(active.id)} is over row ${place(over.id) + 1} of ${ids.length}.` : void 0,
|
|
22
|
+
onDragEnd: ({ active, over }) => over ? `${name(active.id)} was dropped at row ${place(over.id) + 1} of ${ids.length}.` : `${name(active.id)} was put back.`,
|
|
23
|
+
onDragCancel: ({ active }) => `Moving ${name(active.id)} was cancelled. It is back at row ${place(active.id) + 1}.`
|
|
24
|
+
};
|
|
25
|
+
const end = ({ active, over }) => {
|
|
26
|
+
if (!over || active.id === over.id) return;
|
|
27
|
+
reorder.onMove(place(active.id), place(over.id));
|
|
28
|
+
};
|
|
29
|
+
return /* @__PURE__ */ jsx(DndContext, {
|
|
30
|
+
sensors,
|
|
31
|
+
collisionDetection: closestCenter,
|
|
32
|
+
onDragEnd: end,
|
|
33
|
+
accessibility: {
|
|
34
|
+
announcements,
|
|
35
|
+
screenReaderInstructions: { draggable: instructions }
|
|
36
|
+
},
|
|
37
|
+
children: /* @__PURE__ */ jsx(SortableContext, {
|
|
38
|
+
items: ids,
|
|
39
|
+
strategy: verticalListSortingStrategy,
|
|
40
|
+
children
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
/** A row moved by its handle, which takes the checkbox's place; it follows the pointer up and down only. */
|
|
45
|
+
const SortableRow = ({ row, label, ...props }) => {
|
|
46
|
+
const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id: row.id });
|
|
47
|
+
const handle = /* @__PURE__ */ jsx("button", {
|
|
48
|
+
ref: setActivatorNodeRef,
|
|
49
|
+
type: "button",
|
|
50
|
+
"aria-label": `Move ${label}`,
|
|
51
|
+
...attributes,
|
|
52
|
+
...listeners,
|
|
53
|
+
onClick: (event) => event.stopPropagation(),
|
|
54
|
+
style: { touchAction: "none" },
|
|
55
|
+
className: "flex size-7 cursor-grab items-center justify-center rounded text-faint-foreground hover:bg-muted hover:text-foreground focus-visible:text-foreground active:cursor-grabbing pointer-coarse:size-9",
|
|
56
|
+
children: /* @__PURE__ */ jsx(GripVertical, { className: "size-4" })
|
|
57
|
+
});
|
|
58
|
+
return /* @__PURE__ */ jsx(DataRow, {
|
|
59
|
+
...props,
|
|
60
|
+
rowRef: setNodeRef,
|
|
61
|
+
handle,
|
|
62
|
+
dragY: transform?.y ?? 0,
|
|
63
|
+
transition,
|
|
64
|
+
dragging: isDragging
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { ReorderRows, SortableRow };
|
package/dist/index.d.ts
CHANGED
|
@@ -56,7 +56,8 @@ import { MenuSelect, MenuSelectProps } from "./primitives/menu-select.js";
|
|
|
56
56
|
import { ChoiceOption, Choices, ChoicesProps } from "./primitives/choices.js";
|
|
57
57
|
import { Dialog, DialogProps } from "./primitives/dialog.js";
|
|
58
58
|
import { Field, FieldProps } from "./record-view/field.js";
|
|
59
|
+
import { useFieldControl } from "./primitives/field-control.js";
|
|
59
60
|
import { Section } from "./record-view/section.js";
|
|
60
61
|
import { Form, FormProps } from "./app/create/form.js";
|
|
61
62
|
import { Spinner } from "./primitives/spinner.js";
|
|
62
|
-
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, type BackendData, type BackendRefresh, type BeforeSignInProps, Button, type ButtonProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, Spinner, type Step, StepList, type StepState, Textarea, type TextareaProps, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
|
63
|
+
export { ActionCard, type ActionCardProps, type ActionContext, type ActionHandler, ActionInputError, type ActionInputProps, ApiProvider, type ApiProviderProps, App, AppLink, type AppProps, AssistantDock, type AssistantDockProps, AuthLayout, type AuthLayoutProps, type BackendData, type BackendRefresh, type BeforeSignInProps, Button, type ButtonProps, type CardAction, type CardTone, ChatMessage, ChatThread, type ChatThreadProps, type ChoiceOption, Choices, type ChoicesProps, CodeForm, type CodeFormProps, CompactTable, type CompactTableProps, Composer, type ComposerProps, type CurrentAddress, Dialog, type DialogProps, type DiffLine, DiffView, type DiffViewProps, Field, FieldList, type FieldProps, type FieldWidget, type FieldWidgetEditorProps, type FieldWidgetProps, Form, type FormProps, Input, type InputProps, type LayoutRecord, type ListAddress, type ListChange, type ListDisplay, type ListDisplayRow, LogoMark, MenuSelect, type MenuSelectProps, type MetaData, OtpInput, type OtpInputProps, PartView, type PluginInfo, type ProjectUi, type RecordCardProps, RecordLink, ResendCodeButton, type ResendCodeButtonProps, ResponsiveDialog, type ResponsiveDialogProps, Section, Segmented, type SegmentedOption, type SegmentedProps, type ShellLogoProps, SidePanel, type SidePanelProps, SignInPrompt, type SignInPromptProps, Spinner, type Step, StepList, type StepState, Textarea, type TextareaProps, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { LogoMark } from "./app-shell/logo.js";
|
|
2
2
|
import { Segmented } from "./primitives/segmented.js";
|
|
3
|
+
import { useFieldControl } from "./primitives/field-control.js";
|
|
3
4
|
import { Input } from "./primitives/input.js";
|
|
4
5
|
import { Spinner } from "./primitives/spinner.js";
|
|
5
6
|
import { Button, buttonClass } from "./primitives/button.js";
|
|
@@ -49,4 +50,4 @@ import { isConflict, useUpdateRecord } from "./data/use-update-record.js";
|
|
|
49
50
|
import { App } from "./app/app.js";
|
|
50
51
|
import { RecordLink } from "./app/displays/record-link.js";
|
|
51
52
|
import { Choices } from "./primitives/choices.js";
|
|
52
|
-
export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, Button, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, MenuSelect, OtpInput, PartView, RecordLink, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StepList, Textarea, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
|
53
|
+
export { ActionCard, ActionInputError, ApiProvider, App, AppLink, AssistantDock, AuthLayout, Button, ChatMessage, ChatThread, Choices, CodeForm, CompactTable, Composer, Dialog, DiffView, Field, FieldList, Form, Input, LogoMark, MenuSelect, OtpInput, PartView, RecordLink, ResendCodeButton, ResponsiveDialog, Section, Segmented, SidePanel, SignInPrompt, Spinner, StepList, Textarea, buttonClass, definePlugin, defineUi, diffStats, isConflict, mergeUi, parseDiff, pluginApi, protobaseVersion, uiFromModule, useApiToken, useAppAddress, useAssistant, useAssistantClient, useBackendData, useClient, useFacets, useFieldControl, useHistogram, useLayoutRecord, useList, useListDisplay, useMeta, usePlugin, useRecord, useResourceClient, useSeries, useUpdateRecord };
|
package/dist/package.js
CHANGED
|
@@ -2,11 +2,12 @@ import { cn } from "../lib/cn.js";
|
|
|
2
2
|
import { Check, Minus } from "lucide-react";
|
|
3
3
|
import { jsx } from "react/jsx-runtime";
|
|
4
4
|
//#region src/primitives/checkbox.tsx
|
|
5
|
-
const Checkbox = ({ checked, indeterminate, onChange, label, className }) => /* @__PURE__ */ jsx("button", {
|
|
5
|
+
const Checkbox = ({ checked, indeterminate, onChange, label, disabled, className }) => /* @__PURE__ */ jsx("button", {
|
|
6
6
|
type: "button",
|
|
7
7
|
role: "checkbox",
|
|
8
8
|
"aria-checked": indeterminate ? "mixed" : checked,
|
|
9
9
|
"aria-label": label,
|
|
10
|
+
disabled,
|
|
10
11
|
onClick: () => onChange?.(!checked),
|
|
11
12
|
className: cn("relative inline-flex size-4 shrink-0 items-center justify-center rounded border transition-colors after:absolute after:-inset-1 after:content-[\"\"] pointer-coarse:after:-inset-3.5", checked || indeterminate ? "border-primary bg-primary text-primary-foreground" : "border-border-strong bg-background hover:border-faint-foreground", className),
|
|
12
13
|
children: indeterminate ? /* @__PURE__ */ jsx(Minus, {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/primitives/field-control.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The attributes that tie a control to the `Field` around it: the id its label points at, its name, the help and error
|
|
4
|
+
* that describe it, and whether it is invalid or required. None outside a `Field`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const useFieldControl: () => {
|
|
7
|
+
id?: undefined;
|
|
8
|
+
name?: undefined;
|
|
9
|
+
'aria-describedby'?: undefined;
|
|
10
|
+
'aria-invalid'?: undefined;
|
|
11
|
+
'aria-required'?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string | undefined;
|
|
15
|
+
'aria-describedby': string | undefined;
|
|
16
|
+
'aria-invalid': true | undefined;
|
|
17
|
+
'aria-required': true | undefined;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
//#region src/primitives/field-control.ts
|
|
3
|
+
/** Set by a form's `Field` for the control inside it. */
|
|
4
|
+
const FieldControlContext = createContext(void 0);
|
|
5
|
+
/**
|
|
6
|
+
* The attributes that tie a control to the `Field` around it: the id its label points at, its name, the help and error
|
|
7
|
+
* that describe it, and whether it is invalid or required. None outside a `Field`.
|
|
8
|
+
*/
|
|
9
|
+
const useFieldControl = () => {
|
|
10
|
+
const control = useContext(FieldControlContext);
|
|
11
|
+
if (!control) return {};
|
|
12
|
+
return {
|
|
13
|
+
id: control.id,
|
|
14
|
+
name: control.name,
|
|
15
|
+
"aria-describedby": control.describedBy,
|
|
16
|
+
"aria-invalid": control.invalid || void 0,
|
|
17
|
+
"aria-required": control.required || void 0
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
//#endregion
|
|
21
|
+
export { FieldControlContext, useFieldControl };
|
package/dist/primitives/input.js
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { useFieldControl } from "./field-control.js";
|
|
2
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
4
|
//#region src/primitives/input.tsx
|
|
4
|
-
const Input = ({ leading, trailing, invalid, className, wrapperClassName, ...rest }) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
const Input = ({ leading, trailing, invalid, className, wrapperClassName, ...rest }) => {
|
|
6
|
+
const control = useFieldControl();
|
|
7
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
8
|
+
className: cn("flex h-8 items-center gap-2 rounded-md border bg-background px-2.5 text-muted-foreground shadow-sm focus-within:border-primary focus-within:ring-2 focus-within:ring-primary/20", invalid ? "border-danger" : "border-border-strong", wrapperClassName),
|
|
9
|
+
children: [
|
|
10
|
+
leading,
|
|
11
|
+
/* @__PURE__ */ jsx("input", {
|
|
12
|
+
className: cn("min-w-0 flex-1 bg-transparent text-body text-foreground outline-none placeholder:text-faint-foreground", className),
|
|
13
|
+
...control,
|
|
14
|
+
...invalid && { "aria-invalid": true },
|
|
15
|
+
...rest
|
|
16
|
+
}),
|
|
17
|
+
trailing
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
};
|
|
15
21
|
//#endregion
|
|
16
22
|
export { Input };
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { useFieldControl } from "./field-control.js";
|
|
2
3
|
import { jsx } from "react/jsx-runtime";
|
|
3
4
|
//#region src/primitives/switch.tsx
|
|
4
|
-
const Switch = ({ checked, onChange, label }) =>
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const Switch = ({ checked, onChange, label }) => {
|
|
6
|
+
const control = useFieldControl();
|
|
7
|
+
return /* @__PURE__ */ jsx("button", {
|
|
8
|
+
type: "button",
|
|
9
|
+
role: "switch",
|
|
10
|
+
id: control.id,
|
|
11
|
+
"aria-describedby": control["aria-describedby"],
|
|
12
|
+
"aria-checked": checked,
|
|
13
|
+
"aria-label": label,
|
|
14
|
+
onClick: () => onChange?.(!checked),
|
|
15
|
+
className: cn("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors", checked ? "bg-primary" : "bg-border-strong"),
|
|
16
|
+
children: /* @__PURE__ */ jsx("span", { className: cn("size-4 rounded-full bg-white shadow transition-transform", checked ? "translate-x-[18px]" : "translate-x-0.5") })
|
|
17
|
+
});
|
|
18
|
+
};
|
|
13
19
|
//#endregion
|
|
14
20
|
export { Switch };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { useFieldControl } from "./field-control.js";
|
|
2
3
|
import { useEffect, useLayoutEffect, useRef } from "react";
|
|
3
4
|
import { jsx } from "react/jsx-runtime";
|
|
4
5
|
//#region src/primitives/textarea.tsx
|
|
@@ -9,6 +10,7 @@ const fit = (box) => {
|
|
|
9
10
|
/** A text box of several lines that grows with its text instead of scrolling inside. */
|
|
10
11
|
const Textarea = ({ invalid, className, rows = 3, value, ...rest }) => {
|
|
11
12
|
const ref = useRef(null);
|
|
13
|
+
const control = useFieldControl();
|
|
12
14
|
useLayoutEffect(() => {
|
|
13
15
|
if (ref.current) fit(ref.current);
|
|
14
16
|
}, [value]);
|
|
@@ -29,6 +31,8 @@ const Textarea = ({ invalid, className, rows = 3, value, ...rest }) => {
|
|
|
29
31
|
rows,
|
|
30
32
|
value,
|
|
31
33
|
className: cn("block w-full resize-none overflow-hidden rounded-md border bg-background px-2.5 py-1.5 text-body text-foreground shadow-sm outline-none placeholder:text-faint-foreground focus:border-primary focus:ring-2 focus:ring-primary/20", invalid ? "border-danger" : "border-border-strong", className),
|
|
34
|
+
...control,
|
|
35
|
+
...invalid && { "aria-invalid": true },
|
|
32
36
|
...rest
|
|
33
37
|
});
|
|
34
38
|
};
|
|
@@ -12,6 +12,8 @@ export type FieldConflict = {
|
|
|
12
12
|
onUseTheirs: () => void;
|
|
13
13
|
};
|
|
14
14
|
export type FieldProps = {
|
|
15
|
+
/** The control's name, for the browser's autofill. */
|
|
16
|
+
name?: string;
|
|
15
17
|
label: string;
|
|
16
18
|
/** Marks the label with an asterisk. */
|
|
17
19
|
required?: boolean;
|
|
@@ -23,5 +25,9 @@ export type FieldProps = {
|
|
|
23
25
|
wide?: boolean;
|
|
24
26
|
children: ReactNode;
|
|
25
27
|
};
|
|
26
|
-
|
|
28
|
+
/**
|
|
29
|
+
* A labelled form field. The `Input` or `Textarea` inside takes its label, its name, the help and error that describe
|
|
30
|
+
* it and whether it is invalid or required from the field; another control uses `useFieldControl`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const Field: ({ name, label, required, help, example, error, conflict, wide, children }: FieldProps) => import("react").JSX.Element;
|
|
27
33
|
//#endregion
|