@rebasepro/admin 0.13.0 → 0.13.1-canary.gcd6689e
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-CZZZpYOz.js} +3 -3
- package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-CZZZpYOz.js.map} +1 -1
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-CeoHSmUg.js} +2 -2
- package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-CeoHSmUg.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-CMKFWM2J.js} +4 -4
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js.map → RouterCollectionsStudioView-CMKFWM2J.js.map} +1 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +29 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +5 -3
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +29 -4
- package/dist/components/DetailViewBinding.d.ts +6 -0
- package/dist/components/EditViewBinding.d.ts +7 -1
- package/dist/components/EntityIdentityBar.d.ts +10 -1
- package/dist/components/EntityViewBinding.d.ts +41 -6
- package/dist/components/app/Scaffold.d.ts +4 -3
- package/dist/{export-J79rMoNj.js → export-l19WevKo.js} +2 -2
- package/dist/{export-J79rMoNj.js.map → export-l19WevKo.js.map} +1 -1
- package/dist/form/components/FieldBlock.d.ts +16 -0
- package/dist/{history-D7L_JlLO.js → history-JL1ZmSKD.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-JL1ZmSKD.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-DNrKrFQV.js} +3 -3
- package/dist/{import-BvYl--U4.js.map → import-DNrKrFQV.js.map} +1 -1
- package/dist/index.js +79 -15
- package/dist/index.js.map +1 -1
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +7 -0
- package/dist/{util-GiwPgxnL.js → util-pQ3YwBve.js} +1022 -641
- package/dist/util-pQ3YwBve.js.map +1 -0
- package/package.json +9 -9
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +5 -6
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +9 -27
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +11 -24
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +10 -33
- package/src/components/CollectionViewBinding/SlotValue.tsx +52 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +7 -5
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +115 -48
- package/src/components/DetailViewBinding.tsx +71 -54
- package/src/components/EditViewBinding.tsx +51 -33
- package/src/components/EntityIdentityBar.tsx +26 -4
- package/src/components/EntityPreviewBinding.tsx +24 -12
- package/src/components/EntityViewBinding.tsx +237 -39
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/app/Scaffold.tsx +48 -7
- package/src/form/EntityForm.tsx +6 -24
- package/src/form/components/FieldBlock.tsx +24 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +258 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/preview/PropertyPreview.tsx +1 -1
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +8 -0
- package/dist/util-GiwPgxnL.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/admin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.13.
|
|
4
|
+
"version": "0.13.1-canary.gcd6689e",
|
|
5
5
|
"description": "Rebase CMS — content management views, forms, and routing",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -83,14 +83,14 @@
|
|
|
83
83
|
"react-dropzone": "^19.1.1",
|
|
84
84
|
"react-use-measure": "^2.1.7",
|
|
85
85
|
"zod": "^4.4.3",
|
|
86
|
-
"@rebasepro/admin-types": "0.13.
|
|
87
|
-
"@rebasepro/app": "0.13.
|
|
88
|
-
"@rebasepro/
|
|
89
|
-
"@rebasepro/inference": "0.13.
|
|
90
|
-
"@rebasepro/
|
|
91
|
-
"@rebasepro/
|
|
92
|
-
"@rebasepro/
|
|
93
|
-
"@rebasepro/utils": "0.13.
|
|
86
|
+
"@rebasepro/admin-types": "0.13.1-canary.gcd6689e",
|
|
87
|
+
"@rebasepro/app": "0.13.1-canary.gcd6689e",
|
|
88
|
+
"@rebasepro/forms": "0.13.1-canary.gcd6689e",
|
|
89
|
+
"@rebasepro/inference": "0.13.1-canary.gcd6689e",
|
|
90
|
+
"@rebasepro/common": "0.13.1-canary.gcd6689e",
|
|
91
|
+
"@rebasepro/types": "0.13.1-canary.gcd6689e",
|
|
92
|
+
"@rebasepro/ui": "0.13.1-canary.gcd6689e",
|
|
93
|
+
"@rebasepro/utils": "0.13.1-canary.gcd6689e"
|
|
94
94
|
},
|
|
95
95
|
"peerDependencies": {
|
|
96
96
|
"react": ">=19.2.7",
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { AdditionalFieldDelegate, RebaseContext } from "@rebasepro/admin-types";
|
|
2
|
+
import type { Entity } from "@rebasepro/types";
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export interface AdditionalFieldValueProps<M extends Record<string, unknown>> {
|
|
6
|
+
field: AdditionalFieldDelegate<M>;
|
|
7
|
+
entity: Entity<M>;
|
|
8
|
+
context: RebaseContext;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Renders an `additionalFields` entry that supplies a `value` rather than a
|
|
13
|
+
* `Builder`.
|
|
14
|
+
*
|
|
15
|
+
* `value` is typed `string | number | Promise<string | number>`, and the promise
|
|
16
|
+
* arm has been part of that type from the start — the CSV export awaits it
|
|
17
|
+
* correctly. Every *renderer* called `.toString()` on the result instead, so an
|
|
18
|
+
* async value drew the literal text `[object Promise]` in the cell: a shape the
|
|
19
|
+
* type promised, that worked in one of its two consumers, and failed silently in
|
|
20
|
+
* the other. That is what made the obvious port of a FireCMS column — one that
|
|
21
|
+
* fetches a title from a subcollection — look supported and not be.
|
|
22
|
+
*
|
|
23
|
+
* Three call sites had the same three lines copied between them (the table cell,
|
|
24
|
+
* the form and the read-only record), which is why the bug reached all three.
|
|
25
|
+
* Now there is one.
|
|
26
|
+
*/
|
|
27
|
+
export function AdditionalFieldValue<M extends Record<string, unknown>>({
|
|
28
|
+
field,
|
|
29
|
+
entity,
|
|
30
|
+
context
|
|
31
|
+
}: AdditionalFieldValueProps<M>) {
|
|
32
|
+
|
|
33
|
+
const [resolved, setResolved] = useState<string | number | undefined>(() => {
|
|
34
|
+
const value = field.value?.({ entity, context });
|
|
35
|
+
// A synchronous value is available on the first render, so it never
|
|
36
|
+
// flashes empty on its way in.
|
|
37
|
+
return isPromise(value) ? undefined : value;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const value = field.value?.({ entity, context });
|
|
42
|
+
if (!isPromise(value)) {
|
|
43
|
+
setResolved(value);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The row may scroll out, or the record change, before this lands.
|
|
48
|
+
let live = true;
|
|
49
|
+
value.then(
|
|
50
|
+
result => {
|
|
51
|
+
if (live) setResolved(result);
|
|
52
|
+
},
|
|
53
|
+
error => {
|
|
54
|
+
if (live) setResolved(undefined);
|
|
55
|
+
console.warn(`[rebase] Additional field "${field.key}" failed to resolve:`, error);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
return () => {
|
|
59
|
+
live = false;
|
|
60
|
+
};
|
|
61
|
+
}, [field, entity, context]);
|
|
62
|
+
|
|
63
|
+
return <>{resolved === undefined || resolved === null ? null : String(resolved)}</>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isPromise(value: unknown): value is Promise<string | number | undefined> {
|
|
67
|
+
return typeof (value as Promise<unknown> | undefined)?.then === "function";
|
|
68
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AdditionalFieldDelegate, AdminCollection } from "@rebasepro/admin-types";
|
|
2
|
+
import { AdditionalFieldValue } from "../AdditionalFieldValue";
|
|
2
3
|
import React, { useCallback, useMemo, useRef } from "react";
|
|
3
4
|
import { Entity, User, Property } from "@rebasepro/types";
|
|
4
5
|
import { CollectionSize, RebaseContext } from "@rebasepro/admin-types";
|
|
@@ -214,12 +215,10 @@ export const CollectionTableBinding = function CollectionTableBinding<M extends
|
|
|
214
215
|
|
|
215
216
|
const child: React.ReactNode = Builder
|
|
216
217
|
? <Builder entity={entity} context={context}/>
|
|
217
|
-
:
|
|
218
|
-
{additionalField
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
})?.toString()}
|
|
222
|
-
</>;
|
|
218
|
+
: <AdditionalFieldValue
|
|
219
|
+
field={additionalField}
|
|
220
|
+
entity={entity}
|
|
221
|
+
context={context as RebaseContext}/>;
|
|
223
222
|
|
|
224
223
|
return (
|
|
225
224
|
<EntityTableCell
|
|
@@ -6,7 +6,8 @@ import { PropertyPreview } from "../../preview";
|
|
|
6
6
|
import { useAuthController, useCustomizationController } from "@rebasepro/app";
|
|
7
7
|
import { IconForView } from "@rebasepro/app";
|
|
8
8
|
import { BoardItemViewProps } from "@rebasepro/ui";
|
|
9
|
-
import { useCollectionSlotKeys,
|
|
9
|
+
import { useCollectionSlotKeys, useEntitySlots } from "./usePreviewSlots";
|
|
10
|
+
import { SlotValue } from "./SlotValue";
|
|
10
11
|
import type { AdminCollection } from "@rebasepro/admin-types";
|
|
11
12
|
|
|
12
13
|
export type BoardCardBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = BoardItemViewProps<Entity<M>> & {
|
|
@@ -46,13 +47,10 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
|
|
|
46
47
|
customizationController.propertyConfigs
|
|
47
48
|
);
|
|
48
49
|
|
|
49
|
-
const slots =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
slotKeys
|
|
54
|
-
),
|
|
55
|
-
[entity, collection, slotKeys]
|
|
50
|
+
const slots = useEntitySlots(
|
|
51
|
+
entity as Entity<Record<string, unknown>>,
|
|
52
|
+
collection as AdminCollection<Record<string, unknown>>,
|
|
53
|
+
slotKeys
|
|
56
54
|
);
|
|
57
55
|
|
|
58
56
|
const handleClick = useCallback((e: React.MouseEvent) => {
|
|
@@ -126,13 +124,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
|
|
|
126
124
|
selectionEnabled && "group-hover/card:opacity-30",
|
|
127
125
|
selected && "opacity-0"
|
|
128
126
|
)}>
|
|
129
|
-
<
|
|
130
|
-
property={slots.image.property}
|
|
131
|
-
propertyKey={slots.image.propertyKey}
|
|
132
|
-
size="small"
|
|
133
|
-
value={slots.image.value}
|
|
134
|
-
fill={true}
|
|
135
|
-
/>
|
|
127
|
+
<SlotValue slot={slots.image} size="small" fill={true}/>
|
|
136
128
|
</div>
|
|
137
129
|
) : (
|
|
138
130
|
<div className={cls(
|
|
@@ -177,12 +169,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
|
|
|
177
169
|
{/* Title slot */}
|
|
178
170
|
<div className="line-clamp-2 text-sm font-medium">
|
|
179
171
|
{slots.title ? (
|
|
180
|
-
<
|
|
181
|
-
propertyKey={slots.title.propertyKey}
|
|
182
|
-
value={slots.title.value}
|
|
183
|
-
property={slots.title.property}
|
|
184
|
-
size="small"
|
|
185
|
-
/>
|
|
172
|
+
<SlotValue slot={slots.title} size="small"/>
|
|
186
173
|
) : (
|
|
187
174
|
<span className="text-surface-500">{entity.id}</span>
|
|
188
175
|
)}
|
|
@@ -193,12 +180,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
|
|
|
193
180
|
{typeof slots.subtitle.value === "string" ? (
|
|
194
181
|
<Markdown source={slots.subtitle.value} size="small" />
|
|
195
182
|
) : (
|
|
196
|
-
<
|
|
197
|
-
propertyKey={slots.subtitle.propertyKey}
|
|
198
|
-
value={slots.subtitle.value}
|
|
199
|
-
property={slots.subtitle.property}
|
|
200
|
-
size="small"
|
|
201
|
-
/>
|
|
183
|
+
<SlotValue slot={slots.subtitle} size="small"/>
|
|
202
184
|
)}
|
|
203
185
|
</div>
|
|
204
186
|
) : (
|
|
@@ -24,7 +24,8 @@ import { getEntityPreviewKeys } from "../../util/previews";
|
|
|
24
24
|
import { IconForView } from "@rebasepro/app";
|
|
25
25
|
import { getIcon } from "@rebasepro/app";
|
|
26
26
|
import { getValueInPath } from "@rebasepro/utils";
|
|
27
|
-
import { useCollectionSlotKeys,
|
|
27
|
+
import { useCollectionSlotKeys, useEntitySlots, type CollectionSlotKeys } from "./usePreviewSlots";
|
|
28
|
+
import { SlotValue } from "./SlotValue";
|
|
28
29
|
import { useAdminContext } from "../../hooks/useAdminContext";
|
|
29
30
|
import { resolveEntityAction } from "../../util/resolutions";
|
|
30
31
|
|
|
@@ -628,8 +629,11 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
|
|
|
628
629
|
selectionController?: SelectionController<M>;
|
|
629
630
|
openEntityMode?: "side_panel" | "full_screen" | "split" | "dialog";
|
|
630
631
|
}) {
|
|
631
|
-
// ── Resolve slots
|
|
632
|
-
|
|
632
|
+
// ── Resolve slots ──
|
|
633
|
+
// A hook now, not a pure call: a `display` resolver may have to fetch what
|
|
634
|
+
// fills a slot, so the row subscribes to the result and re-renders when it
|
|
635
|
+
// lands. `ListRow` is memoized, which is what keeps that to the one row.
|
|
636
|
+
const slots = useEntitySlots(
|
|
633
637
|
entity as Entity<Record<string, unknown>>,
|
|
634
638
|
collection as AdminCollection<Record<string, unknown>>,
|
|
635
639
|
slotKeys
|
|
@@ -696,7 +700,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
|
|
|
696
700
|
<div className="flex-shrink-0">
|
|
697
701
|
{slots.image ? (
|
|
698
702
|
<div className={cls("w-10 h-10 rounded-lg border relative overflow-hidden bg-surface-100 dark:bg-surface-900", defaultBorderMixin)}>
|
|
699
|
-
<
|
|
703
|
+
<SlotValue slot={slots.image} size="small" fill={true}/>
|
|
700
704
|
</div>
|
|
701
705
|
) : (
|
|
702
706
|
<div className={cls("w-10 h-10 rounded-lg bg-surface-100 dark:bg-surface-900 flex items-center justify-center border", defaultBorderMixin)}>
|
|
@@ -715,13 +719,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
|
|
|
715
719
|
<div className="truncate">
|
|
716
720
|
{slots.title?.value !== undefined ? (
|
|
717
721
|
<Typography component="div" variant="body2" className="font-semibold text-surface-900 dark:text-surface-50 truncate transition-colors group-hover:text-primary-600 dark:group-hover:text-primary-400">
|
|
718
|
-
<
|
|
719
|
-
propertyKey={slots.title.propertyKey}
|
|
720
|
-
value={slots.title.value}
|
|
721
|
-
property={slots.title.property}
|
|
722
|
-
size="small"
|
|
723
|
-
textOnly={true}
|
|
724
|
-
/>
|
|
722
|
+
<SlotValue slot={slots.title} size="small" textOnly={true}/>
|
|
725
723
|
</Typography>
|
|
726
724
|
) : (
|
|
727
725
|
<Typography component="div" variant="body2" className="font-semibold text-surface-500 dark:text-surface-400 font-mono text-xs transition-colors group-hover:text-primary-600 dark:group-hover:text-primary-400">
|
|
@@ -734,13 +732,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
|
|
|
734
732
|
{slots.subtitle && (
|
|
735
733
|
<div className="truncate mt-0.5">
|
|
736
734
|
<Typography variant="caption" component="div" className="text-surface-500 dark:text-surface-400 truncate">
|
|
737
|
-
<
|
|
738
|
-
propertyKey={slots.subtitle.propertyKey}
|
|
739
|
-
value={slots.subtitle.value}
|
|
740
|
-
property={slots.subtitle.property}
|
|
741
|
-
size="small"
|
|
742
|
-
textOnly={true}
|
|
743
|
-
/>
|
|
735
|
+
<SlotValue slot={slots.subtitle} size="small" textOnly={true}/>
|
|
744
736
|
</Typography>
|
|
745
737
|
</div>
|
|
746
738
|
)}
|
|
@@ -825,12 +817,7 @@ const ListRow = React.memo(function ListRow<M extends Record<string, unknown>>({
|
|
|
825
817
|
{/* STATUS slot — hidden when row is narrow */}
|
|
826
818
|
{slots.status && (
|
|
827
819
|
<div className="flex-shrink-0 @max-[400px]:hidden">
|
|
828
|
-
<
|
|
829
|
-
propertyKey={slots.status.propertyKey}
|
|
830
|
-
value={slots.status.value}
|
|
831
|
-
property={slots.status.property}
|
|
832
|
-
size="small"
|
|
833
|
-
/>
|
|
820
|
+
<SlotValue slot={slots.status} size="small"/>
|
|
834
821
|
</div>
|
|
835
822
|
)}
|
|
836
823
|
|
|
@@ -16,7 +16,8 @@ import {
|
|
|
16
16
|
} from "@rebasepro/app";
|
|
17
17
|
import { useAnalyticsController } from "@rebasepro/app";
|
|
18
18
|
import { IconForView } from "@rebasepro/app";
|
|
19
|
-
import { useCollectionSlotKeys,
|
|
19
|
+
import { useCollectionSlotKeys, useEntitySlots } from "./usePreviewSlots";
|
|
20
|
+
import { SlotValue } from "./SlotValue";
|
|
20
21
|
|
|
21
22
|
export type EntityCardBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
22
23
|
entity: Entity<M>;
|
|
@@ -56,13 +57,10 @@ export function EntityCardBinding<M extends Record<string, unknown> = Record<str
|
|
|
56
57
|
customizationController.propertyConfigs
|
|
57
58
|
);
|
|
58
59
|
|
|
59
|
-
const slots =
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
slotKeys
|
|
64
|
-
),
|
|
65
|
-
[entity, collection, slotKeys]
|
|
60
|
+
const slots = useEntitySlots(
|
|
61
|
+
entity as Entity<Record<string, unknown>>,
|
|
62
|
+
collection as AdminCollection<Record<string, unknown>>,
|
|
63
|
+
slotKeys
|
|
66
64
|
);
|
|
67
65
|
|
|
68
66
|
const handleClick = (e?: React.MouseEvent) => {
|
|
@@ -104,13 +102,7 @@ export function EntityCardBinding<M extends Record<string, unknown> = Record<str
|
|
|
104
102
|
<div className="aspect-[4/3] relative overflow-hidden bg-surface-100 dark:bg-surface-900">
|
|
105
103
|
{slots.image ? (
|
|
106
104
|
<div className="w-full h-full">
|
|
107
|
-
<
|
|
108
|
-
property={slots.image.property}
|
|
109
|
-
propertyKey={slots.image.propertyKey}
|
|
110
|
-
size="medium"
|
|
111
|
-
value={slots.image.value}
|
|
112
|
-
fill={true}
|
|
113
|
-
/>
|
|
105
|
+
<SlotValue slot={slots.image} size="medium" fill={true}/>
|
|
114
106
|
</div>
|
|
115
107
|
) : (
|
|
116
108
|
<div className="w-full h-full flex items-center justify-center">
|
|
@@ -160,12 +152,7 @@ export function EntityCardBinding<M extends Record<string, unknown> = Record<str
|
|
|
160
152
|
{/* Title slot */}
|
|
161
153
|
<div className="truncate my-1 text-sm font-medium min-h-[20px]">
|
|
162
154
|
{slots.title ? (
|
|
163
|
-
<
|
|
164
|
-
propertyKey={slots.title.propertyKey}
|
|
165
|
-
value={slots.title.value}
|
|
166
|
-
property={slots.title.property}
|
|
167
|
-
size="small"
|
|
168
|
-
/>
|
|
155
|
+
<SlotValue slot={slots.title} size="small"/>
|
|
169
156
|
) : (
|
|
170
157
|
<Typography variant="body2" className="text-surface-500">
|
|
171
158
|
{entity.id}
|
|
@@ -176,12 +163,7 @@ export function EntityCardBinding<M extends Record<string, unknown> = Record<str
|
|
|
176
163
|
{/* Subtitle slot */}
|
|
177
164
|
{slots.subtitle && (
|
|
178
165
|
<div className="line-clamp-3 [&_div]:line-clamp-3 text-xs text-surface-600 dark:text-surface-400 [&_p]:!my-1 [&_p:first-child]:!mt-0 [&_p:last-child]:!mb-0">
|
|
179
|
-
<
|
|
180
|
-
propertyKey={slots.subtitle.propertyKey}
|
|
181
|
-
value={slots.subtitle.value}
|
|
182
|
-
property={slots.subtitle.property}
|
|
183
|
-
size="small"
|
|
184
|
-
/>
|
|
166
|
+
<SlotValue slot={slots.subtitle} size="small"/>
|
|
185
167
|
</div>
|
|
186
168
|
)}
|
|
187
169
|
|
|
@@ -211,12 +193,7 @@ export function EntityCardBinding<M extends Record<string, unknown> = Record<str
|
|
|
211
193
|
{/* Status slot */}
|
|
212
194
|
{slots.status && (
|
|
213
195
|
<div className="mt-1">
|
|
214
|
-
<
|
|
215
|
-
propertyKey={slots.status.propertyKey}
|
|
216
|
-
value={slots.status.value}
|
|
217
|
-
property={slots.status.property}
|
|
218
|
-
size="small"
|
|
219
|
-
/>
|
|
196
|
+
<SlotValue slot={slots.status} size="small"/>
|
|
220
197
|
</div>
|
|
221
198
|
)}
|
|
222
199
|
</div>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
3
|
+
import { PropertyPreview } from "../../preview";
|
|
4
|
+
import type { PreviewSlot } from "./usePreviewSlots";
|
|
5
|
+
|
|
6
|
+
export interface SlotValueProps {
|
|
7
|
+
slot: PreviewSlot | undefined;
|
|
8
|
+
size: PreviewSize;
|
|
9
|
+
/** Relations and references render as plain text rather than as cards. */
|
|
10
|
+
textOnly?: boolean;
|
|
11
|
+
/** Images only: fill the container rather than fitting inside it. */
|
|
12
|
+
fill?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* One display slot, rendered.
|
|
17
|
+
*
|
|
18
|
+
* A slot's value arrives one of two ways and they cannot be rendered the same:
|
|
19
|
+
*
|
|
20
|
+
* - **From a property** — render it with that property's own preview, so an
|
|
21
|
+
* enum status keeps its colour, a date keeps its format and a storage path
|
|
22
|
+
* becomes a thumbnail. This is why a declared path beats a resolver whenever
|
|
23
|
+
* the value is actually on the record.
|
|
24
|
+
* - **From a resolver** — there is no property, because the value was computed
|
|
25
|
+
* or fetched. All that can be said about it is that it is text.
|
|
26
|
+
*
|
|
27
|
+
* The branch lives here, once, rather than at each of the fourteen places the
|
|
28
|
+
* list row, the card and the board card render a slot. That count is the reason:
|
|
29
|
+
* the last time a rule like this was inlined per call site, it drifted into
|
|
30
|
+
* seven versions.
|
|
31
|
+
*/
|
|
32
|
+
export function SlotValue({ slot, size, textOnly, fill }: SlotValueProps) {
|
|
33
|
+
|
|
34
|
+
if (!slot || slot.value === undefined || slot.value === null) return null;
|
|
35
|
+
|
|
36
|
+
if (!slot.property || !slot.propertyKey) {
|
|
37
|
+
// Computed. Arrays (a `tags` resolver) join rather than rendering as
|
|
38
|
+
// "[object Object]" via String().
|
|
39
|
+
const text = Array.isArray(slot.value)
|
|
40
|
+
? slot.value.map(item => String(item)).join(", ")
|
|
41
|
+
: String(slot.value);
|
|
42
|
+
return <>{text}</>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return <PropertyPreview
|
|
46
|
+
propertyKey={slot.propertyKey}
|
|
47
|
+
value={slot.value as never}
|
|
48
|
+
property={slot.property}
|
|
49
|
+
size={size}
|
|
50
|
+
textOnly={textOnly}
|
|
51
|
+
fill={fill}/>;
|
|
52
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
import { IconButton, iconSize, Tooltip
|
|
3
|
+
import { ChevronsLeftIcon, IconButton, iconSize, Tooltip } from "@rebasepro/ui";
|
|
4
4
|
import { useTranslation } from "@rebasepro/app";
|
|
5
5
|
|
|
6
6
|
import { useSplitView } from "./SplitViewContext";
|
|
@@ -9,9 +9,11 @@ import { useSplitView } from "./SplitViewContext";
|
|
|
9
9
|
* Closes the list of a split view, leaving the open record on its own.
|
|
10
10
|
*
|
|
11
11
|
* It leads the record's own app bar, where the breadcrumb starts — this is the
|
|
12
|
-
* entity pane's old "Open full screen" button, moved to that edge and wearing
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* entity pane's old "Open full screen" button, moved to that edge and wearing a
|
|
13
|
+
* double chevron pointing at the list it folds away: closing the list and
|
|
14
|
+
* opening the record full screen are the same thing, so there is one control
|
|
15
|
+
* and one route (`#full`) rather than two. Full screen carries the mirrored
|
|
16
|
+
* chevron to bring the list back.
|
|
15
17
|
*/
|
|
16
18
|
export function SplitListCloseButton() {
|
|
17
19
|
|
|
@@ -26,7 +28,7 @@ export function SplitListCloseButton() {
|
|
|
26
28
|
size="small"
|
|
27
29
|
onClick={splitView.openFullScreen}
|
|
28
30
|
aria-label={t("hide_list")}>
|
|
29
|
-
<
|
|
31
|
+
<ChevronsLeftIcon size={iconSize.smallest}/>
|
|
30
32
|
</IconButton>
|
|
31
33
|
</Tooltip>
|
|
32
34
|
);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
import { ChevronsRightIcon, IconButton, iconSize, Tooltip } from "@rebasepro/ui";
|
|
4
|
+
import { useTranslation } from "@rebasepro/app";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Brings the list back beside a record that was opened full screen.
|
|
8
|
+
*
|
|
9
|
+
* The mirror of {@link SplitListCloseButton}: the same edge of the same bar,
|
|
10
|
+
* the same double chevron pointing the other way, and the same single route —
|
|
11
|
+
* showing the list is dropping the `#full` hash. It replaces the back arrow
|
|
12
|
+
* where it applies, because the list it unfolds already holds the record.
|
|
13
|
+
*/
|
|
14
|
+
export function SplitListShowButton({ onClick }: { onClick: () => void }) {
|
|
15
|
+
|
|
16
|
+
const { t } = useTranslation();
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Tooltip title={t("show_list")}>
|
|
20
|
+
<IconButton
|
|
21
|
+
size="small"
|
|
22
|
+
onClick={onClick}
|
|
23
|
+
aria-label={t("show_list")}>
|
|
24
|
+
<ChevronsRightIcon size={iconSize.smallest}/>
|
|
25
|
+
</IconButton>
|
|
26
|
+
</Tooltip>
|
|
27
|
+
);
|
|
28
|
+
}
|