@rebasepro/admin 0.13.0 → 0.13.1-canary.g249daa1
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.
- package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-DgfCYDmF.js} +3 -3
- package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-DgfCYDmF.js.map} +1 -1
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-CCrruunW.js} +2 -2
- package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-CCrruunW.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-C_EwDSi1.js} +9 -13
- package/dist/RouterCollectionsStudioView-C_EwDSi1.js.map +1 -0
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
- package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
- package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
- package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +92 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
- package/dist/components/DetailViewBinding.d.ts +15 -0
- package/dist/components/DrawerNavigationGroup.d.ts +9 -6
- package/dist/components/DrawerNavigationItem.d.ts +16 -6
- package/dist/components/EditViewBinding.d.ts +30 -1
- package/dist/components/EntityDisplayHeader.d.ts +53 -0
- package/dist/components/EntityIdentityBar.d.ts +43 -1
- package/dist/components/EntityViewBinding.d.ts +60 -6
- package/dist/components/RebaseLayout.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +1 -1
- package/dist/components/RelationSelector.d.ts +8 -1
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/components/app/Scaffold.d.ts +9 -3
- package/dist/components/index.d.ts +2 -0
- package/dist/{export-J79rMoNj.js → export-CV-qQfxK.js} +2 -2
- package/dist/{export-J79rMoNj.js.map → export-CV-qQfxK.js.map} +1 -1
- package/dist/form/components/FieldBlock.d.ts +58 -4
- package/dist/form/components/FormRail.d.ts +13 -0
- package/dist/form/components/FormSections.d.ts +15 -1
- package/dist/form/components/useRailVisible.d.ts +5 -4
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-D7L_JlLO.js → history-By7QT3V0.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-By7QT3V0.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +102 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
- package/dist/{import-BvYl--U4.js → import-CYbvYa_x.js} +3 -3
- package/dist/{import-BvYl--U4.js.map → import-CYbvYa_x.js.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +152 -57
- package/dist/index.js.map +1 -1
- package/dist/preview/compact_text.d.ts +23 -0
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
- package/dist/util/number_format.d.ts +14 -0
- package/dist/util/previews.d.ts +9 -2
- package/dist/util/view_constants.d.ts +24 -0
- package/dist/util/view_mode.d.ts +18 -0
- package/dist/{util-GiwPgxnL.js → util-B8hurkxt.js} +3569 -1692
- package/dist/util-B8hurkxt.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +822 -354
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +51 -12
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +51 -39
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +285 -0
- package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +47 -7
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +12 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
- package/src/components/CollectionViewBinding/utils.ts +7 -4
- package/src/components/DefaultDrawer.tsx +19 -23
- package/src/components/DetailViewBinding.tsx +109 -62
- package/src/components/DrawerNavigationGroup.tsx +16 -39
- package/src/components/DrawerNavigationItem.tsx +44 -16
- package/src/components/EditViewBinding.tsx +233 -50
- package/src/components/EntityDisplayHeader.tsx +170 -0
- package/src/components/EntityIdentityBar.tsx +163 -23
- package/src/components/EntityInspector.tsx +24 -6
- package/src/components/EntityPreviewBinding.tsx +58 -18
- package/src/components/EntityViewBinding.tsx +344 -41
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/RebaseLayout.tsx +2 -2
- package/src/components/RebaseShell.tsx +2 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +23 -9
- package/src/components/SideDialogs.tsx +15 -3
- package/src/components/SidePanelBinding.tsx +122 -66
- package/src/components/UserSelector.tsx +20 -8
- package/src/components/app/Scaffold.tsx +112 -10
- package/src/components/history/LastEditedByIndicator.tsx +7 -12
- package/src/components/index.ts +3 -0
- package/src/data_import/utils/data.ts +9 -1
- package/src/form/EntityForm.tsx +74 -53
- package/src/form/EntityFormBinding.tsx +24 -11
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/components/FieldBlock.tsx +80 -10
- package/src/form/components/FormRail.tsx +19 -2
- package/src/form/components/FormSections.tsx +32 -5
- package/src/form/components/StorageUploadProgress.tsx +9 -3
- package/src/form/components/useRailVisible.tsx +5 -4
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +21 -2
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
- package/src/form/form_utils.ts +78 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +262 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +25 -5
- package/src/preview/compact_text.ts +68 -0
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/preview/components/StorageThumbnail.tsx +5 -2
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +21 -0
- package/src/util/navigation_utils.ts +4 -2
- package/src/util/number_format.ts +46 -0
- package/src/util/previews.ts +8 -55
- package/src/util/property_utils.tsx +6 -1
- package/src/util/view_constants.ts +27 -0
- package/src/util/view_mode.ts +36 -0
- package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
- 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,18 +46,25 @@ 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 {
|
|
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
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
MAIN_TAB_VALUE,
|
|
54
|
+
JSON_TAB_VALUE,
|
|
55
|
+
HISTORY_TAB_VALUE,
|
|
56
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
57
|
+
ENTITY_VIEW_TAB_CLASS,
|
|
58
|
+
ENTITY_VIEW_TAB_LABEL_CLASS
|
|
59
|
+
} from "../util/view_constants";
|
|
54
60
|
|
|
55
61
|
export type BarActionsParams = {
|
|
56
62
|
values: object,
|
|
57
63
|
status: EntityStatus,
|
|
58
64
|
path: string,
|
|
59
65
|
entityId?: string | number;
|
|
66
|
+
/** Always false here — this view reads a record, it does not edit one. */
|
|
67
|
+
dirty: boolean;
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
export type OnTabChangeParams<M extends Record<string, unknown>> = {
|
|
@@ -75,6 +83,13 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
|
|
|
75
83
|
parentEntityIds: string[];
|
|
76
84
|
onTabChange?: (props: OnTabChangeParams<M>) => void;
|
|
77
85
|
onEditClick?: () => void;
|
|
86
|
+
/**
|
|
87
|
+
* Dismiss this record — the ✕ at the identity bar's trailing edge.
|
|
88
|
+
*
|
|
89
|
+
* Set by the layouts that own the record's place on screen but are not side
|
|
90
|
+
* dialogs: the split, where dismissing is navigating back to the collection.
|
|
91
|
+
*/
|
|
92
|
+
onCloseRequest?: () => void;
|
|
78
93
|
layout?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
79
94
|
barActions?: (params: BarActionsParams) => React.ReactNode;
|
|
80
95
|
/**
|
|
@@ -82,6 +97,12 @@ export interface DetailViewBindingProps<M extends Record<string, unknown> = Reco
|
|
|
82
97
|
* of the breadcrumb — the side panel's close button.
|
|
83
98
|
*/
|
|
84
99
|
barActionsStart?: React.ReactNode;
|
|
100
|
+
/**
|
|
101
|
+
* Full screen only: bring the list back beside this record. Set when the
|
|
102
|
+
* record was opened out of a split collection, so folding the list away is
|
|
103
|
+
* reversible; it takes the place of the back arrow when present.
|
|
104
|
+
*/
|
|
105
|
+
onShowList?: () => void;
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
export function DetailViewBinding<M extends Record<string, unknown>>({
|
|
@@ -137,11 +158,13 @@ function DetailViewBindingInner<M extends Record<string, unknown>>({
|
|
|
137
158
|
parentEntityIds,
|
|
138
159
|
onTabChange,
|
|
139
160
|
onEditClick,
|
|
161
|
+
onCloseRequest,
|
|
140
162
|
entity,
|
|
141
163
|
dataLoading,
|
|
142
164
|
layout = "full_screen",
|
|
143
165
|
barActions,
|
|
144
|
-
barActionsStart
|
|
166
|
+
barActionsStart,
|
|
167
|
+
onShowList
|
|
145
168
|
}: DetailViewBindingProps<M> & {
|
|
146
169
|
entity?: Entity<M>,
|
|
147
170
|
dataLoading: boolean,
|
|
@@ -189,7 +212,12 @@ entityId }
|
|
|
189
212
|
}
|
|
190
213
|
}, [selectedTabProp, defaultSelectedView]);
|
|
191
214
|
|
|
192
|
-
|
|
215
|
+
// `hideFromEntityViews`, not `hideFromNavigation`: the latter governs the
|
|
216
|
+
// drawer. Filtering tabs on it defeated the introspection generator, which
|
|
217
|
+
// sets `hideFromNavigation` on owned-child tables precisely *because* "every
|
|
218
|
+
// inbound foreign key renders as a tab on the parent" — so the rows it
|
|
219
|
+
// deliberately kept out of the drawer became reachable from nowhere.
|
|
220
|
+
const childViews = getAdminEntityChildViews(collection).filter(v => !v.collection.hideFromEntityViews);
|
|
193
221
|
const subcollections = childViews.map(v => v.collection);
|
|
194
222
|
const subcollectionsCount = subcollections?.length ?? 0;
|
|
195
223
|
const customViews = collection.entityViews ?? [];
|
|
@@ -283,18 +311,13 @@ entityId }
|
|
|
283
311
|
const formViewConfig = (collection as AdminCollection<M> & { formView?: FormViewConfig<M> }).formView;
|
|
284
312
|
const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef<EntityCustomViewParams>(formViewConfig.Builder as ComponentRef<EntityCustomViewParams>) : null;
|
|
285
313
|
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
const title = titleUser
|
|
294
|
-
? getUserLabel(titleUser)
|
|
295
|
-
: (rawTitle !== undefined && rawTitle !== null
|
|
296
|
-
? resolveTitleToString(rawTitle)
|
|
297
|
-
: (collection.singularName ?? collection.name));
|
|
314
|
+
// The heading, resolved the one way — this used to be a copy of
|
|
315
|
+
// `useEntityDisplayTitle`'s body, and the two had already drifted.
|
|
316
|
+
const title = useEntityDisplayTitle({
|
|
317
|
+
collection,
|
|
318
|
+
entity: usedEntity,
|
|
319
|
+
status: "existing"
|
|
320
|
+
});
|
|
298
321
|
|
|
299
322
|
// Non-action custom views
|
|
300
323
|
const nonActionCustomViews = useMemo(() =>
|
|
@@ -380,20 +403,32 @@ entityId }
|
|
|
380
403
|
}, [onTabChange, path, entityId, collection]);
|
|
381
404
|
|
|
382
405
|
const propertyDetailView = () => {
|
|
383
|
-
// formView.Builder replaces the default property display
|
|
406
|
+
// formView.Builder replaces the default property display. It gets the
|
|
407
|
+
// centred column the record used to be given here; the default display
|
|
408
|
+
// now lays itself out, rail included.
|
|
384
409
|
if (FormViewBuilder && usedEntity) {
|
|
385
|
-
return <
|
|
386
|
-
<
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
410
|
+
return <div className={"@container/form flex-1 min-w-0 overflow-y-auto flex justify-center items-start"}>
|
|
411
|
+
<div className={cls(
|
|
412
|
+
// Same widths as the default form (see EntityForm).
|
|
413
|
+
"w-full max-w-3xl @7xl/form:max-w-4xl 2xl:max-w-4xl flex flex-col",
|
|
414
|
+
layout === "dialog"
|
|
415
|
+
? "pt-5 pb-12 px-6 sm:px-8"
|
|
416
|
+
: "pt-6 pb-16 px-5 sm:px-8"
|
|
417
|
+
)}>
|
|
418
|
+
<ErrorBoundary>
|
|
419
|
+
<Suspense fallback={<CircularProgressCenter />}>
|
|
420
|
+
<FormViewBuilder
|
|
421
|
+
collection={collection}
|
|
422
|
+
parentCollectionSlugs={parentCollectionSlugs}
|
|
423
|
+
parentEntityIds={parentEntityIds}
|
|
424
|
+
entity={usedEntity}
|
|
425
|
+
modifiedValues={usedEntity?.values}
|
|
426
|
+
formContext={readOnlyFormContext as FormContext<Record<string, unknown>>}
|
|
427
|
+
/>
|
|
428
|
+
</Suspense>
|
|
429
|
+
</ErrorBoundary>
|
|
430
|
+
</div>
|
|
431
|
+
</div>;
|
|
397
432
|
}
|
|
398
433
|
|
|
399
434
|
return (
|
|
@@ -402,6 +437,9 @@ entityId }
|
|
|
402
437
|
entity={usedEntity}
|
|
403
438
|
collection={collection}
|
|
404
439
|
path={path}
|
|
440
|
+
asPage
|
|
441
|
+
openEntityMode={layout}
|
|
442
|
+
formContext={readOnlyFormContext}
|
|
405
443
|
/>}
|
|
406
444
|
</>
|
|
407
445
|
);
|
|
@@ -412,7 +450,7 @@ entityId }
|
|
|
412
450
|
const icon = getIcon(subcollection.icon, undefined, undefined, "smallest");
|
|
413
451
|
return (
|
|
414
452
|
<Tab
|
|
415
|
-
className=
|
|
453
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
416
454
|
value={subcollection.slug}
|
|
417
455
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
418
456
|
<span className="flex items-center gap-1.5">
|
|
@@ -428,7 +466,7 @@ entityId }
|
|
|
428
466
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
429
467
|
return (
|
|
430
468
|
<Tab
|
|
431
|
-
className={!view.tabComponent
|
|
469
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
432
470
|
value={view.key}
|
|
433
471
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
434
472
|
{view.tabComponent ?? (
|
|
@@ -445,7 +483,7 @@ entityId }
|
|
|
445
483
|
const icon = getIcon(view.icon, undefined, undefined, "smallest");
|
|
446
484
|
return (
|
|
447
485
|
<Tab
|
|
448
|
-
className={!view.tabComponent
|
|
486
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, !view.tabComponent && ENTITY_VIEW_TAB_LABEL_CLASS)}
|
|
449
487
|
value={view.key}
|
|
450
488
|
key={`entity_detail_collection_tab_${view.name}`}>
|
|
451
489
|
{view.tabComponent ?? (
|
|
@@ -458,24 +496,27 @@ entityId }
|
|
|
458
496
|
);
|
|
459
497
|
});
|
|
460
498
|
|
|
499
|
+
// The breadcrumb's destination, and the back arrow's when it renders. Both
|
|
500
|
+
// keep the view mode: a collection reached from one of its own records must
|
|
501
|
+
// come back as the user left it.
|
|
502
|
+
const collectionUrl = withViewMode(urlController.buildUrlCollectionPath(path));
|
|
503
|
+
|
|
461
504
|
// Full screen is the one layout with no way out of its own: a side panel and a
|
|
462
505
|
// dialog can be dismissed, and a split keeps the list beside it, but opening an
|
|
463
506
|
// entity full screen replaces the collection entirely and left browser Back as
|
|
464
507
|
// the only route back to it — which is not an affordance the page shows, and is
|
|
465
508
|
// wrong anyway once the user has moved between tabs inside the entity.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews || layout === "side_panel" || layout === "dialog" || Boolean(backToCollectionButton);
|
|
509
|
+
//
|
|
510
|
+
// Unless the list can be unfolded back beside the record: that chevron
|
|
511
|
+
// leads to the same collection while keeping the record open, so it stands
|
|
512
|
+
// alone rather than beside an arrow going almost the same place.
|
|
513
|
+
//
|
|
514
|
+
// It goes to the bar's leading edge — where a back control reads as back,
|
|
515
|
+
// and where the edit view has always put the same arrow — rather than to
|
|
516
|
+
// the trailing edge among the record's own actions.
|
|
517
|
+
const backToCollection = layout === "full_screen" && !onShowList
|
|
518
|
+
? () => navigate(collectionUrl)
|
|
519
|
+
: undefined;
|
|
479
520
|
|
|
480
521
|
const fullScreenButton = !barActions && // Not in split: the list panel beside it carries the close control,
|
|
481
522
|
// and an expand button there competes with it.
|
|
@@ -508,59 +549,64 @@ entityId }
|
|
|
508
549
|
// Main content view. The title, the `path/id` chip and the empty band under
|
|
509
550
|
// them have moved into the identity bar, and the `w-80 2xl:w-96` rail that
|
|
510
551
|
// held a single Edit button is gone — the bar holds it now.
|
|
552
|
+
//
|
|
553
|
+
// The scroll now lives on the record's own column rather than on this row,
|
|
554
|
+
// so the metadata rail beside it stays put while the record scrolls — the
|
|
555
|
+
// same arrangement the form uses.
|
|
511
556
|
const mainView = <div
|
|
512
557
|
className={cls(
|
|
513
|
-
"flex-1 flex flex-row w-full
|
|
558
|
+
"flex-1 flex flex-row w-full min-h-0",
|
|
514
559
|
!mainViewVisible ? "hidden" : ""
|
|
515
560
|
)}>
|
|
516
|
-
|
|
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>
|
|
561
|
+
{propertyDetailView()}
|
|
524
562
|
</div>;
|
|
525
563
|
|
|
526
564
|
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-800">
|
|
527
565
|
|
|
528
566
|
<EntityIdentityBar
|
|
529
567
|
collection={collection as AdminCollection}
|
|
568
|
+
collectionUrl={layout === "full_screen" || layout === "split" ? collectionUrl : undefined}
|
|
530
569
|
title={title}
|
|
531
570
|
entityId={entityId}
|
|
532
571
|
status={"existing"}
|
|
533
572
|
dirty={false}
|
|
534
573
|
saving={false}
|
|
574
|
+
onBack={backToCollection}
|
|
575
|
+
onClose={onCloseRequest}
|
|
535
576
|
onInspect={includeJsonView ? () => setInspectorTab("json") : undefined}
|
|
536
577
|
onViewHistory={includeHistoryView ? () => setInspectorTab("history") : undefined}
|
|
578
|
+
externalLink={usedEntity
|
|
579
|
+
? customizationController?.entityLinkBuilder?.({ entity: usedEntity })
|
|
580
|
+
: undefined}
|
|
537
581
|
leading={<>
|
|
538
582
|
{barActionsStart}
|
|
539
583
|
{/* Split view: closing the list is opening this record
|
|
540
584
|
full screen, and the control for it belongs at this
|
|
541
|
-
bar's leading edge. */}
|
|
585
|
+
bar's leading edge. Full screen carries its mirror. */}
|
|
542
586
|
{layout === "split" && <SplitListCloseButton/>}
|
|
587
|
+
{layout === "full_screen" && onShowList && <SplitListShowButton onClick={onShowList}/>}
|
|
543
588
|
</>}
|
|
544
589
|
trailing={<>
|
|
545
590
|
{editButton}
|
|
546
591
|
{pluginActionsTop}
|
|
547
|
-
{backToCollectionButton}
|
|
548
592
|
{fullScreenButton}
|
|
549
593
|
{barActions?.({
|
|
550
594
|
path,
|
|
551
595
|
entityId,
|
|
552
596
|
values: usedEntity?.values ?? {},
|
|
553
|
-
status: "existing"
|
|
597
|
+
status: "existing",
|
|
598
|
+
dirty: false
|
|
554
599
|
})}
|
|
555
600
|
</>}
|
|
556
601
|
/>
|
|
557
602
|
|
|
558
603
|
{hasAdditionalViews && <div className={cls(
|
|
559
604
|
"h-10 shrink-0 flex items-stretch border-b px-2 min-w-0",
|
|
605
|
+
ENTITY_VIEW_TAB_BAR_CLASS,
|
|
560
606
|
defaultBorderMixin
|
|
561
607
|
)}>
|
|
562
608
|
<Tabs
|
|
563
|
-
className={"!w-full"}
|
|
609
|
+
className={"!w-full items-stretch"}
|
|
564
610
|
innerClassName={"h-full"}
|
|
565
611
|
variant={"boxy"}
|
|
566
612
|
value={activeTab}
|
|
@@ -568,7 +614,8 @@ entityId }
|
|
|
568
614
|
onSideTabClick(value);
|
|
569
615
|
}}>
|
|
570
616
|
|
|
571
|
-
<Tab value={MAIN_TAB_VALUE}
|
|
617
|
+
<Tab value={MAIN_TAB_VALUE}
|
|
618
|
+
className={cls(ENTITY_VIEW_TAB_CLASS, ENTITY_VIEW_TAB_LABEL_CLASS)}>
|
|
572
619
|
<span className="flex items-center gap-1.5">
|
|
573
620
|
{getIcon(collection.icon, undefined, undefined, "smallest")}
|
|
574
621
|
{collection.singularName ?? collection.name}
|
|
@@ -28,11 +28,12 @@ export interface DrawerNavigationGroupProps {
|
|
|
28
28
|
*/
|
|
29
29
|
drawerOpen: boolean;
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
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
|
|
34
|
+
tooltipsOpen?: boolean;
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
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
|
-
*
|
|
54
|
-
*
|
|
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
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
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
|
|
101
|
+
open={!drawerOpen && wantsTooltip}
|
|
102
|
+
onOpenChange={setWantsTooltip}
|
|
75
103
|
side="right"
|
|
76
104
|
title={name}>
|
|
77
105
|
{listItem}
|