@rebasepro/admin 0.13.0 → 0.13.1-canary.g18cfeb7

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 (143) hide show
  1. package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-CUFXevVg.js} +3 -3
  2. package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-CUFXevVg.js.map} +1 -1
  3. package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-DDUC7giL.js} +2 -2
  4. package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-DDUC7giL.js.map} +1 -1
  5. package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-vzS848Gf.js} +9 -13
  6. package/dist/RouterCollectionsStudioView-vzS848Gf.js.map +1 -0
  7. package/dist/collection_editor/serializable_types.d.ts +1 -0
  8. package/dist/collection_editor_ui.js +4 -4
  9. package/dist/components/AdditionalFieldValue.d.ts +25 -0
  10. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
  11. package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
  12. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
  13. package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
  14. package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
  15. package/dist/components/DetailViewBinding.d.ts +15 -0
  16. package/dist/components/DrawerNavigationGroup.d.ts +9 -6
  17. package/dist/components/DrawerNavigationItem.d.ts +16 -6
  18. package/dist/components/EditViewBinding.d.ts +30 -1
  19. package/dist/components/EntityDisplayHeader.d.ts +53 -0
  20. package/dist/components/EntityIdentityBar.d.ts +43 -1
  21. package/dist/components/EntityViewBinding.d.ts +60 -6
  22. package/dist/components/RebaseLayout.d.ts +1 -1
  23. package/dist/components/RebaseShell.d.ts +1 -1
  24. package/dist/components/RelationSelector.d.ts +8 -1
  25. package/dist/components/UserSelector.d.ts +8 -1
  26. package/dist/components/app/Scaffold.d.ts +9 -3
  27. package/dist/components/index.d.ts +2 -0
  28. package/dist/{export-J79rMoNj.js → export-5bl2EoSs.js} +2 -2
  29. package/dist/{export-J79rMoNj.js.map → export-5bl2EoSs.js.map} +1 -1
  30. package/dist/form/components/FieldBlock.d.ts +58 -4
  31. package/dist/form/components/FormRail.d.ts +13 -0
  32. package/dist/form/components/FormSections.d.ts +15 -1
  33. package/dist/form/components/useRailVisible.d.ts +5 -4
  34. package/dist/form/form_utils.d.ts +38 -1
  35. package/dist/{history-D7L_JlLO.js → history-BuKKAhoY.js} +4 -4
  36. package/dist/{history-D7L_JlLO.js.map → history-BuKKAhoY.js.map} +1 -1
  37. package/dist/hooks/useEntityDisplay.d.ts +102 -0
  38. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
  39. package/dist/{import-BvYl--U4.js → import-BhdWs34H.js} +3 -3
  40. package/dist/{import-BvYl--U4.js.map → import-BhdWs34H.js.map} +1 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +152 -57
  43. package/dist/index.js.map +1 -1
  44. package/dist/preview/compact_text.d.ts +23 -0
  45. package/dist/preview/components/BooleanPreview.d.ts +3 -1
  46. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  47. package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
  48. package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
  49. package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
  50. package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
  51. package/dist/routes/RebaseRoute.d.ts +1 -1
  52. package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
  53. package/dist/util/number_format.d.ts +14 -0
  54. package/dist/util/previews.d.ts +9 -2
  55. package/dist/{util-GiwPgxnL.js → util-DNG7iK0w.js} +3756 -2300
  56. package/dist/util-DNG7iK0w.js.map +1 -0
  57. package/package.json +9 -9
  58. package/src/collection_editor/serializable_types.ts +1 -0
  59. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
  60. package/src/components/AdditionalFieldValue.tsx +68 -0
  61. package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
  62. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
  63. package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
  64. package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
  65. package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
  66. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +723 -348
  67. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
  68. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +38 -8
  69. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +23 -36
  70. package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
  71. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
  72. package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
  73. package/src/components/CollectionViewBinding/SplitListView.tsx +33 -2
  74. package/src/components/CollectionViewBinding/useBoardDataController.tsx +12 -3
  75. package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
  76. package/src/components/CollectionViewBinding/utils.ts +7 -4
  77. package/src/components/DefaultDrawer.tsx +19 -23
  78. package/src/components/DetailViewBinding.tsx +93 -56
  79. package/src/components/DrawerNavigationGroup.tsx +16 -39
  80. package/src/components/DrawerNavigationItem.tsx +44 -16
  81. package/src/components/EditViewBinding.tsx +215 -42
  82. package/src/components/EntityDisplayHeader.tsx +170 -0
  83. package/src/components/EntityIdentityBar.tsx +163 -23
  84. package/src/components/EntityInspector.tsx +24 -6
  85. package/src/components/EntityPreviewBinding.tsx +58 -18
  86. package/src/components/EntityViewBinding.tsx +342 -41
  87. package/src/components/InlineEntityPreview.tsx +10 -10
  88. package/src/components/RebaseLayout.tsx +2 -2
  89. package/src/components/RebaseShell.tsx +2 -2
  90. package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
  91. package/src/components/RelationSelector.tsx +15 -4
  92. package/src/components/SideDialogs.tsx +15 -3
  93. package/src/components/SidePanelBinding.tsx +122 -66
  94. package/src/components/UserSelector.tsx +15 -4
  95. package/src/components/app/Scaffold.tsx +112 -10
  96. package/src/components/history/LastEditedByIndicator.tsx +7 -12
  97. package/src/components/index.ts +3 -0
  98. package/src/data_import/utils/data.ts +9 -1
  99. package/src/form/EntityForm.tsx +64 -52
  100. package/src/form/EntityFormBinding.tsx +24 -11
  101. package/src/form/PropertyFieldBinding.tsx +2 -2
  102. package/src/form/components/FieldBlock.tsx +80 -10
  103. package/src/form/components/FormRail.tsx +19 -2
  104. package/src/form/components/FormSections.tsx +32 -5
  105. package/src/form/components/StorageUploadProgress.tsx +9 -3
  106. package/src/form/components/useRailVisible.tsx +5 -4
  107. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
  108. package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
  109. package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
  110. package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
  111. package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
  112. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
  113. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
  114. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -1
  115. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
  116. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
  117. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  118. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
  119. package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
  120. package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
  121. package/src/form/form_utils.ts +78 -0
  122. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
  123. package/src/hooks/useEntityDisplay.tsx +262 -0
  124. package/src/hooks/useEntityDisplayTitle.tsx +27 -38
  125. package/src/index.ts +9 -1
  126. package/src/preview/PropertyPreview.tsx +25 -5
  127. package/src/preview/compact_text.ts +68 -0
  128. package/src/preview/components/BooleanPreview.tsx +5 -2
  129. package/src/preview/components/StorageThumbnail.tsx +5 -2
  130. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
  131. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
  132. package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
  133. package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
  134. package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
  135. package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
  136. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
  137. package/src/routes/RebaseRoute.tsx +62 -4
  138. package/src/types/components/PropertyPreviewProps.tsx +21 -0
  139. package/src/util/number_format.ts +46 -0
  140. package/src/util/previews.ts +8 -55
  141. package/src/util/property_utils.tsx +6 -1
  142. package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
  143. package/dist/util-GiwPgxnL.js.map +0 -1
@@ -10,11 +10,11 @@ import { CollectionViewBinding } from "./CollectionViewBinding/CollectionViewBin
10
10
  import { EntityViewBinding } from "./EntityViewBinding";
11
11
  import { EntityIdentityBar } from "./EntityIdentityBar";
12
12
  import { SplitListCloseButton } from "./CollectionViewBinding/SplitListCloseButton";
13
+ import { SplitListShowButton } from "./CollectionViewBinding/SplitListShowButton";
13
14
  import { EntityInspector, InspectorTab } from "./EntityInspector";
14
15
  import { CircularProgressCenter, iconSize } from "@rebasepro/ui";
15
16
  import {
16
17
  Alert,
17
- ArrowLeftIcon,
18
18
  Button,
19
19
  CenteredView,
20
20
  cls,
@@ -34,6 +34,7 @@ import { ErrorBoundary } from "@rebasepro/ui";
34
34
  import { ErrorView, createFormexStub, usePermissions, useTranslation, getIcon } from "@rebasepro/app";
35
35
 
36
36
  import { removeInitialAndTrailingSlashes, resolveDefaultSelectedView } from "@rebasepro/app";
37
+ import { withViewMode } from "../util/view_mode";
37
38
  import { resolvedSelectedEntityView } from "../util/resolutions";
38
39
  import {
39
40
  useCustomizationController,
@@ -45,9 +46,7 @@ import {
45
46
  import { useUrlController } from "../hooks/navigation/contexts/UrlContext";
46
47
  import { useCollectionRegistryController } from "../hooks/navigation/contexts/CollectionRegistryContext";
47
48
  import { useNavigate } from "react-router";
48
- import { getValueInPath } from "@rebasepro/utils";
49
- import { getEntityTitlePropertyKeyForEntity, isUserSelectProperty, resolveTitleToString } from "../util/previews";
50
- import { getUserLabel, useResolvedUser } from "../hooks/useResolvedUsers";
49
+ import { useEntityDisplayTitle } from "../hooks/useEntityDisplayTitle";
51
50
 
52
51
 
53
52
  import { MAIN_TAB_VALUE, JSON_TAB_VALUE, HISTORY_TAB_VALUE } from "../util/view_constants";
@@ -57,6 +56,8 @@ export type BarActionsParams = {
57
56
  status: EntityStatus,
58
57
  path: string,
59
58
  entityId?: string | number;
59
+ /** Always false here — this view reads a record, it does not edit one. */
60
+ dirty: boolean;
60
61
  };
61
62
 
62
63
  export type OnTabChangeParams<M extends Record<string, unknown>> = {
@@ -75,6 +76,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
75
76
  parentEntityIds: string[];
76
77
  onTabChange?: (props: OnTabChangeParams<M>) => void;
77
78
  onEditClick?: () => void;
79
+ /**
80
+ * Dismiss this record — the ✕ at the identity bar's trailing edge.
81
+ *
82
+ * Set by the layouts that own the record's place on screen but are not side
83
+ * dialogs: the split, where dismissing is navigating back to the collection.
84
+ */
85
+ onCloseRequest?: () => void;
78
86
  layout?: "side_panel" | "full_screen" | "split" | "dialog";
79
87
  barActions?: (params: BarActionsParams) => React.ReactNode;
80
88
  /**
@@ -82,6 +90,12 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
82
90
  * of the breadcrumb — the side panel's close button.
83
91
  */
84
92
  barActionsStart?: React.ReactNode;
93
+ /**
94
+ * Full screen only: bring the list back beside this record. Set when the
95
+ * record was opened out of a split collection, so folding the list away is
96
+ * reversible; it takes the place of the back arrow when present.
97
+ */
98
+ onShowList?: () => void;
85
99
  }
86
100
 
87
101
  export function DetailViewBinding<M extends Record<string, unknown>>({
@@ -137,11 +151,13 @@ function DetailViewBindingInner<M extends Record<string, unknown>>({
137
151
  parentEntityIds,
138
152
  onTabChange,
139
153
  onEditClick,
154
+ onCloseRequest,
140
155
  entity,
141
156
  dataLoading,
142
157
  layout = "full_screen",
143
158
  barActions,
144
- barActionsStart
159
+ barActionsStart,
160
+ onShowList
145
161
  }: DetailViewBindingProps<M> & {
146
162
  entity?: Entity<M>,
147
163
  dataLoading: boolean,
@@ -189,7 +205,12 @@ entityId }
189
205
  }
190
206
  }, [selectedTabProp, defaultSelectedView]);
191
207
 
192
- const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromNavigation);
208
+ // `hideFromEntityViews`, not `hideFromNavigation`: the latter governs the
209
+ // drawer. Filtering tabs on it defeated the introspection generator, which
210
+ // sets `hideFromNavigation` on owned-child tables precisely *because* "every
211
+ // inbound foreign key renders as a tab on the parent" — so the rows it
212
+ // deliberately kept out of the drawer became reachable from nowhere.
213
+ const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromEntityViews);
193
214
  const subcollections = childViews.map(v => v.collection);
194
215
  const subcollectionsCount = subcollections?.length ?? 0;
195
216
  const customViews = collection.entityViews ?? [];
@@ -283,18 +304,13 @@ entityId }
283
304
  const formViewConfig = (collection as AdminCollection<M> & { formView?: FormViewConfig<M> }).formView;
284
305
  const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef<EntityCustomViewParams>(formViewConfig.Builder as ComponentRef<EntityCustomViewParams>) : null;
285
306
 
286
- // Title resolution
287
- const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, usedEntity?.values, usedEntity?.id);
288
- const rawTitle = usedEntity?.values && titlePropertyKey ? getValueInPath(usedEntity.values, titlePropertyKey) : undefined;
289
- // A user picker stores an id: resolve it to the person, like a relation.
290
- const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string"
291
- ? rawTitle
292
- : undefined);
293
- const title = titleUser
294
- ? getUserLabel(titleUser)
295
- : (rawTitle !== undefined && rawTitle !== null
296
- ? resolveTitleToString(rawTitle)
297
- : (collection.singularName ?? collection.name));
307
+ // The heading, resolved the one way — this used to be a copy of
308
+ // `useEntityDisplayTitle`'s body, and the two had already drifted.
309
+ const title = useEntityDisplayTitle({
310
+ collection,
311
+ entity: usedEntity,
312
+ status: "existing"
313
+ });
298
314
 
299
315
  // Non-action custom views
300
316
  const nonActionCustomViews = useMemo(() =>
@@ -380,20 +396,31 @@ entityId }
380
396
  }, [onTabChange, path, entityId, collection]);
381
397
 
382
398
  const propertyDetailView = () => {
383
- // formView.Builder replaces the default property display
399
+ // formView.Builder replaces the default property display. It gets the
400
+ // centred column the record used to be given here; the default display
401
+ // now lays itself out, rail included.
384
402
  if (FormViewBuilder && usedEntity) {
385
- return <ErrorBoundary>
386
- <Suspense fallback={<CircularProgressCenter />}>
387
- <FormViewBuilder
388
- collection={collection}
389
- parentCollectionSlugs={parentCollectionSlugs}
390
- parentEntityIds={parentEntityIds}
391
- entity={usedEntity}
392
- modifiedValues={usedEntity?.values}
393
- formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
394
- />
395
- </Suspense>
396
- </ErrorBoundary>;
403
+ return <div className={"flex-1 min-w-0 overflow-y-auto flex justify-center items-start"}>
404
+ <div className={cls(
405
+ "w-full max-w-3xl 2xl:max-w-4xl flex flex-col",
406
+ layout === "dialog"
407
+ ? "pt-5 pb-12 px-6 sm:px-8"
408
+ : "pt-6 pb-16 px-5 sm:px-8"
409
+ )}>
410
+ <ErrorBoundary>
411
+ <Suspense fallback={<CircularProgressCenter />}>
412
+ <FormViewBuilder
413
+ collection={collection}
414
+ parentCollectionSlugs={parentCollectionSlugs}
415
+ parentEntityIds={parentEntityIds}
416
+ entity={usedEntity}
417
+ modifiedValues={usedEntity?.values}
418
+ formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
419
+ />
420
+ </Suspense>
421
+ </ErrorBoundary>
422
+ </div>
423
+ </div>;
397
424
  }
398
425
 
399
426
  return (
@@ -402,6 +429,9 @@ entityId }
402
429
  entity={usedEntity}
403
430
  collection={collection}
404
431
  path={path}
432
+ asPage
433
+ openEntityMode={layout}
434
+ formContext={readOnlyFormContext}
405
435
  />}
406
436
  </>
407
437
  );
@@ -458,24 +488,27 @@ entityId }
458
488
  );
459
489
  });
460
490
 
491
+ // The breadcrumb's destination, and the back arrow's when it renders. Both
492
+ // keep the view mode: a collection reached from one of its own records must
493
+ // come back as the user left it.
494
+ const collectionUrl = withViewMode(urlController.buildUrlCollectionPath(path));
495
+
461
496
  // Full screen is the one layout with no way out of its own: a side panel and a
462
497
  // dialog can be dismissed, and a split keeps the list beside it, but opening an
463
498
  // entity full screen replaces the collection entirely and left browser Back as
464
499
  // the only route back to it — which is not an affordance the page shows, and is
465
500
  // wrong anyway once the user has moved between tabs inside the entity.
466
- const backToCollectionButton = layout === "full_screen" ? (
467
- <Tooltip title={t("back")}>
468
- <IconButton
469
- size="small"
470
- aria-label={t("back")}
471
- onClick={() => navigate(urlController.buildUrlCollectionPath(path))}
472
- >
473
- <ArrowLeftIcon size={iconSize.smallest} />
474
- </IconButton>
475
- </Tooltip>
476
- ) : null;
477
-
478
- const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog" || Boolean(backToCollectionButton);
501
+ //
502
+ // Unless the list can be unfolded back beside the record: that chevron
503
+ // leads to the same collection while keeping the record open, so it stands
504
+ // alone rather than beside an arrow going almost the same place.
505
+ //
506
+ // It goes to the bar's leading edge — where a back control reads as back,
507
+ // and where the edit view has always put the same arrow — rather than to
508
+ // the trailing edge among the record's own actions.
509
+ const backToCollection = layout === "full_screen" && !onShowList
510
+ ? () => navigate(collectionUrl)
511
+ : undefined;
479
512
 
480
513
  const fullScreenButton = !barActions && // Not in split: the list panel beside it carries the close control,
481
514
  // and an expand button there competes with it.
@@ -508,49 +541,53 @@ entityId }
508
541
  // Main content view. The title, the `path/id` chip and the empty band under
509
542
  // them have moved into the identity bar, and the `w-80 2xl:w-96` rail that
510
543
  // held a single Edit button is gone — the bar holds it now.
544
+ //
545
+ // The scroll now lives on the record's own column rather than on this row,
546
+ // so the metadata rail beside it stays put while the record scrolls — the
547
+ // same arrangement the form uses.
511
548
  const mainView = <div
512
549
  className={cls(
513
- "flex-1 flex flex-row w-full overflow-y-auto justify-center",
550
+ "flex-1 flex flex-row w-full min-h-0",
514
551
  !mainViewVisible ? "hidden" : ""
515
552
  )}>
516
- <div className={cls(
517
- "w-full max-w-3xl 2xl:max-w-4xl flex flex-col",
518
- layout === "dialog"
519
- ? "pt-5 pb-12 px-6 sm:px-8"
520
- : "pt-6 pb-16 px-5 sm:px-8"
521
- )}>
522
- {propertyDetailView()}
523
- </div>
553
+ {propertyDetailView()}
524
554
  </div>;
525
555
 
526
556
  let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
527
557
 
528
558
  <EntityIdentityBar
529
559
  collection={collection as AdminCollection}
560
+ collectionUrl={layout === "full_screen" || layout === "split" ? collectionUrl : undefined}
530
561
  title={title}
531
562
  entityId={entityId}
532
563
  status={"existing"}
533
564
  dirty={false}
534
565
  saving={false}
566
+ onBack={backToCollection}
567
+ onClose={onCloseRequest}
535
568
  onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
536
569
  onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
570
+ externalLink={usedEntity
571
+ ? customizationController?.entityLinkBuilder?.({ entity: usedEntity })
572
+ : undefined}
537
573
  leading={<>
538
574
  {barActionsStart}
539
575
  {/* Split view: closing the list is opening this record
540
576
  full screen, and the control for it belongs at this
541
- bar's leading edge. */}
577
+ bar's leading edge. Full screen carries its mirror. */}
542
578
  {layout === "split" && <SplitListCloseButton/>}
579
+ {layout === "full_screen" && onShowList && <SplitListShowButton onClick={onShowList}/>}
543
580
  </>}
544
581
  trailing={<>
545
582
  {editButton}
546
583
  {pluginActionsTop}
547
- {backToCollectionButton}
548
584
  {fullScreenButton}
549
585
  {barActions?.({
550
586
  path,
551
587
  entityId,
552
588
  values: usedEntity?.values ?? {},
553
- status: "existing"
589
+ status: "existing",
590
+ dirty: false
554
591
  })}
555
592
  </>}
556
593
  />
@@ -28,11 +28,12 @@ export interface DrawerNavigationGroupProps {
28
28
  */
29
29
  drawerOpen: boolean;
30
30
  /**
31
- * Whether tooltips should be shown (drawer closed + hovered)
31
+ * @deprecated No longer read. Entry tooltips are uncontrolled they follow the
32
+ * pointer and focus, and are suppressed wherever the label is already visible.
32
33
  */
33
- tooltipsOpen: boolean;
34
+ tooltipsOpen?: boolean;
34
35
  /**
35
- * Whether admin menu is open (used to control tooltip visibility)
36
+ * @deprecated No longer read. See {@link tooltipsOpen}.
36
37
  */
37
38
  adminMenuOpen?: boolean;
38
39
  /**
@@ -50,8 +51,10 @@ export interface DrawerNavigationGroupProps {
50
51
  /**
51
52
  * Lucide icon name for the group header, from `NavigationGroupMapping.icon`.
52
53
  *
53
- * Supplying one moves the visual anchor from the rows to the group: the header
54
- * gets the icon, and the entries below trade theirs for an indent.
54
+ * It decorates the header and nothing else: the entries below are untouched and
55
+ * keep their own icons. An app that wants the categorised look rows giving up
56
+ * their icons to indent under the group — builds it in its own drawer, by
57
+ * overriding `Shell.DrawerNavigationItem` and passing `indented`.
55
58
  */
56
59
  icon?: string;
57
60
  }
@@ -66,8 +69,6 @@ export function DrawerNavigationGroup({
66
69
  collapsed,
67
70
  onToggleCollapsed,
68
71
  drawerOpen,
69
- tooltipsOpen,
70
- adminMenuOpen,
71
72
  headerActions,
72
73
  onItemClick,
73
74
  hideHeader,
@@ -76,17 +77,13 @@ export function DrawerNavigationGroup({
76
77
  const { t } = useTranslation();
77
78
  const ResolvedDrawerNavigationItem = useComponentOverride("Shell.DrawerNavigationItem", DrawerNavigationItem);
78
79
 
79
- // Opt-in, per group: declaring `icon` on a `NavigationGroupMapping` switches
80
- // this group to the categorised treatment icon on the header, entries
81
- // indented beneath it without their own. A group that declares no icon renders
82
- // exactly as it did before, so an existing project sees no change at all.
83
- //
84
- // Two cases stay on the flat rendering regardless: with no header there is
85
- // nothing to indent under, and collapsed to a rail the entry icons are the only
86
- // affordance left to click.
80
+ // The icon decorates the header, and stops there. It used to also strip the
81
+ // entries of theirs and indent them, which made a per-app styling choice into
82
+ // framework behaviour: every project that labelled a group with an icon lost the
83
+ // icons on its rows, with nothing to turn it off. An app that wants that look
84
+ // now opts into it in its own drawer — see `indented` on
85
+ // {@link DrawerNavigationItem}.
87
86
  const groupIcon = !hideHeader && drawerOpen ? getIcon(icon, undefined, undefined, "small") : undefined;
88
- const categorised = Boolean(groupIcon);
89
- const indentEntries = categorised;
90
87
  return (
91
88
  <div
92
89
  className={"my-2 mx-2 flex flex-col"}
@@ -103,10 +100,7 @@ export function DrawerNavigationGroup({
103
100
  <ChevronDownIcon
104
101
  size={iconSize.small}
105
102
  className={cls(
106
- categorised
107
- ? "text-surface-500 dark:text-surface-400"
108
- : "text-surface-400 dark:text-surface-400",
109
- "transition-transform duration-200 mr-1",
103
+ "text-surface-400 dark:text-surface-400 transition-transform duration-200 mr-1",
110
104
  collapsed ? "-rotate-90" : "rotate-0"
111
105
  )}
112
106
  />
@@ -118,21 +112,7 @@ export function DrawerNavigationGroup({
118
112
  <Typography
119
113
  variant={"caption"}
120
114
  color={"secondary"}
121
- // Two treatments, and which one applies is decided by the group
122
- // itself. Without an icon this is byte-for-byte the original: the
123
- // drawer is a flat list of same-weight rows, and a louder header
124
- // would just compete with them.
125
- //
126
- // With one, the group becomes the thing you scan — its rows have
127
- // given up their icons to indent beneath it — so the label steps up
128
- // to match. At 11px in surface-400 it sat *below* the contrast of
129
- // the rows it labels, which is backwards once it is the anchor.
130
- className={cls(
131
- "font-semibold uppercase flex-grow line-clamp-1",
132
- categorised
133
- ? "text-[12px] tracking-wide text-surface-600 dark:text-surface-300"
134
- : "text-[11px] tracking-wider text-surface-400 dark:text-surface-400"
135
- )}
115
+ className="font-semibold text-[11px] uppercase tracking-wider flex-grow line-clamp-1 text-surface-400 dark:text-surface-400"
136
116
  >
137
117
  {(group || t("views_group"))}
138
118
  </Typography>
@@ -158,13 +138,10 @@ export function DrawerNavigationGroup({
158
138
  <ResolvedDrawerNavigationItem
159
139
  key={entry.id}
160
140
  icon={<IconForView collectionOrView={entry.collection ?? entry.view} size={"small"}/>}
161
- tooltipsOpen={!collapsed && tooltipsOpen}
162
- adminMenuOpen={adminMenuOpen}
163
141
  drawerOpen={drawerOpen}
164
142
  onClick={() => onItemClick?.(entry)}
165
143
  url={entry.url}
166
144
  name={entry.name}
167
- indented={indentEntries}
168
145
  />
169
146
  ))}
170
147
  </div>
@@ -3,30 +3,39 @@ import React from "react";
3
3
  import { NavLink } from "react-router";
4
4
  import { cls, Tooltip } from "@rebasepro/ui";
5
5
 
6
- export function DrawerNavigationItem({
7
- name,
8
- icon,
9
- drawerOpen,
10
- adminMenuOpen,
11
- tooltipsOpen,
12
- url,
13
- onClick,
14
- indented = false
15
- }: {
6
+ /**
7
+ * Props of a drawer navigation row, named so an app overriding
8
+ * `Shell.DrawerNavigationItem` can type its wrapper against them.
9
+ */
10
+ export type DrawerNavigationItemProps = {
16
11
  icon: React.ReactElement,
17
12
  name: string,
18
- tooltipsOpen: boolean,
13
+ /** @deprecated No longer read: the tooltip is uncontrolled. */
14
+ tooltipsOpen?: boolean,
19
15
  drawerOpen: boolean,
16
+ /** @deprecated No longer read: the tooltip is uncontrolled. */
20
17
  adminMenuOpen?: boolean,
21
18
  url: string,
22
19
  onClick?: () => void,
23
20
  /**
24
- * Render as a child of an icon-bearing group: no icon of its own, indented to
25
- * sit under the group's label. Set by {@link DrawerNavigationGroup} only when
26
- * the group actually has an icon and the drawer is expanded.
21
+ * Drop the row's icon and indent the label into the space it occupied, so the
22
+ * row reads as a child of the group header above it.
23
+ *
24
+ * Nothing in the framework sets this: the stock drawer always passes `false`,
25
+ * and an app that wants the categorised look opts into it by overriding
26
+ * `Shell.DrawerNavigationItem` and deciding for itself which rows get it.
27
27
  */
28
28
  indented?: boolean,
29
- }) {
29
+ };
30
+
31
+ export function DrawerNavigationItem({
32
+ name,
33
+ icon,
34
+ drawerOpen,
35
+ url,
36
+ onClick,
37
+ indented = false
38
+ }: DrawerNavigationItemProps) {
30
39
 
31
40
  // Indented rows give up the icon rather than keeping it *and* indenting: a
32
41
  // per-row icon repeated down a group is what flattens the hierarchy, since
@@ -40,6 +49,15 @@ export function DrawerNavigationItem({
40
49
  {icon}
41
50
  </div>;
42
51
 
52
+ const [wantsTooltip, setWantsTooltip] = React.useState(false);
53
+
54
+ // Radix will not report a close it never noticed: while the tooltip is masked
55
+ // the pointer can leave the row without `onOpenChange(false)` ever firing. So
56
+ // the stale `true` is dropped on the way *into* the masked state — otherwise
57
+ // it surfaces the moment the mask lifts, as a tooltip hanging over a rail the
58
+ // pointer left long ago.
59
+ if (drawerOpen && wantsTooltip) setWantsTooltip(false);
60
+
43
61
  const listItem = <div>
44
62
  <NavLink
45
63
  onClick={onClick}
@@ -70,8 +88,18 @@ export function DrawerNavigationItem({
70
88
  </NavLink>
71
89
  </div>;
72
90
 
91
+ // Per row, and always controlled. The old shared flag could only say "every
92
+ // tooltip" or "none", and the state it needed was unreachable, so no entry
93
+ // tooltip ever opened — leaving a bare rail of unlabelled icons. Radix decides
94
+ // when this one wants to be open, from the pointer and from focus; the drawer
95
+ // only masks it once the label says the same thing.
96
+ //
97
+ // Masked rather than withheld: dropping `open`, or the title, would move the
98
+ // tooltip between controlled and uncontrolled as the drawer opens, which
99
+ // strands the old state — a tooltip left up over a pointer nowhere near it.
73
100
  return <Tooltip
74
- open={drawerOpen || adminMenuOpen ? false : tooltipsOpen}
101
+ open={!drawerOpen && wantsTooltip}
102
+ onOpenChange={setWantsTooltip}
75
103
  side="right"
76
104
  title={name}>
77
105
  {listItem}