@_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,295 @@
1
+ import { Button } from '@_linked/primitives/components/Button';
2
+ import { Checkbox } from '@_linked/primitives/components/Checkbox';
3
+ import { Drawer } from '@_linked/primitives/components/Drawer';
4
+ import type { PropertyShapeWire, NodeShapeWire } from '@_linked/core/shapes/nodeShapeWire';
5
+ import { shacl } from '@_linked/core/ontologies/shacl';
6
+ import React from 'react';
7
+ import { getPropertyUiType } from '../shape/propertyTypes.js';
8
+ import { humanizeEnumLabel, enumOptions } from '../shape/nodeDisplay.js';
9
+ import { Icons } from '@_linked/icons';
10
+ import { Spinner } from '@_linked/primitives/components/Spinner';
11
+ import style from './BatchEditDrawer.module.css';
12
+
13
+ interface BatchEditDrawerProps {
14
+ isOpen: boolean;
15
+ onClose: () => void;
16
+ shape: NodeShapeWire;
17
+ properties: Record<string, PropertyShapeWire>;
18
+ selectedCount: number;
19
+ onApply: (changes: Record<string, any>) => Promise<void>;
20
+ isLoading?: boolean;
21
+ }
22
+
23
+ interface FieldState {
24
+ enabled: boolean;
25
+ value: any;
26
+ }
27
+
28
+ function BatchEditDrawer({
29
+ isOpen,
30
+ onClose,
31
+ shape,
32
+ properties,
33
+ selectedCount,
34
+ onApply,
35
+ isLoading = false,
36
+ }: BatchEditDrawerProps) {
37
+ const [fields, setFields] = React.useState<Record<string, FieldState>>({});
38
+ const [step, setStep] = React.useState<'edit' | 'review'>('edit');
39
+
40
+ // Reset state when drawer opens/closes
41
+ React.useEffect(() => {
42
+ if (isOpen) {
43
+ setFields({});
44
+ setStep('edit');
45
+ }
46
+ }, [isOpen]);
47
+
48
+ // Get editable properties (skip id, rdf:type, and IRI/reference fields for V1)
49
+ const editableProperties = React.useMemo(() => {
50
+ return Object.entries(properties).filter(([label, prop]) => {
51
+ if (label === 'id') return false;
52
+ if (prop.id === 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type') return false;
53
+ const nodeKind = prop.nodeKind?.id;
54
+ // Skip IRI/reference fields — too complex for batch V1
55
+ if (
56
+ nodeKind === shacl.IRI.id ||
57
+ nodeKind === shacl.BlankNode.id ||
58
+ nodeKind === shacl.BlankNodeOrIRI.id
59
+ ) {
60
+ return false;
61
+ }
62
+ return true;
63
+ });
64
+ }, [properties]);
65
+
66
+ const toggleField = (label: string) => {
67
+ setFields((prev) => ({
68
+ ...prev,
69
+ [label]: {
70
+ enabled: !prev[label]?.enabled,
71
+ value: prev[label]?.value ?? '',
72
+ },
73
+ }));
74
+ };
75
+
76
+ const updateFieldValue = (label: string, value: any) => {
77
+ setFields((prev) => ({
78
+ ...prev,
79
+ [label]: { enabled: true, value },
80
+ }));
81
+ };
82
+
83
+ const enabledFields = Object.entries(fields).filter(([, f]) => f.enabled);
84
+ const hasChanges = enabledFields.length > 0;
85
+
86
+ const handleApply = () => {
87
+ const changes: Record<string, any> = {};
88
+ for (const [label, fieldState] of enabledFields) {
89
+ changes[label] = fieldState.value;
90
+ }
91
+ onApply(changes);
92
+ };
93
+
94
+ const humanizeLabel = (label: string) =>
95
+ label
96
+ .replace(/([A-Z])/g, ' $1')
97
+ .replace(/_/g, ' ')
98
+ .replace(/^./, (c) => c.toUpperCase());
99
+
100
+ const formatValue = (value: any, prop: PropertyShapeWire) => {
101
+ if (value === '' || value === null || value === undefined) return '(empty)';
102
+ const uiType = getPropertyUiType(prop);
103
+ if (uiType === 'boolean') return value === 'true' || value === true ? 'Yes' : 'No';
104
+ if (uiType === 'enum-literal' || uiType === 'enum-iri') {
105
+ const match = enumOptions(prop).find((v) => v.id === value);
106
+ if (match) return match.label;
107
+ }
108
+ return String(value);
109
+ };
110
+
111
+ const renderFieldInput = (label: string, prop: PropertyShapeWire) => {
112
+ const uiType = getPropertyUiType(prop);
113
+ const fieldState = fields[label];
114
+ const value = fieldState?.value ?? '';
115
+ const disabled = !fieldState?.enabled;
116
+
117
+ switch (uiType) {
118
+ case 'boolean':
119
+ return (
120
+ <select
121
+ className={style.input}
122
+ disabled={disabled}
123
+ value={value}
124
+ onChange={(e) => updateFieldValue(label, e.target.value)}
125
+ >
126
+ <option value="">— Select —</option>
127
+ <option value="true">Yes</option>
128
+ <option value="false">No</option>
129
+ </select>
130
+ );
131
+
132
+ case 'enum-literal':
133
+ case 'enum-iri':
134
+ return (
135
+ <select
136
+ className={style.input}
137
+ disabled={disabled}
138
+ value={value}
139
+ onChange={(e) => updateFieldValue(label, e.target.value)}
140
+ >
141
+ <option value="">— Select —</option>
142
+ {enumOptions(prop).map((opt) => (
143
+ <option key={opt.id} value={opt.id}>
144
+ {opt.label}
145
+ </option>
146
+ ))}
147
+ </select>
148
+ );
149
+
150
+ case 'number':
151
+ return (
152
+ <input
153
+ type="number"
154
+ className={style.input}
155
+ disabled={disabled}
156
+ value={value}
157
+ placeholder="Enter number..."
158
+ onChange={(e) => updateFieldValue(label, e.target.value ? Number(e.target.value) : '')}
159
+ />
160
+ );
161
+
162
+ case 'date':
163
+ return (
164
+ <input
165
+ type="date"
166
+ className={style.input}
167
+ disabled={disabled}
168
+ value={value}
169
+ onChange={(e) => updateFieldValue(label, e.target.value)}
170
+ />
171
+ );
172
+
173
+ case 'datetime':
174
+ return (
175
+ <input
176
+ type="datetime-local"
177
+ className={style.input}
178
+ disabled={disabled}
179
+ value={value}
180
+ onChange={(e) => updateFieldValue(label, e.target.value)}
181
+ />
182
+ );
183
+
184
+ case 'text':
185
+ case 'url':
186
+ default:
187
+ return (
188
+ <input
189
+ type={uiType === 'url' ? 'url' : 'text'}
190
+ className={style.input}
191
+ disabled={disabled}
192
+ value={value}
193
+ placeholder={`Enter ${uiType === 'url' ? 'URL' : 'text'}...`}
194
+ onChange={(e) => updateFieldValue(label, e.target.value)}
195
+ />
196
+ );
197
+ }
198
+ };
199
+
200
+ return (
201
+ <Drawer.Root open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }} direction="right" handleOnly>
202
+ <Drawer.Content hideHandle className={style.drawer}>
203
+ <div className={style.drawerContent}>
204
+ <Drawer.Header className={style.header}>
205
+ <Drawer.Title>Edit {selectedCount} {shape?.label || 'item'}{selectedCount > 1 ? 's' : ''}</Drawer.Title>
206
+ <Drawer.Description>
207
+ {step === 'edit'
208
+ ? 'Toggle on the fields you want to change. Only enabled fields will be updated.'
209
+ : 'Review your changes before applying.'}
210
+ </Drawer.Description>
211
+ </Drawer.Header>
212
+
213
+ <div className={style.body}>
214
+ {step === 'edit' ? (
215
+ <div className={style.fieldList}>
216
+ {editableProperties.map(([label, prop]) => {
217
+ const isEnabled = fields[label]?.enabled ?? false;
218
+ return (
219
+ <div key={label} className={style.fieldRow}>
220
+ <div className={style.fieldToggle}>
221
+ <Checkbox
222
+ checked={isEnabled}
223
+ onCheckedChange={() => toggleField(label)}
224
+ />
225
+ <label
226
+ className={style.fieldLabel}
227
+ onClick={() => toggleField(label)}
228
+ >
229
+ {humanizeLabel(label)}
230
+ </label>
231
+ </div>
232
+ <div className={isEnabled ? style.fieldInput : style.fieldInputDisabled}>
233
+ {renderFieldInput(label, prop)}
234
+ </div>
235
+ </div>
236
+ );
237
+ })}
238
+ {editableProperties.length === 0 && (
239
+ <p className={style.noFields}>No editable fields available for batch editing.</p>
240
+ )}
241
+ </div>
242
+ ) : (
243
+ <div className={style.reviewList}>
244
+ <p className={style.reviewIntro}>
245
+ The following changes will be applied to {selectedCount} item{selectedCount > 1 ? 's' : ''}:
246
+ </p>
247
+ {enabledFields.map(([label]) => {
248
+ const prop = properties[label];
249
+ const value = fields[label].value;
250
+ return (
251
+ <div key={label} className={style.reviewItem}>
252
+ <span className={style.reviewField}>{humanizeLabel(label)}</span>
253
+ <Icons.ChevronRight width={12} height={12} />
254
+ <span className={style.reviewValue}>
255
+ {formatValue(value, prop)}
256
+ </span>
257
+ </div>
258
+ );
259
+ })}
260
+ </div>
261
+ )}
262
+ </div>
263
+
264
+ <div className={style.footer}>
265
+ {step === 'edit' ? (
266
+ <>
267
+ <Button variant="outline" onClick={onClose} disabled={isLoading}>
268
+ Cancel
269
+ </Button>
270
+ <Button onClick={() => setStep('review')} disabled={!hasChanges || isLoading}>
271
+ Review Changes ({enabledFields.length})
272
+ </Button>
273
+ </>
274
+ ) : (
275
+ <>
276
+ <Button variant="outline" onClick={() => setStep('edit')} disabled={isLoading}>
277
+ Back
278
+ </Button>
279
+ <Button onClick={handleApply} disabled={isLoading}>
280
+ {isLoading ? (
281
+ <><Spinner size="small" /> Updating {selectedCount} items...</>
282
+ ) : (
283
+ `Apply to ${selectedCount} items`
284
+ )}
285
+ </Button>
286
+ </>
287
+ )}
288
+ </div>
289
+ </div>
290
+ </Drawer.Content>
291
+ </Drawer.Root>
292
+ );
293
+ }
294
+
295
+ export default BatchEditDrawer;
@@ -0,0 +1,6 @@
1
+ .Root {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 0.5rem;
5
+ /* padding: 10px;*/
6
+ }
@@ -0,0 +1,86 @@
1
+ import React, { useState } from 'react';
2
+ import type { PropertyShapeWire } from '@_linked/core/shapes/nodeShapeWire';
3
+ import { Checkbox } from '@_linked/primitives/components/Checkbox';
4
+ import style from './CheckboxEditor.module.css';
5
+
6
+ export interface CheckboxEditorProps {
7
+ onCheckedChange?: (e) => void;
8
+ of?: Record<string, any>;
9
+ property?: PropertyShapeWire;
10
+
11
+ /**
12
+ * Converts the value returned by the shape, to the input string for the checkbox
13
+ * @param shapePropertyValue
14
+ * @returns
15
+ */
16
+ preConvertValueFn?: (shapePropertyValue: any) => string;
17
+ /**
18
+ * Converts the value of the checkbox to a value that can be provided to the set method of the shape
19
+ * @param inputValue
20
+ * @returns
21
+ */
22
+ postConvertValueFn?: (inputValue: string) => any;
23
+ }
24
+
25
+ export const CheckboxEditor = ({
26
+ onCheckedChange,
27
+ of,
28
+ property,
29
+ preConvertValueFn,
30
+ postConvertValueFn,
31
+ ...restProps
32
+ }: CheckboxEditorProps) => {
33
+ const currentValues = of[property.label];
34
+
35
+ const onChangeHandler = (item, checked) => {
36
+ if (!of) return;
37
+
38
+ if (property.in) {
39
+ if (checked) {
40
+ currentValues.add(item);
41
+ } else {
42
+ currentValues.delete(item);
43
+ }
44
+ } else {
45
+ of[property.label] = checked;
46
+ }
47
+
48
+ if (onCheckedChange) {
49
+ onCheckedChange(currentValues);
50
+ }
51
+ };
52
+
53
+ return (
54
+ <>
55
+ {property.in ? (
56
+ property.in.map((item, i) => {
57
+ return (
58
+ <label className={style.Root}>
59
+ <Checkbox
60
+ value={of[property.label]}
61
+ onCheckedChange={onChangeHandler.bind(this, item)}
62
+ defaultChecked={currentValues.has(item)}
63
+ {...restProps}
64
+ />
65
+ TODO: {item.toString()}
66
+ </label>
67
+ );
68
+ })
69
+ ) : (
70
+ <div>checkbox usage unimplemented</div>
71
+ // <Checkbox
72
+ // value={of[property.label]}
73
+ // onCheckedChange={onChangeHandler}
74
+ // defaultChecked={
75
+ // of
76
+ // ? preConvertValueFn
77
+ // ? preConvertValueFn(of[property.label])
78
+ // : of[property.label]
79
+ // : ''
80
+ // }
81
+ // {...restProps}
82
+ // />
83
+ )}
84
+ </>
85
+ );
86
+ };
@@ -0,0 +1,291 @@
1
+ /* ============================================================================
2
+ CustomMultiSelect.module.css
3
+
4
+ Multi-select dropdown with search and create new functionality.
5
+ Uses semantic tokens from theme.css.
6
+ ============================================================================ */
7
+
8
+ /* === Root Container === */
9
+ .Root {
10
+ display: flex;
11
+ gap: var(--space-sm);
12
+ align-items: center;
13
+ position: relative;
14
+ width: 100%;
15
+ }
16
+
17
+ /* === Table icon === */
18
+ .tableIcon {
19
+ cursor: pointer;
20
+ color: var(--custom-multi-select-gray-500, var(--color-gray-500));
21
+ flex-shrink: 0;
22
+ display: flex;
23
+ align-items: center;
24
+ transition: color var(--transition-fast);
25
+ }
26
+
27
+ .tableIcon:hover {
28
+ color: var(--custom-multi-select-tertiary-500, var(--color-tertiary-500));
29
+ }
30
+
31
+ /* === Selected Options Container === */
32
+ .selectedOptionsAndChevronIconContainer {
33
+ display: flex;
34
+ gap: var(--space-sm);
35
+ align-items: center;
36
+ width: 100%;
37
+ padding: var(--surface-gap) 0;
38
+ overflow-x: hidden;
39
+ }
40
+
41
+ .selectedOptionsContainer {
42
+ position: relative;
43
+ border: 1px solid var(--field-border);
44
+ border-radius: var(--radius-field);
45
+ width: 100%;
46
+ padding: 0;
47
+ background-color: var(--field-bg);
48
+ }
49
+
50
+ .selectedOptionsContainer.expanded {
51
+ overflow-y: scroll;
52
+ flex-wrap: wrap;
53
+ }
54
+
55
+ /* === Chips Container === */
56
+ .noBorderContainer {
57
+ display: flex;
58
+ flex-wrap: wrap;
59
+ max-height: 196px;
60
+ padding: var(--space-sm) 0 var(--space-sm) var(--space-sm);
61
+ gap: var(--space-sm);
62
+ width: 93%;
63
+ overflow-y: hidden;
64
+ border-color: transparent;
65
+ }
66
+
67
+ .noBorderContainer.expanded {
68
+ overflow-y: scroll;
69
+ }
70
+
71
+ /* === Chevron Icon === */
72
+ .chevronDownIcon {
73
+ opacity: 0.4;
74
+ transition: transform var(--transition-fast), opacity var(--transition-fast);
75
+ position: absolute;
76
+ right: 0.25rem;
77
+ top: 51%;
78
+ transform: translate(-40px, -50%);
79
+ color: var(--custom-multi-select-gray-500, var(--color-gray-500));
80
+ }
81
+
82
+ .chevronDownIcon.open {
83
+ opacity: 0.7;
84
+ transform: rotate(180deg) translate(40px, 0.75rem);
85
+ }
86
+
87
+ /* === Option chips (selected values) === */
88
+ .optionButton {
89
+ border-radius: var(--radius-full);
90
+ display: inline-flex;
91
+ align-items: center;
92
+ gap: var(--space-xs);
93
+ }
94
+
95
+ /* === Selector placeholder === */
96
+ .selectorButton {
97
+ color: var(--field-text-placeholder);
98
+ font-size: var(--typography-small-size);
99
+ }
100
+
101
+ /* ============================================================================
102
+ Dropdown (Popover.Content)
103
+ ============================================================================ */
104
+
105
+
106
+ .options {
107
+ width: 100%;
108
+ padding: 0;
109
+ background-color: var(--surface-bg);
110
+ border: 1px solid var(--surface-border);
111
+ border-radius: var(--radius-field);
112
+ overflow: hidden;
113
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
114
+ }
115
+
116
+ /* === Search wrapper === */
117
+ .searchWrapper {
118
+ position: relative;
119
+ border-bottom: 1px solid var(--surface-border);
120
+ }
121
+
122
+ /* === Command input === */
123
+ input[cmdk-input] {
124
+ width: 100%;
125
+ background-color: var(--field-bg);
126
+ color: var(--field-text);
127
+ border: none;
128
+ padding: var(--space-sm-plus) var(--space-xl) var(--space-sm-plus) var(--space-md);
129
+ font-size: var(--typography-small-size);
130
+ }
131
+
132
+ input[cmdk-input]::placeholder {
133
+ color: var(--field-text-placeholder);
134
+ }
135
+
136
+ /* === Reset search icon === */
137
+ .resetSearchInputIcon {
138
+ position: absolute;
139
+ right: var(--space-md);
140
+ top: 50%;
141
+ transform: translateY(-50%);
142
+ opacity: 0.5;
143
+ color: var(--custom-multi-select-gray-500, var(--color-gray-500));
144
+ cursor: pointer;
145
+ transition: all var(--transition-fast);
146
+ }
147
+
148
+ .resetSearchInputIcon:hover {
149
+ opacity: 1;
150
+ color: var(--custom-multi-select-tertiary-500, var(--color-tertiary-500));
151
+ }
152
+
153
+ /* === Command root / list === */
154
+ .commandRoot {
155
+ position: relative;
156
+ max-height: 220px;
157
+ overflow-y: auto;
158
+ }
159
+
160
+ /* === Options group === */
161
+ .commandGroup {
162
+ padding: var(--space-xs) 0;
163
+ }
164
+
165
+ /* === Individual option item === */
166
+ .option {
167
+ display: flex;
168
+ align-items: center;
169
+ justify-content: space-between;
170
+ padding: var(--space-sm-plus) var(--space-md);
171
+ font-size: var(--typography-small-size);
172
+ cursor: pointer;
173
+ gap: var(--space-sm);
174
+ }
175
+
176
+ .optionLabel {
177
+ flex: 1;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ white-space: nowrap;
181
+ }
182
+
183
+ .optionCheck {
184
+ flex-shrink: 0;
185
+ color: var(--custom-multi-select-tertiary-500, var(--color-tertiary-500));
186
+ }
187
+
188
+ .optionSelected {
189
+ opacity: 0.6;
190
+ }
191
+
192
+ /* === Empty result === */
193
+ .emptyResult {
194
+ text-align: left;
195
+ padding: var(--space-sm-plus) var(--space-md);
196
+ font-size: var(--typography-small-size);
197
+ color: var(--custom-multi-select-gray-500, var(--color-gray-500));
198
+ }
199
+
200
+ /* === Load More sentinel (infinite scroll) === */
201
+ .loadMoreSentinel {
202
+ display: flex;
203
+ justify-content: center;
204
+ align-items: center;
205
+ padding: var(--space-sm) 0;
206
+ min-height: 32px;
207
+ }
208
+
209
+ /* === "Create New" section === */
210
+ .createNewContainer {
211
+ border-top: 1px solid var(--surface-border);
212
+ padding: 0;
213
+ }
214
+
215
+ .modalButton {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: var(--space-sm);
219
+ width: 100%;
220
+ border: none;
221
+ background: transparent;
222
+ padding: var(--space-sm-plus) var(--space-md);
223
+ font-size: var(--typography-small-size);
224
+ color: var(--custom-multi-select-tertiary-500, var(--color-tertiary-500));
225
+ cursor: pointer;
226
+ text-align: left;
227
+ transition: background var(--transition-fast);
228
+ }
229
+
230
+ .modalButton:hover {
231
+ background: color-mix(in srgb, var(--custom-multi-select-tertiary-500, var(--color-tertiary-500)) 8%, transparent);
232
+ }
233
+
234
+ /* === View Less text (expand/collapse) === */
235
+ .viewLessText {
236
+ color: var(--custom-multi-select-gray-500, var(--color-gray-500));
237
+ font-size: var(--typography-small-size);
238
+ padding: 0 var(--space-sm);
239
+ position: absolute;
240
+ bottom: 3px;
241
+ left: 15px;
242
+ border-radius: var(--radius-full);
243
+ background: var(--surface-bg);
244
+ cursor: pointer;
245
+ transition: all var(--transition-fast);
246
+ }
247
+
248
+ .viewLessText:hover {
249
+ color: var(--custom-multi-select-tertiary-500, var(--color-tertiary-500));
250
+ }
251
+
252
+ /* === Dialog === */
253
+ .dialogRoot {
254
+ background-color: var(--modal-bg, var(--surface-bg));
255
+ border: 1px solid var(--modal-border, var(--surface-border));
256
+ border-radius: var(--radius-modal);
257
+ min-width: min(90vw, 680px);
258
+ max-height: 90vh;
259
+ display: flex;
260
+ flex-direction: column;
261
+ }
262
+
263
+ .dialogHeader {
264
+ padding: var(--space-xl) var(--space-xl) var(--space-md);
265
+ border-bottom: 1px solid var(--surface-border);
266
+ flex-shrink: 0;
267
+ }
268
+
269
+ .dialogContent {
270
+ overflow-y: auto;
271
+ flex: 1;
272
+ padding: 0 var(--space-md) var(--space-md);
273
+ }
274
+
275
+ /* ============================================================================
276
+ Dark Mode
277
+ ============================================================================ */
278
+
279
+ /* All surfaces use semantic tokens that auto-switch in dark mode */
280
+
281
+ [data-theme='dark'] .modalButton {
282
+ color: var(--custom-multi-select-tertiary-400, var(--color-tertiary-400));
283
+ }
284
+
285
+ [data-theme='dark'] .viewLessText {
286
+ color: var(--custom-multi-select-gray-400, var(--color-gray-400));
287
+ }
288
+
289
+ [data-theme='dark'] .options {
290
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5);
291
+ }