@rebasepro/app 0.17.3 → 0.18.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 (185) hide show
  1. package/README.md +4 -1
  2. package/dist/components/index.d.ts +0 -3
  3. package/dist/contexts/AuthControllerContext.d.ts +2 -1
  4. package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
  5. package/dist/contexts/DialogsProvider.d.ts +2 -1
  6. package/dist/contexts/RebaseDataContext.d.ts +10 -1
  7. package/dist/contexts/StorageSourceContext.d.ts +11 -1
  8. package/dist/core/RebaseProps.d.ts +2 -12
  9. package/dist/debug/index.d.ts +19 -0
  10. package/dist/debug.js +3631 -0
  11. package/dist/hooks/data/delete.d.ts +16 -7
  12. package/dist/hooks/data/save.d.ts +18 -4
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/useAdminModeController.d.ts +5 -4
  15. package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
  16. package/dist/hooks/useStudioBridge.d.ts +33 -0
  17. package/dist/index.es.js +2599 -6980
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/vitePlugin.d.ts +22 -0
  20. package/dist/vitePlugin.js +40 -1
  21. package/package.json +42 -31
  22. package/src/auth/api.ts +0 -134
  23. package/src/auth/index.ts +0 -26
  24. package/src/auth/types.ts +0 -115
  25. package/src/auth/useRebaseAuthController.ts +0 -345
  26. package/src/collections/collection_view_config.ts +0 -76
  27. package/src/collections/entity-display-cache.ts +0 -195
  28. package/src/collections/entity-display.ts +0 -77
  29. package/src/collections/entity_image_preview.ts +0 -61
  30. package/src/collections/filter-operator-resolution.ts +0 -119
  31. package/src/collections/form-layout.ts +0 -394
  32. package/src/collections/index.ts +0 -13
  33. package/src/collections/navigation_from_path.ts +0 -123
  34. package/src/collections/navigation_utils.ts +0 -171
  35. package/src/collections/parent_references_from_path.ts +0 -59
  36. package/src/collections/property-path.ts +0 -30
  37. package/src/collections/property_presentation.ts +0 -264
  38. package/src/collections/summary-property.ts +0 -114
  39. package/src/collections/title-property.ts +0 -312
  40. package/src/components/AIIcon.tsx +0 -47
  41. package/src/components/AuthSimulationMenu.tsx +0 -0
  42. package/src/components/ConfirmationDialog.tsx +0 -48
  43. package/src/components/Debug/UIReferenceView.tsx +0 -1053
  44. package/src/components/Debug/UIStyleGuide.tsx +0 -159
  45. package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
  46. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
  47. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
  48. package/src/components/Debug/collection-views/index.ts +0 -3
  49. package/src/components/Debug/collection-views/sample_data.ts +0 -44
  50. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
  51. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
  52. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
  53. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
  54. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
  55. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
  56. package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
  57. package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
  58. package/src/components/ErrorTooltip.tsx +0 -12
  59. package/src/components/ErrorView.tsx +0 -94
  60. package/src/components/LanguageToggle.tsx +0 -59
  61. package/src/components/LoginView/LoginView.tsx +0 -1235
  62. package/src/components/LoginView/index.ts +0 -2
  63. package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
  64. package/src/components/NotFoundPage.tsx +0 -27
  65. package/src/components/RebaseAuth.tsx +0 -27
  66. package/src/components/RebaseLogo.tsx +0 -88
  67. package/src/components/SchemaDriftBanner.tsx +0 -102
  68. package/src/components/UnsavedChangesDialog.tsx +0 -46
  69. package/src/components/UserDisplay.tsx +0 -54
  70. package/src/components/UserSelectPopover.tsx +0 -399
  71. package/src/components/UserSettingsView.tsx +0 -318
  72. package/src/components/common/index.ts +0 -6
  73. package/src/components/common/table_height.tsx +0 -21
  74. package/src/components/common/types.tsx +0 -66
  75. package/src/components/common/useColumnsIds.tsx +0 -261
  76. package/src/components/common/useDataTableController.tsx +0 -571
  77. package/src/components/common/useDebouncedData.ts +0 -49
  78. package/src/components/common/useScrollRestoration.tsx +0 -68
  79. package/src/components/index.tsx +0 -26
  80. package/src/contexts/AdminModeController.tsx +0 -11
  81. package/src/contexts/AnalyticsContext.tsx +0 -4
  82. package/src/contexts/AuthControllerContext.tsx +0 -4
  83. package/src/contexts/CollectionResolverContext.tsx +0 -24
  84. package/src/contexts/CollectionScopeContext.tsx +0 -68
  85. package/src/contexts/ComponentOverrideContext.tsx +0 -81
  86. package/src/contexts/CustomizationControllerContext.tsx +0 -4
  87. package/src/contexts/DataDriverContext.tsx +0 -4
  88. package/src/contexts/DataSourcesContext.tsx +0 -36
  89. package/src/contexts/DatabaseAdminContext.tsx +0 -4
  90. package/src/contexts/DialogsProvider.tsx +0 -56
  91. package/src/contexts/EffectiveRoleController.tsx +0 -12
  92. package/src/contexts/ModeController.tsx +0 -11
  93. package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
  94. package/src/contexts/RebaseDataContext.tsx +0 -4
  95. package/src/contexts/SnackbarProvider.tsx +0 -15
  96. package/src/contexts/StorageSourceContext.tsx +0 -4
  97. package/src/contexts/StorageSourcesContext.tsx +0 -30
  98. package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
  99. package/src/contexts/index.ts +0 -18
  100. package/src/core/PluginLifecycleManager.tsx +0 -96
  101. package/src/core/PluginProviderStack.tsx +0 -40
  102. package/src/core/Rebase.tsx +0 -488
  103. package/src/core/RebaseProps.tsx +0 -333
  104. package/src/core/RebaseRouter.tsx +0 -18
  105. package/src/core/RebaseRoutes.tsx +0 -24
  106. package/src/core/index.tsx +0 -5
  107. package/src/hooks/ApiConfigContext.tsx +0 -83
  108. package/src/hooks/data/collectionQuery.ts +0 -55
  109. package/src/hooks/data/delete.ts +0 -56
  110. package/src/hooks/data/save.ts +0 -78
  111. package/src/hooks/data/useCollection.tsx +0 -183
  112. package/src/hooks/data/useData.tsx +0 -18
  113. package/src/hooks/data/useDataOrder.ts +0 -26
  114. package/src/hooks/data/useFetch.tsx +0 -156
  115. package/src/hooks/data/useRelationSelector.tsx +0 -280
  116. package/src/hooks/index.tsx +0 -45
  117. package/src/hooks/useAdminModeController.tsx +0 -23
  118. package/src/hooks/useAnalyticsController.tsx +0 -8
  119. package/src/hooks/useAuthController.tsx +0 -15
  120. package/src/hooks/useAuthSubscription.ts +0 -130
  121. package/src/hooks/useBackendStorageSource.ts +0 -295
  122. package/src/hooks/useBridgeRegistration.tsx +0 -32
  123. package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
  124. package/src/hooks/useBuildAdminModeController.tsx +0 -29
  125. package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
  126. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
  127. package/src/hooks/useBuildModeController.tsx +0 -73
  128. package/src/hooks/useClipboard.tsx +0 -158
  129. package/src/hooks/useCollapsedGroups.ts +0 -116
  130. package/src/hooks/useComponentOverride.tsx +0 -59
  131. package/src/hooks/useCustomizationController.tsx +0 -14
  132. package/src/hooks/useDialogsController.tsx +0 -15
  133. package/src/hooks/useEffectiveRoleController.tsx +0 -10
  134. package/src/hooks/useLargeLayout.tsx +0 -65
  135. package/src/hooks/useModeController.tsx +0 -23
  136. package/src/hooks/useNavigationBlocker.tsx +0 -108
  137. package/src/hooks/usePermissions.ts +0 -45
  138. package/src/hooks/useRebaseClient.tsx +0 -10
  139. package/src/hooks/useRebaseContext.tsx +0 -84
  140. package/src/hooks/useRebaseRegistry.tsx +0 -91
  141. package/src/hooks/useResolvedComponent.tsx +0 -195
  142. package/src/hooks/useSlot.tsx +0 -64
  143. package/src/hooks/useSnackbarController.tsx +0 -80
  144. package/src/hooks/useStorageSource.tsx +0 -11
  145. package/src/hooks/useStudioBridge.tsx +0 -258
  146. package/src/hooks/useTranslation.ts +0 -38
  147. package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
  148. package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
  149. package/src/i18n/RebaseI18nProvider.tsx +0 -159
  150. package/src/index.ts +0 -32
  151. package/src/internal/common.tsx +0 -28
  152. package/src/internal/useRestoreScroll.tsx +0 -69
  153. package/src/locales/de.ts +0 -941
  154. package/src/locales/en.ts +0 -1021
  155. package/src/locales/es.ts +0 -962
  156. package/src/locales/fr.ts +0 -941
  157. package/src/locales/hi.ts +0 -941
  158. package/src/locales/it.ts +0 -941
  159. package/src/locales/pt.ts +0 -949
  160. package/src/util/constants.ts +0 -7
  161. package/src/util/createFormexStub.tsx +0 -66
  162. package/src/util/entity_cache.ts +0 -242
  163. package/src/util/enums.ts +0 -34
  164. package/src/util/icon_list.ts +0 -22
  165. package/src/util/icons.tsx +0 -129
  166. package/src/util/index.ts +0 -13
  167. package/src/util/local_storage.ts +0 -66
  168. package/src/util/previews.ts +0 -127
  169. package/src/util/useStorageUploadController.tsx +0 -354
  170. package/src/vitePlugin.ts +0 -215
  171. /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
  172. /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
  173. /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
  174. /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
  175. /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
  176. /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
  177. /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
  178. /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
  179. /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
  180. /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
  181. /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
  182. /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
  183. /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
  184. /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
  185. /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
@@ -1,399 +0,0 @@
1
-
2
- import React, { useCallback, useMemo, useRef, useState } from "react";
3
- import {
4
- CircleUserIcon,
5
- CircularProgress,
6
- cls,
7
- defaultBorderMixin,
8
- IconButton,
9
- iconSize,
10
- Popover,
11
- SearchBar,
12
- SearchIcon,
13
- Typography,
14
- XIcon
15
- } from "@rebasepro/ui";
16
- import { User } from "@rebasepro/types";
17
-
18
- /**
19
- * Represents a selectable user item with optional role information.
20
- */
21
- export interface SelectableUser {
22
- uid: string;
23
- displayName?: string | null;
24
- email?: string | null;
25
- photoURL?: string | null;
26
- roles?: string[];
27
- }
28
-
29
- export interface UserSelectPopoverProps {
30
- /**
31
- * The currently selected user, or `null` for "self"/default.
32
- */
33
- selectedUser: SelectableUser | null;
34
- /**
35
- * Called when the user selection changes.
36
- * `null` means "reset to self/default".
37
- */
38
- onUserSelected: (user: SelectableUser | null) => void;
39
- /**
40
- * Full list of users to display.
41
- * The component handles client-side search/filter internally.
42
- */
43
- users: SelectableUser[];
44
- /**
45
- * Whether users are currently loading.
46
- */
47
- loading?: boolean;
48
- /**
49
- * The current user (displayed as the "self" option at the top).
50
- */
51
- currentUser?: SelectableUser | null;
52
- /**
53
- * Label to display when no user is selected.
54
- * @default "Current user"
55
- */
56
- defaultLabel?: string;
57
- /**
58
- * Maximum number of users to render at a time (for performance).
59
- * Users can still be found via search.
60
- * @default 100
61
- */
62
- renderLimit?: number;
63
- /**
64
- * Additional class name for the trigger button.
65
- */
66
- className?: string;
67
- /**
68
- * Size variant.
69
- * @default "small"
70
- */
71
- size?: "small" | "medium";
72
- }
73
-
74
- const ITEM_HEIGHT = 44;
75
- const MAX_VISIBLE_ITEMS = 7;
76
-
77
- /**
78
- * A reusable user picker popover with search support.
79
- * Designed to handle very large user lists (100k+) by filtering
80
- * client-side and limiting the rendered set.
81
- *
82
- * @group Components
83
- */
84
- export function UserSelectPopover({
85
- selectedUser,
86
- onUserSelected,
87
- users,
88
- loading = false,
89
- currentUser,
90
- defaultLabel = "Current user",
91
- renderLimit = 100,
92
- className,
93
- size = "small"
94
- }: UserSelectPopoverProps) {
95
- const [open, setOpen] = useState(false);
96
- const [searchText, setSearchText] = useState("");
97
- const inputRef = useRef<HTMLInputElement>(null);
98
- const listRef = useRef<HTMLDivElement>(null);
99
-
100
- // Effective user label for the trigger
101
- const effectiveUserLabel = selectedUser
102
- ? (selectedUser.displayName || selectedUser.email || selectedUser.uid)
103
- : (currentUser?.displayName || currentUser?.email || defaultLabel);
104
-
105
- // Filter users based on search text
106
- const filteredUsers = useMemo(() => {
107
- const query = searchText.trim().toLowerCase();
108
- const base = users.filter(u => u.uid !== currentUser?.uid);
109
-
110
- if (!query) return base.slice(0, renderLimit);
111
-
112
- const matched = base.filter(u => {
113
- const name = (u.displayName || "").toLowerCase();
114
- const email = (u.email || "").toLowerCase();
115
- const uid = u.uid.toLowerCase();
116
- const roles = (u.roles || []).join(" ").toLowerCase();
117
- return (
118
- name.includes(query) ||
119
- email.includes(query) ||
120
- uid.includes(query) ||
121
- roles.includes(query)
122
- );
123
- });
124
-
125
- return matched.slice(0, renderLimit);
126
- }, [users, currentUser?.uid, searchText, renderLimit]);
127
-
128
- const totalFilterable = useMemo(() => {
129
- const query = searchText.trim().toLowerCase();
130
- const base = users.filter(u => u.uid !== currentUser?.uid);
131
- if (!query) return base.length;
132
- return base.filter(u => {
133
- const name = (u.displayName || "").toLowerCase();
134
- const email = (u.email || "").toLowerCase();
135
- const uid = u.uid.toLowerCase();
136
- const roles = (u.roles || []).join(" ").toLowerCase();
137
- return name.includes(query) || email.includes(query) || uid.includes(query) || roles.includes(query);
138
- }).length;
139
- }, [users, currentUser?.uid, searchText]);
140
-
141
- const handleSelect = useCallback((user: SelectableUser | null) => {
142
- onUserSelected(user);
143
- setOpen(false);
144
- setSearchText("");
145
- }, [onUserSelected]);
146
-
147
- const handleOpenChange = useCallback((isOpen: boolean) => {
148
- setOpen(isOpen);
149
- if (isOpen) {
150
- // Auto-focus the search input when opening
151
- setTimeout(() => inputRef.current?.focus(), 50);
152
- } else {
153
- setSearchText("");
154
- }
155
- }, []);
156
-
157
- const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
158
- if (e.key === "Escape") {
159
- setOpen(false);
160
- setSearchText("");
161
- }
162
- }, []);
163
-
164
- const listHeight = Math.min(
165
- (filteredUsers.length + 1) * ITEM_HEIGHT, // +1 for the "self" option
166
- MAX_VISIBLE_ITEMS * ITEM_HEIGHT
167
- );
168
-
169
- return (
170
- <Popover
171
- open={open}
172
- onOpenChange={handleOpenChange}
173
- side="bottom"
174
- align="end"
175
- sideOffset={4}
176
- trigger={
177
- <button
178
- className={cls(
179
- "flex items-center gap-1.5 text-xs font-medium transition-colors rounded border border-transparent",
180
- "text-text-secondary dark:text-text-secondary-dark",
181
- "hover:text-text-primary dark:hover:text-text-primary-dark",
182
- "bg-surface-100 hover:bg-surface-200 dark:bg-surface-950 dark:hover:bg-surface-700",
183
- size === "small" ? "px-2 py-1" : "px-3 py-1.5",
184
- className
185
- )}
186
- >
187
- {selectedUser?.photoURL ? (
188
- <img
189
- src={selectedUser.photoURL}
190
- alt=""
191
- className="w-4 h-4 rounded-full object-cover"
192
- />
193
- ) : (
194
- <svg
195
- className="w-3.5 h-3.5 text-text-disabled dark:text-text-disabled-dark"
196
- fill="none"
197
- stroke="currentColor"
198
- viewBox="0 0 24 24"
199
- >
200
- <path
201
- strokeLinecap="round"
202
- strokeLinejoin="round"
203
- strokeWidth={2}
204
- d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
205
- />
206
- </svg>
207
- )}
208
- <span className="max-w-[120px] truncate">{effectiveUserLabel}</span>
209
- {selectedUser && (
210
- <span className="text-[9px] text-amber-600 dark:text-amber-400 font-semibold uppercase">
211
- impersonating
212
- </span>
213
- )}
214
- <svg className="w-3 h-3 opacity-50 ml-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
215
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7"/>
216
- </svg>
217
- </button>
218
- }
219
- >
220
- <div
221
- className="flex flex-col w-[320px] max-w-[90vw] overflow-hidden"
222
- onKeyDown={handleKeyDown}
223
- >
224
- {/* Header */}
225
- <div className={cls("px-3 py-2 border-b flex items-center justify-between shrink-0", defaultBorderMixin)}>
226
- <Typography
227
- variant="caption"
228
- className="font-semibold uppercase tracking-wider text-[9px] text-text-disabled dark:text-text-disabled-dark"
229
- >
230
- Run as user
231
- </Typography>
232
- {users.length > 0 && (
233
- <Typography
234
- variant="caption"
235
- className="text-[9px] text-text-disabled dark:text-text-disabled-dark tabular-nums"
236
- >
237
- {totalFilterable} user{totalFilterable !== 1 ? "s" : ""}
238
- </Typography>
239
- )}
240
- </div>
241
-
242
- {/* Search input */}
243
- <div className={cls("px-2 py-1.5 border-b shrink-0", defaultBorderMixin)}>
244
- <SearchBar
245
- inputRef={inputRef}
246
- size="smallest"
247
- placeholder="Search by name, email, or role…"
248
- onTextSearch={(val) => setSearchText(val ?? "")}
249
- />
250
- </div>
251
-
252
- {/* User list */}
253
- <div
254
- ref={listRef}
255
- className="overflow-y-auto overscroll-contain"
256
- style={{ maxHeight: MAX_VISIBLE_ITEMS * ITEM_HEIGHT }}
257
- >
258
- {/* Self (default) option — always shown unless search hides it */}
259
- {currentUser && (!searchText.trim() || matchesSearch(currentUser, searchText)) && (
260
- <UserRow
261
- user={currentUser}
262
- isSelected={!selectedUser}
263
- isSelf
264
- onClick={() => handleSelect(null)}
265
- />
266
- )}
267
-
268
- {/* Loading state */}
269
- {loading && (
270
- <div className="flex items-center justify-center py-6">
271
- <CircularProgress size="small"/>
272
- </div>
273
- )}
274
-
275
- {/* Empty state */}
276
- {!loading && filteredUsers.length === 0 && searchText.trim() && (
277
- <div className="flex flex-col items-center justify-center py-6 px-4">
278
- <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark text-center">
279
- No users matching &ldquo;{searchText}&rdquo;
280
- </Typography>
281
- </div>
282
- )}
283
-
284
- {/* User items */}
285
- {!loading && filteredUsers.map(user => (
286
- <UserRow
287
- key={user.uid}
288
- user={user}
289
- isSelected={selectedUser?.uid === user.uid}
290
- onClick={() => handleSelect(user)}
291
- />
292
- ))}
293
-
294
- {/* Truncation notice */}
295
- {!loading && totalFilterable > renderLimit && (
296
- <div className={cls("px-3 py-2 border-t text-center", defaultBorderMixin)}>
297
- <Typography
298
- variant="caption"
299
- className="text-[10px] text-text-disabled dark:text-text-disabled-dark"
300
- >
301
- Showing {renderLimit} of {totalFilterable} results. Use search to narrow down.
302
- </Typography>
303
- </div>
304
- )}
305
- </div>
306
- </div>
307
- </Popover>
308
- );
309
- }
310
-
311
- // ─── Internal Components ─────────────────────────────────────────────
312
-
313
- interface UserRowProps {
314
- user: SelectableUser;
315
- isSelected: boolean;
316
- isSelf?: boolean;
317
- onClick: () => void;
318
- }
319
-
320
- function UserRow({ user, isSelected, isSelf, onClick }: UserRowProps) {
321
- return (
322
- <button
323
- onClick={onClick}
324
- className={cls(
325
- "w-full flex items-center gap-2.5 px-3 py-2 text-left transition-colors",
326
- "hover:bg-surface-accent-100 dark:hover:bg-surface-accent-900",
327
- isSelected && "bg-primary/5 dark:bg-primary-dark/10"
328
- )}
329
- style={{ minHeight: ITEM_HEIGHT }}
330
- >
331
- {/* Avatar */}
332
- {user.photoURL ? (
333
- <img
334
- src={user.photoURL}
335
- alt=""
336
- className="w-7 h-7 rounded-full object-cover shrink-0"
337
- />
338
- ) : (
339
- <div className="w-7 h-7 rounded-full bg-surface-200 dark:bg-surface-700 flex items-center justify-center shrink-0">
340
- <CircleUserIcon
341
- size={"smallest"}
342
- className="text-text-disabled dark:text-text-disabled-dark"
343
- />
344
- </div>
345
- )}
346
-
347
- {/* User info */}
348
- <div className="flex flex-col min-w-0 flex-grow">
349
- <div className="flex items-center gap-1.5">
350
- <span
351
- className={cls(
352
- "text-xs font-medium truncate",
353
- isSelected
354
- ? "text-primary dark:text-primary-dark"
355
- : "text-text-primary dark:text-text-primary-dark"
356
- )}
357
- >
358
- {user.displayName || user.email || user.uid}
359
- </span>
360
- {isSelf && (
361
- <span className="text-[9px] text-text-disabled dark:text-text-disabled-dark font-medium shrink-0">
362
- (you)
363
- </span>
364
- )}
365
- </div>
366
- {(user.email || user.roles?.length) && (
367
- <span className="text-[10px] text-text-disabled dark:text-text-disabled-dark truncate">
368
- {user.email && user.displayName ? user.email : ""}
369
- {user.email && user.displayName && user.roles?.length ? " · " : ""}
370
- {user.roles?.length ? user.roles.join(", ") : ""}
371
- </span>
372
- )}
373
- </div>
374
-
375
- {/* Selection indicator */}
376
- {isSelected && (
377
- <svg
378
- className="w-4 h-4 text-primary dark:text-primary-dark shrink-0"
379
- fill="none"
380
- stroke="currentColor"
381
- viewBox="0 0 24 24"
382
- >
383
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M5 13l4 4L19 7"/>
384
- </svg>
385
- )}
386
- </button>
387
- );
388
- }
389
-
390
- // ─── Helpers ─────────────────────────────────────────────────────────
391
-
392
- function matchesSearch(user: SelectableUser, searchText: string): boolean {
393
- const query = searchText.trim().toLowerCase();
394
- if (!query) return true;
395
- const name = (user.displayName || "").toLowerCase();
396
- const email = (user.email || "").toLowerCase();
397
- const uid = user.uid.toLowerCase();
398
- return name.includes(query) || email.includes(query) || uid.includes(query);
399
- }