@firecms/core 3.0.0-rc.1 → 3.0.0-rc.3

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 (96) hide show
  1. package/dist/components/HomePage/HomePageDnD.d.ts +2 -1
  2. package/dist/components/PropertyCollectionView.d.ts +23 -0
  3. package/dist/components/UserDisplay.d.ts +7 -0
  4. package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +12 -0
  5. package/dist/contexts/InternalUserManagementContext.d.ts +3 -0
  6. package/dist/core/EntityEditView.d.ts +10 -4
  7. package/dist/core/FireCMS.d.ts +0 -1
  8. package/dist/core/field_configs.d.ts +1 -1
  9. package/dist/form/EntityForm.d.ts +5 -2
  10. package/dist/form/components/LocalChangesMenu.d.ts +11 -0
  11. package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +12 -0
  12. package/dist/form/index.d.ts +2 -1
  13. package/dist/hooks/index.d.ts +2 -0
  14. package/dist/hooks/useCollapsedGroups.d.ts +9 -0
  15. package/dist/hooks/useInternalUserManagementController.d.ts +12 -0
  16. package/dist/index.es.js +1983 -650
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.umd.js +1981 -648
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/preview/components/UserPreview.d.ts +8 -0
  21. package/dist/preview/index.d.ts +1 -0
  22. package/dist/types/collections.d.ts +13 -0
  23. package/dist/types/entities.d.ts +5 -1
  24. package/dist/types/firecms.d.ts +15 -0
  25. package/dist/types/firecms_context.d.ts +16 -0
  26. package/dist/types/index.d.ts +1 -0
  27. package/dist/types/internal_user_management.d.ts +20 -0
  28. package/dist/types/plugins.d.ts +2 -0
  29. package/dist/types/properties.d.ts +41 -6
  30. package/dist/types/property_config.d.ts +1 -1
  31. package/dist/types/user.d.ts +1 -1
  32. package/dist/util/collections.d.ts +1 -0
  33. package/dist/util/entity_cache.d.ts +6 -1
  34. package/dist/util/make_properties_editable.d.ts +1 -2
  35. package/dist/util/objects.d.ts +1 -0
  36. package/dist/util/useStorageUploadController.d.ts +1 -0
  37. package/package.json +6 -6
  38. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +47 -47
  39. package/src/components/EntityCollectionTable/PropertyTableCell.tsx +12 -0
  40. package/src/components/EntityCollectionView/EntityCollectionView.tsx +6 -1
  41. package/src/components/EntityView.tsx +29 -40
  42. package/src/components/ErrorView.tsx +1 -1
  43. package/src/components/HomePage/DefaultHomePage.tsx +21 -34
  44. package/src/components/HomePage/HomePageDnD.tsx +143 -83
  45. package/src/components/HomePage/RenameGroupDialog.tsx +9 -3
  46. package/src/components/PropertyCollectionView.tsx +329 -0
  47. package/src/components/PropertyConfigBadge.tsx +2 -2
  48. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +2 -1
  49. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +1 -2
  50. package/src/components/UserDisplay.tsx +55 -0
  51. package/src/components/VirtualTable/fields/VirtualTableUserSelect.tsx +99 -0
  52. package/src/components/common/useColumnsIds.tsx +1 -8
  53. package/src/contexts/InternalUserManagementContext.tsx +4 -0
  54. package/src/core/EntityEditView.tsx +27 -14
  55. package/src/core/EntityEditViewFormActions.tsx +33 -18
  56. package/src/core/EntitySidePanel.tsx +9 -3
  57. package/src/core/FireCMS.tsx +22 -13
  58. package/src/core/field_configs.tsx +15 -1
  59. package/src/form/EntityForm.tsx +173 -42
  60. package/src/form/EntityFormActions.tsx +30 -15
  61. package/src/form/PropertyFieldBinding.tsx +4 -0
  62. package/src/form/components/ErrorFocus.tsx +22 -29
  63. package/src/form/components/LocalChangesMenu.tsx +144 -0
  64. package/src/form/field_bindings/UserSelectFieldBinding.tsx +94 -0
  65. package/src/form/index.tsx +5 -1
  66. package/src/hooks/index.tsx +3 -0
  67. package/src/hooks/useBrowserTitleAndIcon.tsx +1 -1
  68. package/src/hooks/useBuildNavigationController.tsx +104 -31
  69. package/src/hooks/useCollapsedGroups.ts +64 -0
  70. package/src/hooks/useFireCMSContext.tsx +6 -2
  71. package/src/hooks/useInternalUserManagementController.tsx +16 -0
  72. package/src/preview/PropertyPreview.tsx +8 -0
  73. package/src/preview/components/ReferencePreview.tsx +4 -2
  74. package/src/preview/components/UserPreview.tsx +27 -0
  75. package/src/preview/index.ts +1 -0
  76. package/src/preview/property_previews/ArrayPropertyPreview.tsx +1 -1
  77. package/src/preview/property_previews/MapPropertyPreview.tsx +2 -2
  78. package/src/preview/property_previews/NumberPropertyPreview.tsx +2 -2
  79. package/src/types/collections.ts +14 -0
  80. package/src/types/entities.ts +7 -1
  81. package/src/types/firecms.tsx +16 -0
  82. package/src/types/firecms_context.tsx +17 -0
  83. package/src/types/index.ts +1 -0
  84. package/src/types/internal_user_management.ts +24 -0
  85. package/src/types/plugins.tsx +3 -0
  86. package/src/types/properties.ts +45 -6
  87. package/src/types/property_config.tsx +1 -0
  88. package/src/types/user.ts +1 -1
  89. package/src/util/collections.ts +8 -0
  90. package/src/util/createFormexStub.tsx +4 -0
  91. package/src/util/entities.ts +1 -1
  92. package/src/util/entity_cache.ts +72 -53
  93. package/src/util/join_collections.ts +3 -3
  94. package/src/util/make_properties_editable.ts +0 -22
  95. package/src/util/objects.ts +40 -2
  96. package/src/util/useStorageUploadController.tsx +71 -34
@@ -0,0 +1,329 @@
1
+ import React from "react";
2
+ import { defaultBorderMixin, Typography } from "@firecms/ui";
3
+ import { PreviewSize, PropertyPreview } from "../preview";
4
+ import { ResolvedProperties, ResolvedProperty } from "../types";
5
+ import { getValueInPath } from "../util";
6
+
7
+ /**
8
+ * Build a readable label for a path and resolve the property
9
+ * Supports map and array (including arrays of maps)
10
+ */
11
+ export function buildPropertyLabelAndGetProperty(
12
+ properties: ResolvedProperties,
13
+ key: string
14
+ ): { label: string; property: ResolvedProperty | undefined } {
15
+ if (!key) return {
16
+ label: "",
17
+ property: undefined
18
+ };
19
+
20
+ // Parse "a[0].b.c[2]" -> ["a", 0, "b", "c", 2]
21
+ const segments: (string | number)[] = [];
22
+ const re = /([^[.\]]+)|\[(\d+)\]/g;
23
+ let m: RegExpExecArray | null;
24
+ while ((m = re.exec(key)) !== null) {
25
+ if (m[1] !== undefined) segments.push(m[1]);
26
+ else if (m[2] !== undefined) segments.push(Number(m[2]));
27
+ }
28
+
29
+ let currentProps: ResolvedProperties | undefined = properties;
30
+ let currentProp: ResolvedProperty | undefined;
31
+ let lastLabel = "";
32
+
33
+ const getArrayOfProp = (p?: ResolvedProperty): ResolvedProperty | undefined => {
34
+ if (!p || p.dataType !== "array") return undefined;
35
+ return Array.isArray(p.of) ? (p.of[0] as ResolvedProperty) : (p.of as ResolvedProperty | undefined);
36
+ };
37
+
38
+ for (const seg of segments) {
39
+ if (typeof seg === "number") {
40
+ // Last segment label should be the index itself
41
+ lastLabel = `[${seg}]`;
42
+
43
+ // Move schema context into the array element
44
+ if (currentProp?.dataType === "array") {
45
+ currentProp = getArrayOfProp(currentProp);
46
+ if (currentProp?.dataType === "map" && currentProp.properties) {
47
+ currentProps = currentProp.properties as ResolvedProperties;
48
+ } else {
49
+ currentProps = undefined;
50
+ }
51
+ } else {
52
+ // Index without array schema context
53
+ currentProp = undefined;
54
+ currentProps = undefined;
55
+ }
56
+ continue;
57
+ }
58
+
59
+ // seg is a string key
60
+ if (currentProps && (currentProps as any)[seg]) {
61
+ const nextProp = (currentProps as any)[seg] as ResolvedProperty;
62
+ currentProp = nextProp;
63
+ // Last segment label should be the property name (or the raw key)
64
+ lastLabel = nextProp.name || String(seg);
65
+
66
+ if (nextProp.dataType === "map" && nextProp.properties) {
67
+ currentProps = nextProp.properties as ResolvedProperties;
68
+ } else if (nextProp.dataType === "array") {
69
+ // Keep array prop; the next segment (index) will step into its element schema
70
+ currentProps = undefined;
71
+ } else {
72
+ currentProps = undefined;
73
+ }
74
+ } else {
75
+ // Unknown key or no schema context
76
+ currentProp = undefined;
77
+ currentProps = undefined;
78
+ lastLabel = String(seg);
79
+ }
80
+ }
81
+
82
+ return {
83
+ label: lastLabel,
84
+ property: currentProp
85
+ };
86
+ }
87
+
88
+ const pathEndsWithIndex = (p: string) => /\[\d+\]$/.test(p);
89
+
90
+ /**
91
+ * Improved simple layout for nested changes:
92
+ * - Map or Array-of-Map -> section header + indented rows
93
+ * - Leaf or Array-of-Primitives -> single row with label and value
94
+ */
95
+ export const PropertyCollectionView = ({
96
+ data,
97
+ properties,
98
+ baseKey = "",
99
+ suppressHeader = false,
100
+ size = "small"
101
+ }: {
102
+ data: any;
103
+ properties: ResolvedProperties;
104
+ baseKey?: string;
105
+ suppressHeader?: boolean;
106
+ size?: PreviewSize;
107
+ }) => {
108
+
109
+ const isTopLevel = !!baseKey && !baseKey.includes(".") && !baseKey.includes("[");
110
+
111
+ // Arrays
112
+ if (Array.isArray(data)) {
113
+ const {
114
+ label: arrayLabel,
115
+ property
116
+ } = baseKey
117
+ ? buildPropertyLabelAndGetProperty(properties, baseKey)
118
+ : {
119
+ label: "",
120
+ property: undefined as ResolvedProperty | undefined
121
+ };
122
+
123
+ const ofProp = property?.dataType === "array"
124
+ ? (Array.isArray(property.of) ? property.of[0] : property.of) as ResolvedProperty | undefined
125
+ : undefined;
126
+
127
+ const isArrayOfMaps = ofProp?.dataType === "map";
128
+ const isArrayOfPrimitives = property?.dataType === "array" && ofProp && ofProp.dataType !== "map";
129
+
130
+ // Array of primitives -> single row
131
+ if (baseKey && property && isArrayOfPrimitives) {
132
+ return (
133
+ <div
134
+ className={`grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`}>
135
+ <div className="col-span-4 pr-2">
136
+ <Typography variant="caption"
137
+ color={"secondary"}
138
+ component={"span"}
139
+ className="break-words">
140
+ {arrayLabel}
141
+ </Typography>
142
+ </div>
143
+ <div className="col-span-8">
144
+ <PropertyPreview propertyKey={baseKey}
145
+ value={data}
146
+ property={property}
147
+ size={size}/>
148
+ </div>
149
+ </div>
150
+ );
151
+ }
152
+
153
+ // Array of maps or unknown -> array header + combined item header (MapName [index]) then content
154
+ return (
155
+ <div className={`${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`}>
156
+ {baseKey && arrayLabel && !suppressHeader && (
157
+ <Typography variant="caption"
158
+ color={"secondary"}
159
+ component={"span"}>
160
+ {arrayLabel}
161
+ </Typography>
162
+ )}
163
+ <div className={baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : ""}>
164
+ {data.map((item, index) => {
165
+ if (item === null || item === undefined) return null;
166
+ const currentKey = baseKey ? `${baseKey}[${index}]` : `[${index}]`;
167
+
168
+ // Combined header text
169
+ const itemHeader = isArrayOfMaps && ofProp?.name
170
+ ? `${ofProp.name} [${index}]`
171
+ : `[${index}]`;
172
+
173
+ return (
174
+ <div key={currentKey} className="py-1">
175
+ <Typography variant="caption"
176
+ color={"secondary"}
177
+ component={"span"}>
178
+ {itemHeader}
179
+ </Typography>
180
+ <div className={`pl-4 mt-1 border-l ${defaultBorderMixin}`}>
181
+ <PropertyCollectionView
182
+ data={item}
183
+ properties={properties}
184
+ baseKey={currentKey}
185
+ suppressHeader={true} // don’t repeat the inner map header
186
+ size={size}
187
+ />
188
+ </div>
189
+ </div>
190
+ );
191
+ })}
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ // Objects (maps or plain objects)
198
+ if (typeof data === "object" && data !== null) {
199
+ const {
200
+ label,
201
+ property
202
+ } = baseKey
203
+ ? buildPropertyLabelAndGetProperty(properties, baseKey)
204
+ : {
205
+ label: "",
206
+ property: undefined as ResolvedProperty | undefined
207
+ };
208
+
209
+ // Non-map leaf-like object -> single row
210
+ if (baseKey && (!property || property.dataType !== "map" || !property.properties)) {
211
+ if (!property) return null;
212
+ return (
213
+ <div
214
+ className={`grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`}>
215
+ <div className="col-span-4 pr-2">
216
+ <Typography variant="caption"
217
+ color={"secondary"}
218
+ component={"span"}
219
+ className="break-words">
220
+ {label}
221
+ </Typography>
222
+ </div>
223
+ <div className="col-span-8">
224
+ <PropertyPreview propertyKey={baseKey}
225
+ value={data}
226
+ property={property}
227
+ size={size}/>
228
+ </div>
229
+ </div>
230
+ );
231
+ }
232
+
233
+ // Map with defined properties -> show map header only if not suppressed
234
+ const showMapHeader =
235
+ baseKey &&
236
+ !suppressHeader &&
237
+ property?.dataType === "map" &&
238
+ (property.name || !pathEndsWithIndex(baseKey));
239
+
240
+ const headerText = property?.name || label;
241
+
242
+ return (
243
+ <div className={`${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`}>
244
+ {showMapHeader && (
245
+ <Typography variant="caption"
246
+ color={"secondary"}
247
+ component={"span"}
248
+ >
249
+ {headerText}
250
+ </Typography>
251
+ )}
252
+ <div className={baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : ""}>
253
+ {Object.entries(data).map(([key, value]) => {
254
+ if (value === null || value === undefined) return null;
255
+ const currentKey = baseKey ? `${baseKey}.${key}` : key;
256
+ return (
257
+ <PropertyCollectionView
258
+ key={currentKey}
259
+ data={value}
260
+ properties={properties}
261
+ baseKey={currentKey}
262
+ size={size}
263
+ />
264
+ );
265
+ })}
266
+ </div>
267
+ </div>
268
+ );
269
+ }
270
+
271
+ // Primitives
272
+ if (baseKey) {
273
+ const {
274
+ label,
275
+ property
276
+ } = buildPropertyLabelAndGetProperty(properties, baseKey);
277
+ if (!property) return null;
278
+ return (
279
+ <div
280
+ className={`grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`}>
281
+ <div className="col-span-4 pr-2">
282
+ <Typography variant="caption"
283
+ color={"secondary"}
284
+ component={"span"}
285
+ className="break-words">
286
+ {label}
287
+ </Typography>
288
+ </div>
289
+ <div className="col-span-8">
290
+ <PropertyPreview propertyKey={baseKey}
291
+ value={data}
292
+ property={property}
293
+ size={size}/>
294
+ </div>
295
+ </div>
296
+ );
297
+ }
298
+
299
+ return null;
300
+ };
301
+
302
+ export function buildDataFromPaths(values: object, paths: string[]): object {
303
+ const result = {};
304
+ paths.forEach(path => {
305
+ const value = getValueInPath(values, path);
306
+ if (value === undefined) return;
307
+
308
+ // lodash.set would be perfect here
309
+ const segments = path.replace(/\[(\d+)\]/g, ".$1").split(".");
310
+ let current: any = result;
311
+ segments.forEach((segment, index) => {
312
+ if (index === segments.length - 1) {
313
+ current[segment] = value;
314
+ } else {
315
+ const nextSegment = segments[index + 1];
316
+ const isNextAnIndex = /^\d+$/.test(nextSegment);
317
+ if (!current[segment]) {
318
+ if (isNextAnIndex) {
319
+ current[segment] = [];
320
+ } else {
321
+ current[segment] = {};
322
+ }
323
+ }
324
+ current = current[segment];
325
+ }
326
+ });
327
+ });
328
+ return result;
329
+ }
@@ -9,7 +9,7 @@ export function PropertyConfigBadge({
9
9
  propertyConfig: PropertyConfig | undefined,
10
10
  disabled?: boolean
11
11
  }): React.ReactNode {
12
- const classes = "h-8 w-8 p-1 rounded-full shadow text-white " + (disabled ? "bg-surface-400 dark:bg-surface-600" : "");
12
+ const classes = "h-8 w-8 flex items-center justify-center rounded-full shadow text-white " + (disabled ? "bg-surface-400 dark:bg-surface-600" : "");
13
13
 
14
14
  const defaultPropertyConfig = typeof propertyConfig?.property === "object" ? getDefaultFieldConfig(propertyConfig.property) : undefined;
15
15
 
@@ -18,6 +18,6 @@ export function PropertyConfigBadge({
18
18
  style={{
19
19
  background: !disabled ? (propertyConfig?.color ?? defaultPropertyConfig?.color ?? "#888") : undefined
20
20
  }}>
21
- {propertyConfig?.Icon ? getIconForWidget(propertyConfig, "medium") : getIconForWidget(defaultPropertyConfig, "medium")}
21
+ {propertyConfig?.Icon ? getIconForWidget(propertyConfig, "small") : getIconForWidget(defaultPropertyConfig, "small")}
22
22
  </div>
23
23
  }
@@ -55,7 +55,7 @@ export function DateTimeFilterField({
55
55
  }
56
56
 
57
57
  setOperation(op);
58
- setInternalValue(newValue === null ? undefined : newValue);
58
+ setInternalValue(newValue);
59
59
 
60
60
  const hasNewValue = newValue !== null && Array.isArray(newValue)
61
61
  ? newValue.length > 0
@@ -96,6 +96,7 @@ export function DateTimeFilterField({
96
96
  mode={mode}
97
97
  size={"large"}
98
98
  locale={locale}
99
+ disabled={internalValue === null}
99
100
  value={internalValue ?? undefined}
100
101
  onChange={(dateValue: Date | null) => {
101
102
  updateFilter(operation, dateValue === null ? undefined : dateValue);
@@ -123,7 +123,7 @@ export function StringNumberFilterField({
123
123
  : evt.target.value;
124
124
  updateFilter(operation, val);
125
125
  }}
126
- endAdornment={internalValue && <IconButton
126
+ endAdornment={internalValue !== undefined && internalValue != null && <IconButton
127
127
  onClick={(e) => updateFilter(operation, undefined)}>
128
128
  <CloseIcon/>
129
129
  </IconButton>}
@@ -139,7 +139,6 @@ export function StringNumberFilterField({
139
139
  updateFilter(operation, dataType === "number" ? parseInt(value as string) : value as string)
140
140
  }}
141
141
  endAdornment={internalValue && <IconButton
142
- className="absolute right-2 top-3"
143
142
  onClick={(e) => updateFilter(operation, undefined)}>
144
143
  <CloseIcon/>
145
144
  </IconButton>}
@@ -0,0 +1,55 @@
1
+ import { User } from "../types";
2
+ import { AccountCircleIcon, cls, defaultBorderMixin } from "@firecms/ui";
3
+ import { EmptyValue } from "../preview";
4
+
5
+ /**
6
+ * Component to render a single user with name and email
7
+ */
8
+ export function UserDisplay({
9
+ user,
10
+ }: { user: User | null }) {
11
+ if (!user) {
12
+ return <EmptyValue/>;
13
+ }
14
+
15
+ const avatarSizeClass = "w-6 h-6";
16
+
17
+ return (
18
+ <div className={cls(
19
+ "inline-flex items-center gap-4 px-2 py-1 rounded-xl",
20
+ "bg-surface-accent-100 dark:bg-surface-accent-800",
21
+ "border",
22
+ defaultBorderMixin
23
+ )}>
24
+ {user.photoURL ? (
25
+ <img
26
+ src={user.photoURL}
27
+ alt={user.displayName || user.email || "User"}
28
+ className={cls(
29
+ "rounded-full object-cover",
30
+ avatarSizeClass
31
+ )}
32
+ />
33
+ ) : (
34
+ <AccountCircleIcon
35
+ className={cls(
36
+ "text-text-secondary dark:text-text-secondary-dark",
37
+ avatarSizeClass
38
+ )}
39
+ />
40
+ )}
41
+ <div className="flex flex-col min-w-0">
42
+ <span className={cls("font-regular truncate", "text-sm")}>
43
+ {user.displayName || user.email || "-"}
44
+ </span>
45
+ {user.displayName && user.email && (
46
+ <span className={cls("text-text-secondary dark:text-text-secondary-dark truncate",
47
+ "text-xs"
48
+ )}>
49
+ {user.email}
50
+ </span>
51
+ )}
52
+ </div>
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,99 @@
1
+ import React, { useCallback, useEffect } from "react";
2
+ import { MultiSelect, MultiSelectItem, Select, SelectItem } from "@firecms/ui";
3
+ import { useInternalUserManagementController } from "../../../hooks";
4
+ import { UserDisplay } from "../../UserDisplay";
5
+
6
+ export function VirtualTableUserSelect(props: {
7
+ name: string;
8
+ error: Error | undefined;
9
+ multiple: boolean;
10
+ disabled: boolean;
11
+ small: boolean;
12
+ internalValue: string | string[] | undefined;
13
+ updateValue: (newValue: (string | string[] | null)) => void;
14
+ focused: boolean;
15
+ onBlur?: React.FocusEventHandler<HTMLInputElement | HTMLTextAreaElement>;
16
+ }) {
17
+
18
+ const {
19
+ internalValue,
20
+ disabled,
21
+ small,
22
+ focused,
23
+ updateValue,
24
+ multiple
25
+ } = props;
26
+
27
+ const { users, getUser } = useInternalUserManagementController();
28
+
29
+ const validValue = (Array.isArray(internalValue) && multiple) ||
30
+ (!Array.isArray(internalValue) && !multiple);
31
+
32
+ const ref = React.useRef<HTMLButtonElement>(null);
33
+ useEffect(() => {
34
+ if (ref.current && focused) {
35
+ ref.current?.focus({ preventScroll: true });
36
+ }
37
+ }, [focused, ref]);
38
+
39
+ const onChange = useCallback((updatedValue: string | string[]) => {
40
+ if (!updatedValue) {
41
+ updateValue(null);
42
+ } else {
43
+ updateValue(updatedValue);
44
+ }
45
+ }, [updateValue]);
46
+
47
+ const renderValue = (userId: string) => {
48
+ const user = getUser(userId);
49
+ return <UserDisplay user={user} />;
50
+ };
51
+
52
+ return (
53
+ multiple
54
+ ? <MultiSelect
55
+ inputRef={ref}
56
+ className="w-full h-full p-0 bg-transparent"
57
+ position={"item-aligned"}
58
+ disabled={disabled}
59
+ includeClear={false}
60
+ useChips={false}
61
+ value={validValue
62
+ ? (internalValue as string[])
63
+ : ([])}
64
+ onValueChange={onChange}>
65
+ {users?.map((user) => (
66
+ <MultiSelectItem
67
+ key={user.uid}
68
+ value={user.uid}>
69
+ <UserDisplay
70
+ user={user} />
71
+ </MultiSelectItem>
72
+ ))}
73
+ </MultiSelect>
74
+ : <Select
75
+ inputRef={ref}
76
+ size={"large"}
77
+ fullWidth={true}
78
+ className="w-full h-full p-0 bg-transparent"
79
+ position={"item-aligned"}
80
+ disabled={disabled}
81
+ padding={false}
82
+ value={validValue
83
+ ? internalValue as string
84
+ : ""}
85
+ onValueChange={onChange}
86
+ renderValue={renderValue}>
87
+ {users?.map((user) => (
88
+ <SelectItem
89
+ key={user.uid}
90
+ value={user.uid}>
91
+ <UserDisplay
92
+ user={user}/>
93
+ </SelectItem>
94
+ ))}
95
+ </Select>
96
+
97
+ );
98
+ }
99
+
@@ -8,14 +8,7 @@ export const COLLECTION_GROUP_PARENT_ID = "collectionGroupParent";
8
8
  export function useColumnIds<M extends Record<string, any>>(collection: ResolvedEntityCollection<M>, includeSubcollections: boolean): PropertyColumnConfig[] {
9
9
  return useMemo(() => {
10
10
  if (collection.propertiesOrder) {
11
- const propertyColumnConfigs = hideAndExpandKeys(collection, collection.propertiesOrder);
12
- if (collection.collectionGroup) {
13
- propertyColumnConfigs.push({
14
- key: COLLECTION_GROUP_PARENT_ID,
15
- disabled: true
16
- });
17
- }
18
- return propertyColumnConfigs;
11
+ return hideAndExpandKeys(collection, collection.propertiesOrder);
19
12
  }
20
13
  return getDefaultColumnKeys(collection, includeSubcollections);
21
14
  }, [collection, includeSubcollections]);
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { InternalUserManagement, NavigationController } from "../types";
3
+
4
+ export const InternalUserManagementContext = React.createContext<InternalUserManagement<any>>({} as InternalUserManagement);