@_linked/shape-ui 2.0.0 → 2.2.0

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.
Files changed (210) hide show
  1. package/lib/esm/columns.d.ts +58 -0
  2. package/lib/esm/columns.js +173 -0
  3. package/lib/esm/columns.js.map +1 -0
  4. package/lib/esm/components/AddInstanceForms.d.ts +38 -0
  5. package/lib/esm/components/AddInstanceForms.js +143 -0
  6. package/lib/esm/components/AddInstanceForms.js.map +1 -0
  7. package/lib/esm/components/AddInstanceForms.module.css +12 -0
  8. package/lib/esm/components/BatchEditDrawer.d.ts +13 -0
  9. package/lib/esm/components/BatchEditDrawer.js +122 -0
  10. package/lib/esm/components/BatchEditDrawer.js.map +1 -0
  11. package/lib/esm/components/BatchEditDrawer.module.css +193 -0
  12. package/lib/esm/components/CustomMultiSelect.d.ts +26 -0
  13. package/lib/esm/components/CustomMultiSelect.js +344 -0
  14. package/lib/esm/components/CustomMultiSelect.js.map +1 -0
  15. package/lib/esm/components/CustomMultiSelect.module.css +291 -0
  16. package/lib/esm/components/DynamicFormField.d.ts +21 -0
  17. package/lib/esm/components/DynamicFormField.js +149 -0
  18. package/lib/esm/components/DynamicFormField.js.map +1 -0
  19. package/lib/esm/components/DynamicFormField.module.css +169 -0
  20. package/lib/esm/components/EditInstanceForms.d.ts +15 -0
  21. package/lib/esm/components/EditInstanceForms.js +118 -0
  22. package/lib/esm/components/EditInstanceForms.js.map +1 -0
  23. package/lib/esm/components/FilterBadge.d.ts +20 -0
  24. package/lib/esm/components/FilterBadge.js +26 -0
  25. package/lib/esm/components/FilterBadge.js.map +1 -0
  26. package/lib/esm/components/FilterBadge.module.css +25 -0
  27. package/lib/esm/components/FormField.d.ts +18 -0
  28. package/lib/esm/components/FormField.js +9 -0
  29. package/lib/esm/components/FormField.js.map +1 -0
  30. package/lib/esm/components/FormField.module.css +57 -0
  31. package/lib/esm/components/InstanceDeletionDialog.d.ts +17 -0
  32. package/lib/esm/components/InstanceDeletionDialog.js +83 -0
  33. package/lib/esm/components/InstanceDeletionDialog.js.map +1 -0
  34. package/lib/esm/components/InstanceDeletionDialog.module.css +59 -0
  35. package/lib/esm/components/InstanceOverview.d.ts +37 -0
  36. package/lib/esm/components/InstanceOverview.js +233 -0
  37. package/lib/esm/components/InstanceOverview.js.map +1 -0
  38. package/lib/esm/components/InstanceOverview.module.css +35 -0
  39. package/lib/esm/components/InstanceView.d.ts +29 -0
  40. package/lib/esm/components/InstanceView.js +97 -0
  41. package/lib/esm/components/InstanceView.js.map +1 -0
  42. package/lib/esm/components/InstanceView.module.css +157 -0
  43. package/lib/esm/components/NodeBadge.d.ts +25 -0
  44. package/lib/esm/components/NodeBadge.js +29 -0
  45. package/lib/esm/components/NodeBadge.js.map +1 -0
  46. package/lib/esm/components/NodeValuesEditor.d.ts +15 -0
  47. package/lib/esm/components/NodeValuesEditor.js +29 -0
  48. package/lib/esm/components/NodeValuesEditor.js.map +1 -0
  49. package/lib/esm/components/NodeValuesEditor.module.css +4 -0
  50. package/lib/esm/components/ReactTable.d.ts +50 -0
  51. package/lib/esm/components/ReactTable.js +370 -0
  52. package/lib/esm/components/ReactTable.js.map +1 -0
  53. package/lib/esm/components/ReactTable.module.css +950 -0
  54. package/lib/esm/components/TextfieldEditor.d.ts +1 -2
  55. package/lib/esm/components/TextfieldEditor.js +2 -2
  56. package/lib/esm/components/TextfieldEditor.js.map +1 -1
  57. package/lib/esm/components/forms.module.css +96 -0
  58. package/lib/esm/host.d.ts +196 -0
  59. package/lib/esm/host.js +25 -0
  60. package/lib/esm/host.js.map +1 -0
  61. package/lib/esm/hostContext.d.ts +34 -0
  62. package/lib/esm/hostContext.js +95 -0
  63. package/lib/esm/hostContext.js.map +1 -0
  64. package/lib/esm/index.d.ts +64 -10
  65. package/lib/esm/index.js +63 -14
  66. package/lib/esm/index.js.map +1 -1
  67. package/lib/esm/read.d.ts +48 -0
  68. package/lib/esm/read.js +96 -0
  69. package/lib/esm/read.js.map +1 -0
  70. package/lib/esm/search.d.ts +32 -0
  71. package/lib/esm/search.js +87 -0
  72. package/lib/esm/search.js.map +1 -0
  73. package/lib/esm/shape/contracts.d.ts +58 -0
  74. package/lib/esm/shape/contracts.js +15 -0
  75. package/lib/esm/shape/contracts.js.map +1 -0
  76. package/lib/esm/shape/naming.d.ts +30 -0
  77. package/lib/esm/shape/naming.js +52 -0
  78. package/lib/esm/shape/naming.js.map +1 -0
  79. package/lib/esm/shape/nodeDisplay.d.ts +36 -0
  80. package/lib/esm/shape/nodeDisplay.js +117 -0
  81. package/lib/esm/shape/nodeDisplay.js.map +1 -0
  82. package/lib/esm/shape/propertyTypes.d.ts +12 -0
  83. package/lib/esm/shape/propertyTypes.js +60 -0
  84. package/lib/esm/shape/propertyTypes.js.map +1 -0
  85. package/lib/esm/shape/propertyVisibility.d.ts +56 -0
  86. package/lib/esm/shape/propertyVisibility.js +112 -0
  87. package/lib/esm/shape/propertyVisibility.js.map +1 -0
  88. package/lib/esm/shape/shapeDependencies.d.ts +41 -0
  89. package/lib/esm/shape/shapeDependencies.js +141 -0
  90. package/lib/esm/shape/shapeDependencies.js.map +1 -0
  91. package/lib/esm/shape/tableFilters.d.ts +35 -0
  92. package/lib/esm/shape/tableFilters.js +171 -0
  93. package/lib/esm/shape/tableFilters.js.map +1 -0
  94. package/lib/esm/shape/useFormDependencies.d.ts +38 -0
  95. package/lib/esm/shape/useFormDependencies.js +178 -0
  96. package/lib/esm/shape/useFormDependencies.js.map +1 -0
  97. package/lib/esm/shape/validation.d.ts +15 -0
  98. package/lib/esm/shape/validation.js +106 -0
  99. package/lib/esm/shape/validation.js.map +1 -0
  100. package/lib/esm/tokens.css +25 -0
  101. package/lib/esm/types.d.ts +22 -0
  102. package/lib/esm/types.js +1 -0
  103. package/lib/esm/types.js.map +1 -0
  104. package/lib/esm/write.d.ts +51 -0
  105. package/lib/esm/write.js +99 -0
  106. package/lib/esm/write.js.map +1 -0
  107. package/package.json +44 -18
  108. package/src/backend.ts +1 -0
  109. package/src/columns.regression.test.ts +143 -0
  110. package/src/columns.test.ts +200 -0
  111. package/src/columns.ts +228 -0
  112. package/src/components/AddInstanceForms.module.css +12 -0
  113. package/src/components/AddInstanceForms.tsx +321 -0
  114. package/src/components/AvatarEditor.tsx +34 -0
  115. package/src/components/BatchEditDrawer.module.css +193 -0
  116. package/src/components/BatchEditDrawer.tsx +295 -0
  117. package/src/components/CheckboxEditor.module.css +6 -0
  118. package/src/components/CheckboxEditor.tsx +86 -0
  119. package/src/components/CustomMultiSelect.module.css +291 -0
  120. package/src/components/CustomMultiSelect.tsx +527 -0
  121. package/src/components/DateEditor.tsx +50 -0
  122. package/src/components/DynamicFormField.module.css +169 -0
  123. package/src/components/DynamicFormField.tsx +357 -0
  124. package/src/components/EditInstanceForms.tsx +260 -0
  125. package/src/components/FilterBadge.module.css +25 -0
  126. package/src/components/FilterBadge.tsx +70 -0
  127. package/src/components/FormField.module.css +57 -0
  128. package/src/components/FormField.tsx +59 -0
  129. package/src/components/ImageValueEditor.module.css +3 -0
  130. package/src/components/ImageValueEditor.tsx +82 -0
  131. package/src/components/InstanceDeletionDialog.module.css +59 -0
  132. package/src/components/InstanceDeletionDialog.tsx +175 -0
  133. package/src/components/InstanceOverview.module.css +35 -0
  134. package/src/components/InstanceOverview.tsx +374 -0
  135. package/src/components/InstanceView.module.css +157 -0
  136. package/src/components/InstanceView.tsx +212 -0
  137. package/src/components/NodeBadge.tsx +60 -0
  138. package/src/components/NodeValuesEditor.module.css +4 -0
  139. package/src/components/NodeValuesEditor.tsx +64 -0
  140. package/src/components/RadioButtonEditor.module.css +22 -0
  141. package/src/components/RadioButtonEditor.tsx +96 -0
  142. package/src/components/ReactTable.module.css +950 -0
  143. package/src/components/ReactTable.tsx +991 -0
  144. package/src/components/SelectEditor.tsx +85 -0
  145. package/src/components/ShapeTable.module.css +394 -0
  146. package/src/components/SwitchEditor.tsx +60 -0
  147. package/src/components/TextareaEditor.tsx +55 -0
  148. package/src/components/TextfieldEditor.tsx +67 -0
  149. package/src/components/ToggleEditor.tsx +63 -0
  150. package/src/components/forms.module.css +96 -0
  151. package/src/host.ts +226 -0
  152. package/src/hostContext.tsx +114 -0
  153. package/src/index.ts +99 -0
  154. package/src/package.ts +21 -0
  155. package/src/read.ts +148 -0
  156. package/src/search.test.ts +174 -0
  157. package/src/search.ts +108 -0
  158. package/src/shape/contracts.ts +66 -0
  159. package/src/shape/naming.ts +52 -0
  160. package/src/shape/nodeDisplay.ts +114 -0
  161. package/src/shape/propertyTypes.ts +86 -0
  162. package/src/shape/propertyVisibility.ts +180 -0
  163. package/src/shape/shapeDependencies.ts +183 -0
  164. package/src/shape/tableFilters.ts +223 -0
  165. package/src/shape/types.ts +1158 -0
  166. package/src/shape/useFormDependencies.ts +257 -0
  167. package/src/shape/validation.ts +121 -0
  168. package/src/tokens.css +25 -0
  169. package/src/types.ts +20 -0
  170. package/src/utils/helper.ts +35 -0
  171. package/src/write.test.ts +174 -0
  172. package/src/write.ts +114 -0
  173. package/.changeset/README.md +0 -20
  174. package/.changeset/config.json +0 -14
  175. package/.github/workflows/changeset-check.yml +0 -44
  176. package/.github/workflows/ci.yml +0 -43
  177. package/.github/workflows/publish.yml +0 -158
  178. package/CHANGELOG.md +0 -30
  179. package/lib/esm/components/AvatarEditor.d.ts +0 -20
  180. package/lib/esm/components/AvatarEditor.js +0 -7
  181. package/lib/esm/components/AvatarEditor.js.map +0 -1
  182. package/lib/esm/components/CheckboxEditor.d.ts +0 -21
  183. package/lib/esm/components/CheckboxEditor.js +0 -44
  184. package/lib/esm/components/CheckboxEditor.js.map +0 -1
  185. package/lib/esm/components/DateEditor.d.ts +0 -20
  186. package/lib/esm/components/DateEditor.js +0 -22
  187. package/lib/esm/components/DateEditor.js.map +0 -1
  188. package/lib/esm/components/RadioButtonEditor.d.ts +0 -23
  189. package/lib/esm/components/RadioButtonEditor.js +0 -41
  190. package/lib/esm/components/RadioButtonEditor.js.map +0 -1
  191. package/lib/esm/components/SelectEditor.d.ts +0 -21
  192. package/lib/esm/components/SelectEditor.js +0 -30
  193. package/lib/esm/components/SelectEditor.js.map +0 -1
  194. package/lib/esm/components/SwitchEditor.d.ts +0 -21
  195. package/lib/esm/components/SwitchEditor.js +0 -23
  196. package/lib/esm/components/SwitchEditor.js.map +0 -1
  197. package/lib/esm/components/TextareaEditor.d.ts +0 -23
  198. package/lib/esm/components/TextareaEditor.js +0 -15
  199. package/lib/esm/components/TextareaEditor.js.map +0 -1
  200. package/lib/esm/components/ToggleEditor.d.ts +0 -22
  201. package/lib/esm/components/ToggleEditor.js +0 -23
  202. package/lib/esm/components/ToggleEditor.js.map +0 -1
  203. package/lib/esm/ontologies/lincd-ui.d.ts +0 -6
  204. package/lib/esm/ontologies/lincd-ui.js +0 -18
  205. package/lib/esm/ontologies/lincd-ui.js.map +0 -1
  206. package/lib/esm/package.d.ts +0 -3
  207. package/lib/esm/package.js +0 -4
  208. package/lib/esm/package.js.map +0 -1
  209. package/tsconfig-cjs.json +0 -8
  210. package/tsconfig-esm.json +0 -9
@@ -0,0 +1,12 @@
1
+ .container {
2
+ display: flex;
3
+ flex-direction: column;
4
+ margin: 30px 0;
5
+ position: relative;
6
+
7
+ .saveButton {
8
+ position: fixed;
9
+ bottom: 1rem;
10
+ right: 3rem;
11
+ }
12
+ }
@@ -0,0 +1,321 @@
1
+ import { motion } from 'framer-motion';
2
+ import { Button } from '@_linked/primitives/components/Button';
3
+ import { Select } from '@_linked/primitives/components/Select';
4
+ import type { NodeShapeWire } from '@_linked/core/shapes/nodeShapeWire';
5
+ import { shacl } from '@_linked/core/ontologies/shacl';
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
+ import { useDataManagerHost } from '../hostContext.js';
8
+ import { Spinner } from '@_linked/primitives/components/Spinner';
9
+ import { useFormDependencies } from '../shape/useFormDependencies.js';
10
+ import {
11
+ getTransition,
12
+ staggerContainer,
13
+ staggerItem,
14
+ useReducedMotion,
15
+ } from '@_linked/primitives/motion';
16
+ import {
17
+ type CodeUsageData,
18
+ type PropertyUsageData,
19
+ classifyProperties,
20
+ getVisibleProperties,
21
+ } from '../shape/propertyVisibility.js';
22
+ import { validateAllFields } from '../shape/validation.js';
23
+ import { DynamicForm } from './DynamicFormField.js';
24
+ // Shared by both form organisms — named for what it is, rather than one of its two
25
+ // consumers importing the other's stylesheet.
26
+ import style from './forms.module.css';
27
+
28
+ interface AddInstanceFormsProps {
29
+ /**
30
+ * Persist the new instance and return its id.
31
+ *
32
+ * Optional. Falls back to the host's `createInstance`, which is itself filled in from the
33
+ * DSL — so a host that wires nothing still gets a working save. It was required, which
34
+ * quietly made the package's "saving works with no host wiring" untrue through the one
35
+ * surface that does the saving: a consumer had to reach for `useDataManagerHost()` and
36
+ * thread the method back in.
37
+ *
38
+ * Override it when the save has to do more than write the instance — record an edit, flush
39
+ * a draft — which is why the seam exists at all.
40
+ */
41
+ onSave?: (formData: Record<string, any>) => Promise<{id: string}>;
42
+ /**
43
+ * @deprecated Retained only for callers that still pass it; the organism no longer uses
44
+ * it for persistence. Remove once every caller supplies `onSave`.
45
+ */
46
+ projectId?: string;
47
+ shapeId: string;
48
+ shape: NodeShapeWire;
49
+ allShapes?: Record<string, NodeShapeWire>;
50
+ onComplete: (newId: string, formData?: Record<string, any>) => void;
51
+ onCancel?: () => void;
52
+ initialData?: Record<string, any>;
53
+ draftId?: string;
54
+ onFieldChange?: (data: Record<string, any>) => void;
55
+ onFlushDraft?: (data: Record<string, any>) => Promise<string | void> | string | void;
56
+ onClearDraft?: () => void;
57
+ propertyUsage?: Record<string, PropertyUsageData> | null;
58
+ codeUsage?: Record<string, CodeUsageData> | null;
59
+ }
60
+
61
+ function AddInstanceForms({
62
+ onSave,
63
+ projectId,
64
+ shapeId,
65
+ shape,
66
+ allShapes,
67
+ onComplete,
68
+ onCancel,
69
+ initialData,
70
+ draftId,
71
+ onFieldChange,
72
+ onFlushDraft,
73
+ onClearDraft,
74
+ propertyUsage,
75
+ codeUsage,
76
+ }: AddInstanceFormsProps) {
77
+ const reducedMotion = useReducedMotion();
78
+ const formDataRef = useRef<Record<string, any>>(initialData ? { ...initialData } : {});
79
+ const [isSaving, setIsSaving] = useState(false);
80
+ const [saveError, setSaveError] = useState<string | null>(null);
81
+ const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
82
+ const [extraFields, setExtraFields] = useState<Set<string>>(new Set());
83
+
84
+ // Dependent field visibility & narrowing
85
+ const host = useDataManagerHost();
86
+ const {
87
+ depMap,
88
+ onParentValueChange,
89
+ initFromData,
90
+ isParent,
91
+ getDependencyState,
92
+ } = useFormDependencies(shape, allShapes || null, projectId, host.readInstance);
93
+
94
+ // Proxy wraps formDataRef.current — intercepts property sets to detect field changes
95
+ // and trigger dependency updates when parent fields change.
96
+ const proxyOf = useMemo(() => {
97
+ return new Proxy(formDataRef.current, {
98
+ set(target, prop, value) {
99
+ const propLabel = prop as string;
100
+ const oldValue = target[propLabel];
101
+ target[propLabel] = value;
102
+ onFieldChange?.(target);
103
+
104
+ // If this property is a parent in the dependency graph,
105
+ // notify the dependency system and clear dependent field values.
106
+ // Only cascade when the value actually changed (not init write-backs).
107
+ if (isParent(propLabel)) {
108
+ let changed: boolean;
109
+ if (oldValue == null || value == null) {
110
+ changed = oldValue !== value;
111
+ } else if (Array.isArray(oldValue) && Array.isArray(value)) {
112
+ changed =
113
+ oldValue.length !== value.length ||
114
+ oldValue.some((o, i) => o?.id !== value[i]?.id);
115
+ } else if (typeof oldValue === 'object' && typeof value === 'object') {
116
+ changed = oldValue.id !== value.id;
117
+ } else {
118
+ changed = oldValue !== value;
119
+ }
120
+
121
+ if (changed) {
122
+ onParentValueChange(propLabel, value);
123
+ // Cascade reset: clear child field values
124
+ for (const [childLabel, deps] of depMap) {
125
+ if (deps.some((d) => d.parentLabel === propLabel)) {
126
+ target[childLabel] = undefined;
127
+ }
128
+ }
129
+ }
130
+ }
131
+
132
+ return true;
133
+ },
134
+ });
135
+ }, [depMap, onParentValueChange, isParent]);
136
+
137
+ // Initialize dependency state from draft/initial data
138
+ useEffect(() => {
139
+ if (initialData) {
140
+ initFromData(initialData);
141
+ }
142
+ }, []);
143
+
144
+ // Immediate save for use before pick navigation — returns draftId
145
+ const handleBeforeNavigate = useCallback(async (): Promise<string | void> => {
146
+ if (!onFlushDraft) return;
147
+ return await onFlushDraft(formDataRef.current);
148
+ }, [onFlushDraft]);
149
+
150
+ const handleSave = async () => {
151
+ setSaveError(null);
152
+
153
+ // Submit-time validation
154
+ const errors = validateAllFields(shape.propertyShapes, formDataRef.current);
155
+ if (errors.length > 0) {
156
+ const errorMap: Record<string, string> = {};
157
+ errors.forEach((e) => { errorMap[e.propertyLabel] = e.message; });
158
+ setFieldErrors(errorMap);
159
+ return;
160
+ }
161
+ setFieldErrors({});
162
+
163
+ setIsSaving(true);
164
+ try {
165
+ const persist =
166
+ onSave ?? ((data: Record<string, any>) => host.createInstance!(shapeId, data));
167
+ const result = await persist(formDataRef.current);
168
+ onComplete(result.id, formDataRef.current);
169
+ } catch (err) {
170
+ setSaveError(err instanceof Error ? err.message : String(err));
171
+ setIsSaving(false);
172
+ }
173
+ };
174
+
175
+ if (!shape) return null;
176
+
177
+ return (
178
+ <motion.div
179
+ initial={{ opacity: 0 }}
180
+ animate={{ opacity: 1 }}
181
+ transition={{ duration: reducedMotion ? 0 : 0.3, ease: [0.4, 0, 0.2, 1] }}
182
+ >
183
+ <motion.div
184
+ className={style.container}
185
+ initial={{ opacity: 0, y: 10 }}
186
+ animate={{ opacity: 1, y: 0 }}
187
+ transition={{
188
+ duration: reducedMotion ? 0 : 0.4,
189
+ ease: [0.4, 0, 0.2, 1],
190
+ delay: reducedMotion ? 0 : 0.1,
191
+ }}
192
+ >
193
+ <motion.div
194
+ className={style.rows}
195
+ variants={staggerContainer}
196
+ initial="initial"
197
+ animate="animate"
198
+ >
199
+ {(() => {
200
+ const classified = classifyProperties(shape.propertyShapes, propertyUsage || null, { codeUsage });
201
+ const autoVisible = getVisibleProperties(classified, 'form-add');
202
+ const autoVisibleLabels = new Set(autoVisible.map((c) => c.property.label));
203
+
204
+ const visibleProps = shape.propertyShapes.filter(
205
+ (p) => autoVisibleLabels.has(p.label) || extraFields.has(p.label)
206
+ );
207
+ const availableExtras = classified.filter(
208
+ (c) => !autoVisibleLabels.has(c.property.label) && !extraFields.has(c.property.label)
209
+ );
210
+
211
+ return (
212
+ <>
213
+ {visibleProps.map((propShape, i) => {
214
+ let required = propShape.minCount > 0;
215
+ let formName = propShape.label;
216
+ let label = propShape.label
217
+ .replace(/([A-Z])/g, ' $1')
218
+ .replace(/_/g, ' ')
219
+ .replace(/^./, (char) => char.toUpperCase());
220
+
221
+ let dataType;
222
+ let nodeKind = propShape.nodeKind;
223
+ if (nodeKind?.id === shacl.Literal.id) {
224
+ dataType = propShape.datatype?.id.split('#')[1];
225
+ }
226
+ if (nodeKind?.id === shacl.IRI.id) {
227
+ dataType = 'nodeShape';
228
+ }
229
+
230
+ return (
231
+ <motion.div
232
+ key={propShape.label}
233
+ variants={staggerItem}
234
+ transition={getTransition(reducedMotion)}
235
+ >
236
+ <DynamicForm
237
+ dataType={dataType}
238
+ formName={formName}
239
+ label={label}
240
+ placeholder={`Type ${label.toLowerCase()} here`}
241
+ required={required}
242
+ value={formDataRef.current[propShape.label]}
243
+ of={proxyOf as any}
244
+ property={propShape}
245
+ shape={null}
246
+ onBeforeNavigate={handleBeforeNavigate}
247
+ error={fieldErrors[propShape.label]}
248
+ dependencyState={getDependencyState(propShape.label)}
249
+ />
250
+ </motion.div>
251
+ );
252
+ })}
253
+ {availableExtras.length > 0 && (
254
+ <motion.div
255
+ variants={staggerItem}
256
+ transition={getTransition(reducedMotion)}
257
+ className={style.addFieldSection}
258
+ >
259
+ <Select.Root
260
+ value=""
261
+ onValueChange={(label) =>
262
+ setExtraFields((prev) => new Set(prev).add(label))
263
+ }
264
+ >
265
+ <Select.Trigger aria-label="Add optional field">
266
+ <Select.Value placeholder="+ Add optional field..." />
267
+ </Select.Trigger>
268
+ <Select.Content>
269
+ <Select.Group>
270
+ {availableExtras.map((c) => (
271
+ <Select.Item key={c.property.label} value={c.property.label}>
272
+ {c.property.label
273
+ .replace(/([A-Z])/g, ' $1')
274
+ .replace(/_/g, ' ')
275
+ .replace(/^./, (char) => char.toUpperCase())}
276
+ </Select.Item>
277
+ ))}
278
+ </Select.Group>
279
+ </Select.Content>
280
+ </Select.Root>
281
+ </motion.div>
282
+ )}
283
+ </>
284
+ );
285
+ })()}
286
+ </motion.div>
287
+ </motion.div>
288
+
289
+ {saveError && (
290
+ <p className={style.saveError}>{saveError}</p>
291
+ )}
292
+
293
+ <motion.div
294
+ className={style.actions}
295
+ initial={{ opacity: 0, y: 10 }}
296
+ animate={{ opacity: 1, y: 0 }}
297
+ transition={{
298
+ duration: reducedMotion ? 0 : 0.3,
299
+ ease: [0.4, 0, 0.2, 1],
300
+ delay: reducedMotion ? 0 : 0.3,
301
+ }}
302
+ >
303
+ {draftId && (
304
+ <Button variant="ghost" onClick={onClearDraft} disabled={isSaving}>
305
+ Clear Form
306
+ </Button>
307
+ )}
308
+ <Button variant="outline" onClick={onCancel} disabled={isSaving}>Cancel</Button>
309
+ <Button onClick={handleSave} disabled={isSaving}>
310
+ {isSaving ? (
311
+ <><Spinner size="small" /> Creating</>
312
+ ) : (
313
+ 'Create'
314
+ )}
315
+ </Button>
316
+ </motion.div>
317
+ </motion.div>
318
+ );
319
+ }
320
+
321
+ export default AddInstanceForms;
@@ -0,0 +1,34 @@
1
+ import React, { useState } from 'react';
2
+ import type { PropertyShapeWire } from '@_linked/core/shapes/nodeShapeWire';
3
+ import { Root, Image } from '@_linked/primitives/components/Avatar';
4
+
5
+ export interface AvatarEditorProps {
6
+ of?: Record<string, any>;
7
+ property?: PropertyShapeWire;
8
+ /**
9
+ * Converts the value returned by the shape, to the input string for the avatar
10
+ * @param shapePropertyValue
11
+ * @returns
12
+ */
13
+ preConvertValueFn?: (shapePropertyValue: any) => string;
14
+ /**
15
+ * Converts the value of the avatar to a value that can be provided to the set method of the shape
16
+ * @param inputValue
17
+ * @returns
18
+ */
19
+ postConvertValueFn?: (inputValue: string) => any;
20
+ }
21
+
22
+ export const AvatarEditor = ({
23
+ of,
24
+ property,
25
+ preConvertValueFn,
26
+ postConvertValueFn,
27
+ ...restProps
28
+ }: AvatarEditorProps) => {
29
+ return (
30
+ <Root>
31
+ <Image />
32
+ </Root>
33
+ );
34
+ };
@@ -0,0 +1,193 @@
1
+ /* ============================================================================
2
+ BatchEditDrawer.module.css
3
+
4
+ Drawer for batch-editing multiple selected instances.
5
+ Uses same drawer pattern as filter drawer in ReactTable.
6
+ ============================================================================ */
7
+
8
+ .drawer {
9
+ position: fixed !important;
10
+ top: 0 !important;
11
+ right: 0 !important;
12
+ bottom: 0 !important;
13
+ left: auto !important;
14
+ height: 100vh !important;
15
+ max-height: 100vh !important;
16
+ width: 380px !important;
17
+ min-width: 380px !important;
18
+ background-color: var(--surface-bg) !important;
19
+ border-left: 1px solid var(--surface-border) !important;
20
+ box-shadow: -4px 0 24px rgba(0, 0, 0, 0.15) !important;
21
+ border-radius: var(--radius-surface) 0 0 var(--radius-surface) !important;
22
+ }
23
+
24
+ .drawerContent {
25
+ display: flex;
26
+ flex-direction: column;
27
+ height: 100%;
28
+ }
29
+
30
+ .header {
31
+ padding: var(--space-lg) !important;
32
+ border-bottom: 1px solid var(--surface-border);
33
+ text-align: left !important;
34
+ }
35
+
36
+ .header h2,
37
+ .header [class*='Title'] {
38
+ color: var(--heading-color);
39
+ margin-bottom: var(--space-xs);
40
+ }
41
+
42
+ .header p,
43
+ .header [class*='Description'] {
44
+ color: var(--text-auxiliary);
45
+ font-size: var(--typography-small-size);
46
+ }
47
+
48
+ /* === Body === */
49
+ .body {
50
+ flex: 1;
51
+ padding: var(--space-lg);
52
+ overflow-y: auto;
53
+ }
54
+
55
+ /* === Field List (edit step) === */
56
+ .fieldList {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--space-md);
60
+ }
61
+
62
+ .fieldRow {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--space-xs);
66
+ padding: var(--space-sm) 0;
67
+ border-bottom: 1px solid var(--table-row-border);
68
+ }
69
+
70
+ .fieldRow:last-child {
71
+ border-bottom: none;
72
+ }
73
+
74
+ .fieldToggle {
75
+ display: flex;
76
+ align-items: center;
77
+ gap: var(--space-sm);
78
+ }
79
+
80
+ .fieldLabel {
81
+ font-size: var(--typography-font-size);
82
+ font-weight: 500;
83
+ color: var(--text-primary);
84
+ cursor: pointer;
85
+ user-select: none;
86
+ }
87
+
88
+ .fieldInput {
89
+ padding-left: calc(18px + var(--space-sm)); /* align with label after checkbox */
90
+ }
91
+
92
+ .fieldInputDisabled {
93
+ padding-left: calc(18px + var(--space-sm));
94
+ opacity: 0.4;
95
+ pointer-events: none;
96
+ }
97
+
98
+ .input {
99
+ width: 100%;
100
+ box-shadow: none;
101
+ border: 1px solid var(--field-border);
102
+ border-radius: var(--radius-field);
103
+ padding: var(--space-sm) var(--field-padding-x);
104
+ background-color: var(--field-bg);
105
+ color: var(--field-text);
106
+ font-size: var(--typography-small-size);
107
+ transition: all var(--transition-fast);
108
+ }
109
+
110
+ .input:focus {
111
+ outline: none;
112
+ border-color: var(--field-border-focus);
113
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--batch-edit-drawer-tertiary-500, var(--color-tertiary-500)) 15%, transparent);
114
+ }
115
+
116
+ .input:disabled {
117
+ background-color: var(--field-bg);
118
+ cursor: not-allowed;
119
+ }
120
+
121
+ .input::placeholder {
122
+ color: var(--field-text-placeholder);
123
+ }
124
+
125
+ .noFields {
126
+ color: var(--text-auxiliary);
127
+ font-size: var(--typography-small-size);
128
+ text-align: center;
129
+ padding: var(--space-xl) 0;
130
+ }
131
+
132
+ /* === Review Step === */
133
+ .reviewList {
134
+ display: flex;
135
+ flex-direction: column;
136
+ gap: var(--space-sm);
137
+ }
138
+
139
+ .reviewIntro {
140
+ color: var(--text-auxiliary);
141
+ font-size: var(--typography-small-size);
142
+ margin-bottom: var(--space-md);
143
+ }
144
+
145
+ .reviewItem {
146
+ display: flex;
147
+ align-items: center;
148
+ gap: var(--space-sm);
149
+ padding: var(--space-sm) var(--space-md);
150
+ background-color: var(--table-header-bg);
151
+ border-radius: var(--radius-field);
152
+ font-size: var(--typography-small-size);
153
+ }
154
+
155
+ .reviewField {
156
+ font-weight: 600;
157
+ color: var(--text-primary);
158
+ min-width: 0;
159
+ }
160
+
161
+ .reviewValue {
162
+ color: var(--batch-edit-drawer-tertiary-500, var(--color-tertiary-500));
163
+ font-weight: 500;
164
+ word-break: break-word;
165
+ }
166
+
167
+ /* === Footer === */
168
+ .footer {
169
+ padding: var(--space-lg);
170
+ border-top: 1px solid var(--surface-border);
171
+ display: flex;
172
+ gap: var(--space-sm);
173
+ justify-content: flex-end;
174
+ }
175
+
176
+ /* === Dark Mode === */
177
+ [data-theme='dark'] .drawer {
178
+ background-color: var(--surface-bg) !important;
179
+ border-left-color: var(--surface-border) !important;
180
+ box-shadow: -4px 0 24px rgba(0, 0, 0, 0.4) !important;
181
+ }
182
+
183
+ [data-theme='dark'] .header {
184
+ border-bottom-color: var(--surface-border);
185
+ }
186
+
187
+ [data-theme='dark'] .footer {
188
+ border-top-color: var(--surface-border);
189
+ }
190
+
191
+ [data-theme='dark'] .reviewItem {
192
+ background-color: var(--table-header-bg);
193
+ }