@_linked/shape-ui 2.0.0 → 2.3.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 (207) 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 +43 -0
  36. package/lib/esm/components/InstanceOverview.js +236 -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 +59 -0
  51. package/lib/esm/components/ReactTable.js +378 -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 +60 -10
  65. package/lib/esm/index.js +60 -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 +385 -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 +1013 -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 +93 -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/tsconfig-cjs.json +0 -8
  207. package/tsconfig-esm.json +0 -9
@@ -0,0 +1,260 @@
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 { PropertyShapeWire, NodeShapeWire } from '@_linked/core/shapes/nodeShapeWire';
5
+ import { shacl } from '@_linked/core/ontologies/shacl';
6
+ import { useEffect, useMemo, 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 { DynamicForm } from './DynamicFormField.js';
23
+ // Shared by both form organisms — named for what it is, rather than one of its two
24
+ // consumers importing the other's stylesheet.
25
+ import style from './forms.module.css';
26
+
27
+ interface EditInstanceFormsProps {
28
+ of: Record<string, any>;
29
+ onSave?: (data: Record<string, any>) => Promise<void>;
30
+ onCancel?: () => void;
31
+ shape: NodeShapeWire;
32
+ properties: Record<string, PropertyShapeWire>;
33
+ allShapes?: Record<string, NodeShapeWire>;
34
+ projectId?: string;
35
+ propertyUsage?: Record<string, PropertyUsageData> | null;
36
+ codeUsage?: Record<string, CodeUsageData> | null;
37
+ }
38
+
39
+ function EditInstanceForms({
40
+ of,
41
+ onSave,
42
+ onCancel,
43
+ shape,
44
+ properties,
45
+ allShapes,
46
+ projectId,
47
+ propertyUsage,
48
+ codeUsage,
49
+ }: EditInstanceFormsProps) {
50
+ const reducedMotion = useReducedMotion();
51
+ const propShapes = shape.propertyShapes;
52
+ const [isSaving, setIsSaving] = useState(false);
53
+ const [saveError, setSaveError] = useState<string | null>(null);
54
+ const [extraFields, setExtraFields] = useState<Set<string>>(new Set());
55
+
56
+ // Dependent field visibility & narrowing
57
+ const host = useDataManagerHost();
58
+ const {
59
+ depMap,
60
+ onParentValueChange,
61
+ initFromData,
62
+ isParent,
63
+ getDependencyState,
64
+ } = useFormDependencies(shape, allShapes || null, projectId || null, host.readInstance);
65
+
66
+ // Wrap `of` in a Proxy to detect parent field changes
67
+ const proxyOf = useMemo(() => {
68
+ return new Proxy(of, {
69
+ set(target, prop, value) {
70
+ const propLabel = prop as string;
71
+ const oldValue = target[propLabel];
72
+ target[propLabel] = value;
73
+
74
+ if (isParent(propLabel)) {
75
+ // Only cascade-clear children when the parent value actually changed,
76
+ // not during initialization write-backs from CustomMultiSelect
77
+ let changed: boolean;
78
+ if (oldValue == null || value == null) {
79
+ changed = oldValue !== value;
80
+ } else if (Array.isArray(oldValue) && Array.isArray(value)) {
81
+ changed =
82
+ oldValue.length !== value.length ||
83
+ oldValue.some((o, i) => o?.id !== value[i]?.id);
84
+ } else if (typeof oldValue === 'object' && typeof value === 'object') {
85
+ changed = oldValue.id !== value.id;
86
+ } else {
87
+ changed = oldValue !== value;
88
+ }
89
+
90
+ if (changed) {
91
+ onParentValueChange(propLabel, value);
92
+ for (const [childLabel, deps] of depMap) {
93
+ if (deps.some((d) => d.parentLabel === propLabel)) {
94
+ target[childLabel] = undefined;
95
+ }
96
+ }
97
+ }
98
+ }
99
+
100
+ return true;
101
+ },
102
+ });
103
+ }, [of, depMap, onParentValueChange, isParent]);
104
+
105
+ // Initialize dependency state from existing instance data
106
+ useEffect(() => {
107
+ if (of) {
108
+ initFromData(of);
109
+ }
110
+ }, []);
111
+
112
+ const handleSave = async () => {
113
+ setIsSaving(true);
114
+ setSaveError(null);
115
+ try {
116
+ await onSave?.(of);
117
+ } catch (err) {
118
+ setSaveError(err instanceof Error ? err.message : String(err));
119
+ setIsSaving(false);
120
+ }
121
+ };
122
+
123
+ return (
124
+ <motion.div
125
+ initial={{ opacity: 0 }}
126
+ animate={{ opacity: 1 }}
127
+ transition={{ duration: reducedMotion ? 0 : 0.3, ease: [0.4, 0, 0.2, 1] }}
128
+ >
129
+ <motion.div
130
+ className={style.container}
131
+ initial={{ opacity: 0, y: 10 }}
132
+ animate={{ opacity: 1, y: 0 }}
133
+ transition={{
134
+ duration: reducedMotion ? 0 : 0.4,
135
+ ease: [0.4, 0, 0.2, 1],
136
+ delay: reducedMotion ? 0 : 0.1,
137
+ }}
138
+ >
139
+ <motion.div
140
+ className={style.rows}
141
+ variants={staggerContainer}
142
+ initial="initial"
143
+ animate="animate"
144
+ >
145
+ {(() => {
146
+ const classified = classifyProperties(propShapes, propertyUsage || null, { codeUsage });
147
+ const autoVisible = getVisibleProperties(classified, 'form-edit', of);
148
+ const autoVisibleLabels = new Set(autoVisible.map((c) => c.property.label));
149
+
150
+ const visibleProps = propShapes.filter(
151
+ (p) => autoVisibleLabels.has(p.label) || extraFields.has(p.label)
152
+ );
153
+ const availableExtras = classified.filter(
154
+ (c) => !autoVisibleLabels.has(c.property.label) && !extraFields.has(c.property.label)
155
+ );
156
+
157
+ return (
158
+ <>
159
+ {visibleProps.map((propShape) => {
160
+ let required = propShape.minCount > 0;
161
+ let formName = propShape.label;
162
+ let label = propShape.label
163
+ .replace(/([A-Z])/g, ' $1')
164
+ .replace(/_/g, ' ')
165
+ .replace(/^./, (char) => char.toUpperCase());
166
+
167
+ let dataType;
168
+ let nodeKind = propShape.nodeKind;
169
+ if (nodeKind?.id === shacl.Literal.id) {
170
+ dataType = propShape.datatype?.id.split('#')[1];
171
+ }
172
+ if (nodeKind?.id === shacl.IRI.id) {
173
+ dataType = 'nodeShape';
174
+ }
175
+
176
+ return (
177
+ <motion.div
178
+ key={propShape.label}
179
+ variants={staggerItem}
180
+ transition={getTransition(reducedMotion)}
181
+ >
182
+ <DynamicForm
183
+ dataType={dataType}
184
+ formName={formName}
185
+ label={label}
186
+ placeholder={`Type ${label.toLowerCase()} here`}
187
+ required={required}
188
+ value={proxyOf[propShape.label]}
189
+ of={proxyOf as any}
190
+ property={propShape}
191
+ shape={null}
192
+ dependencyState={getDependencyState(propShape.label)}
193
+ />
194
+ </motion.div>
195
+ );
196
+ })}
197
+ {availableExtras.length > 0 && (
198
+ <motion.div
199
+ variants={staggerItem}
200
+ transition={getTransition(reducedMotion)}
201
+ className={style.addFieldSection}
202
+ >
203
+ <Select.Root
204
+ value=""
205
+ onValueChange={(label) =>
206
+ setExtraFields((prev) => new Set(prev).add(label))
207
+ }
208
+ >
209
+ <Select.Trigger aria-label="Add optional field">
210
+ <Select.Value placeholder="+ Add optional field..." />
211
+ </Select.Trigger>
212
+ <Select.Content>
213
+ <Select.Group>
214
+ {availableExtras.map((c) => (
215
+ <Select.Item key={c.property.label} value={c.property.label}>
216
+ {c.property.label
217
+ .replace(/([A-Z])/g, ' $1')
218
+ .replace(/_/g, ' ')
219
+ .replace(/^./, (char) => char.toUpperCase())}
220
+ </Select.Item>
221
+ ))}
222
+ </Select.Group>
223
+ </Select.Content>
224
+ </Select.Root>
225
+ </motion.div>
226
+ )}
227
+ </>
228
+ );
229
+ })()}
230
+ </motion.div>
231
+ </motion.div>
232
+
233
+ {saveError && (
234
+ <p className={style.saveError}>{saveError}</p>
235
+ )}
236
+
237
+ <motion.div
238
+ className={style.actions}
239
+ initial={{ opacity: 0, y: 10 }}
240
+ animate={{ opacity: 1, y: 0 }}
241
+ transition={{
242
+ duration: reducedMotion ? 0 : 0.3,
243
+ ease: [0.4, 0, 0.2, 1],
244
+ delay: reducedMotion ? 0 : 0.3,
245
+ }}
246
+ >
247
+ <Button variant="outline" onClick={onCancel} disabled={isSaving}>Cancel</Button>
248
+ <Button onClick={handleSave} disabled={isSaving}>
249
+ {isSaving ? (
250
+ <><Spinner size="small" /> Saving</>
251
+ ) : (
252
+ 'Save'
253
+ )}
254
+ </Button>
255
+ </motion.div>
256
+ </motion.div>
257
+ );
258
+ }
259
+
260
+ export default EditInstanceForms;
@@ -0,0 +1,25 @@
1
+ .Root {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ gap: var(--space-xs);
5
+ }
6
+
7
+ .inactive {
8
+ opacity: 0.55;
9
+ }
10
+
11
+ .toggle,
12
+ .remove {
13
+ all: unset;
14
+ cursor: pointer;
15
+ display: inline-flex;
16
+ align-items: center;
17
+ }
18
+
19
+ .remove {
20
+ opacity: 0.7;
21
+ }
22
+
23
+ .remove:hover {
24
+ opacity: 1;
25
+ }
@@ -0,0 +1,70 @@
1
+ import * as React from 'react';
2
+ import {Badge} from '@_linked/primitives/components/Badge';
3
+ import style from './FilterBadge.module.css';
4
+
5
+ export interface FilterBadgeProps {
6
+ label: string;
7
+ /** A filter can be muted without being removed, so a viewer can compare with and without. */
8
+ isActive?: boolean;
9
+ onToggle?: () => void;
10
+ onRemove?: () => void;
11
+ className?: string;
12
+ }
13
+
14
+ /**
15
+ * One active filter, as a chip that can be muted or removed.
16
+ *
17
+ * Two controls in one badge, which is why it is a component rather than a `<Badge>` with
18
+ * props: the body toggles and the trailing button removes, and each has to stop the other
19
+ * from firing. Getting that wrong means a viewer aiming for "remove" mutes it instead.
20
+ *
21
+ * An inactive filter is dimmed rather than hidden — the point of toggling instead of removing
22
+ * is to see the filter you have switched off.
23
+ */
24
+ export function FilterBadge({
25
+ label,
26
+ isActive = true,
27
+ onToggle,
28
+ onRemove,
29
+ className,
30
+ }: FilterBadgeProps) {
31
+ return (
32
+ <Badge
33
+ variant={isActive ? 'soft' : 'outline'}
34
+ color={isActive ? 'primary' : 'neutral'}
35
+ className={[style.Root, !isActive && style.inactive, className]
36
+ .filter(Boolean)
37
+ .join(' ')}
38
+ >
39
+ <button
40
+ type="button"
41
+ className={style.toggle}
42
+ onClick={(event) => {
43
+ event.stopPropagation();
44
+ onToggle?.();
45
+ }}
46
+ aria-pressed={isActive}
47
+ >
48
+ {label}
49
+ </button>
50
+ {onRemove && (
51
+ <button
52
+ type="button"
53
+ className={style.remove}
54
+ aria-label={`Remove filter ${label}`}
55
+ onClick={(event) => {
56
+ // Without this the toggle fires too, and the filter is muted on its way out.
57
+ event.stopPropagation();
58
+ onRemove();
59
+ }}
60
+ >
61
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
62
+ strokeWidth="2.5" strokeLinecap="round" aria-hidden="true">
63
+ <line x1="18" y1="6" x2="6" y2="18" />
64
+ <line x1="6" y1="6" x2="18" y2="18" />
65
+ </svg>
66
+ </button>
67
+ )}
68
+ </Badge>
69
+ );
70
+ }
@@ -0,0 +1,57 @@
1
+ /* ============================================================================
2
+ FormField.module.css
3
+
4
+ Basic form field wrapper component.
5
+ Uses semantic tokens from theme.css.
6
+ ============================================================================ */
7
+
8
+ /* === Root Container === */
9
+ .Root {
10
+ display: grid;
11
+ width: 100%;
12
+ max-width: 24rem;
13
+ align-items: center;
14
+ gap: var(--space-xs-plus);
15
+ }
16
+
17
+ .Root div > input {
18
+ width: 98%;
19
+ background-color: var(--field-bg);
20
+ border: 1px solid var(--field-border);
21
+ border-radius: var(--radius-field);
22
+ padding: var(--space-sm-plus) 0.75rem;
23
+ color: var(--field-text);
24
+ font-size: var(--typography-small-size);
25
+ transition: all var(--transition-fast);
26
+ }
27
+
28
+ .Root div > input:focus {
29
+ outline: none;
30
+ border-color: var(--field-border-focus);
31
+ box-shadow: 0 0 0 3px
32
+ color-mix(in srgb, var(--form-field-tertiary-500, var(--color-tertiary-500)) 15%, transparent);
33
+ }
34
+
35
+ .Root div > input::placeholder {
36
+ color: var(--field-text-placeholder);
37
+ }
38
+
39
+ /* ============================================================================
40
+ Dark Mode
41
+ ============================================================================ */
42
+
43
+ [data-theme='dark'] .Root div > input {
44
+ background-color: var(--field-bg);
45
+ border-color: var(--field-border);
46
+ color: var(--field-text);
47
+ }
48
+
49
+ [data-theme='dark'] .Root div > input:focus {
50
+ border-color: var(--field-border-focus);
51
+ box-shadow: 0 0 0 3px
52
+ color-mix(in srgb, var(--form-field-tertiary-500, var(--color-tertiary-500)) 20%, transparent);
53
+ }
54
+
55
+ [data-theme='dark'] .Root div > input::placeholder {
56
+ color: var(--field-text-placeholder);
57
+ }
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { Label } from '@_linked/primitives/components/Label';
3
+ import style from './FormField.module.css';
4
+ import { TextfieldEditor } from './TextfieldEditor.js'; // import input field that already connected with shape
5
+
6
+ interface FormFieldProps {
7
+ of?;
8
+ property?;
9
+ label?: string;
10
+ name?: string;
11
+ type: string;
12
+ placeholder?: string;
13
+ disabled?: boolean;
14
+ className?: string;
15
+ onChange?: (e: any) => void;
16
+ onBlur?: (e: any) => void;
17
+ value?: string | number;
18
+ required?: boolean;
19
+ defaultValue?;
20
+ }
21
+
22
+ const FormField = ({
23
+ of,
24
+ property,
25
+ label,
26
+ name,
27
+ type,
28
+ placeholder = '',
29
+ className,
30
+ onChange,
31
+ value,
32
+ required,
33
+ onBlur,
34
+ defaultValue,
35
+ ...restProps
36
+ }: FormFieldProps) => {
37
+ return (
38
+ <div className={style.Root}>
39
+ {label && <Label htmlFor={name}>{label}</Label>}
40
+ <TextfieldEditor
41
+ of={of}
42
+ type={type}
43
+ name={name}
44
+ placeholder={placeholder}
45
+ className={className}
46
+ onChange={onChange}
47
+ onBlur={onBlur}
48
+ value={value}
49
+ defaultValue={defaultValue}
50
+ required={required}
51
+ readOnly={false}
52
+ disabled={false}
53
+ {...restProps}
54
+ />
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export { FormField };
@@ -0,0 +1,3 @@
1
+ .Root {
2
+ display: block;
3
+ }
@@ -0,0 +1,82 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import type { PropertyShapeWire } from '@_linked/core/shapes/nodeShapeWire';
3
+ import style from './ImageValueEditor.module.css';
4
+ // import CustomMultiSelect from './CustomMultiSelect.js';
5
+ import { Shape } from '@_linked/core/shapes/Shape';
6
+ import { PropertyShape, NodeShape } from '@_linked/core/shapes/SHACL';
7
+ import { Thing } from '@_linked/schema/shapes/Thing';
8
+ import { getShapeClass } from '@_linked/core/utils/ShapeClass';
9
+ import { ShapeSet } from '@_linked/core/collections/ShapeSet';
10
+ import { ImageUploader } from '@_linked/schema/components/ImageUploader';
11
+ import { ImageObject } from '@_linked/schema/shapes/ImageObject';
12
+ import {
13
+ generateRandomName,
14
+ replaceLocalhostWithSiteRoot,
15
+ } from '../utils/helper.js';
16
+
17
+ // interface ImageValuesEditor {
18
+ // shape?: Shape;
19
+ // property?: PropertyShapeWire;
20
+ // potentialValues?: any[];
21
+ // createInstanceFor?;
22
+ // }
23
+
24
+ interface ImageValueEditorProps {
25
+ of?: Record<string, any>;
26
+ property?: PropertyShapeWire;
27
+ shape: NodeShape;
28
+ thumbnailWidth?: number;
29
+ potentialValue?;
30
+ }
31
+ const ImageValueEditor = ({
32
+ of,
33
+ property,
34
+ shape,
35
+ potentialValue,
36
+ thumbnailWidth,
37
+ }: ImageValueEditorProps) => {
38
+ // const potentialValues is shape[property.label];
39
+ let valueShape = property.valueShape;
40
+ let shapeClass = getShapeClass(shape.namedNode);
41
+ let [bool, setBool] = useState(false);
42
+ let forceUpdate = () => setBool(!bool);
43
+
44
+ if (!valueShape) {
45
+ console.log('No #valueshape defined in ' + property.uri);
46
+ valueShape = Thing.shape;
47
+ } else {
48
+ console.log(property.label + ' uses #valueShape' + valueShape.label);
49
+ }
50
+ let values = (getShapeClass(valueShape.namedNode) as any).getLocalInstances();
51
+
52
+ const onImageUploaded = async (dataUrl, format) => {
53
+ try {
54
+ const filename = generateRandomName(format);
55
+ // create an ImageObject and save it
56
+ const imageObj = await ImageObject.fromDataURL(dataUrl, filename);
57
+
58
+ if (imageObj) {
59
+ forceUpdate();
60
+ of[property.label] = imageObj;
61
+ imageObj.save();
62
+ } else {
63
+ console.error('Failed to create image object', imageObj);
64
+ }
65
+ } catch (err) {
66
+ console.error('Error on uploading image:', err);
67
+ }
68
+ };
69
+
70
+ return (
71
+ <div className={style.Root}>
72
+ <ImageUploader
73
+ // thumbnailHeight={100}
74
+ thumbnailWidth={thumbnailWidth}
75
+ onUploaded={onImageUploaded}
76
+ potentialValue={potentialValue}
77
+ />
78
+ </div>
79
+ );
80
+ };
81
+
82
+ export default ImageValueEditor;
@@ -0,0 +1,59 @@
1
+ .summary {
2
+ display: grid;
3
+ gap: 0.875rem;
4
+ min-width: min(32rem, 75vw);
5
+ }
6
+
7
+ .intro,
8
+ .empty,
9
+ .error,
10
+ .changed {
11
+ margin: 0;
12
+ }
13
+
14
+ .targets {
15
+ display: grid;
16
+ gap: 0.75rem;
17
+ max-height: 48vh;
18
+ overflow: auto;
19
+ }
20
+
21
+ .target {
22
+ display: grid;
23
+ gap: 0.45rem;
24
+ padding: 0.75rem;
25
+ border: 1px solid var(--surface-border, #d8dee8);
26
+ border-radius: 0.5rem;
27
+ background: var(--section-bg, #f8fafc);
28
+ }
29
+
30
+ .target strong,
31
+ .target small {
32
+ overflow-wrap: anywhere;
33
+ }
34
+
35
+ .impactList {
36
+ display: grid;
37
+ gap: 0.25rem;
38
+ margin: 0;
39
+ padding-left: 1.15rem;
40
+ }
41
+
42
+ .impactList li {
43
+ overflow-wrap: anywhere;
44
+ }
45
+
46
+ .warning {
47
+ color: var(--intent-warning-text, #8a4b08);
48
+ }
49
+
50
+ .error {
51
+ color: var(--color-error, #b42318);
52
+ }
53
+
54
+ .changed {
55
+ padding: 0.65rem;
56
+ border-radius: 0.4rem;
57
+ background: var(--intent-warning-bg, #fff4e5);
58
+ color: var(--intent-warning-text, #8a4b08);
59
+ }