@datalayer/primer-addons 1.0.20 → 1.0.22
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/README.md +14 -0
- package/lib/components/appearance/AppearanceMenuWithStore.d.ts +26 -0
- package/lib/components/appearance/AppearanceMenuWithStore.js +21 -0
- package/lib/components/appearance/CollaboratorPalette.d.ts +65 -0
- package/lib/components/appearance/CollaboratorPalette.js +160 -0
- package/lib/components/appearance/index.d.ts +2 -0
- package/lib/components/appearance/index.js +2 -0
- package/lib/components/box/Box.d.ts +17 -5
- package/lib/components/box/Box.js +3 -5
- package/lib/components/calendar-picker/CalendarPicker.d.ts +35 -0
- package/lib/components/calendar-picker/CalendarPicker.js +146 -0
- package/lib/components/calendar-picker/index.d.ts +1 -0
- package/lib/components/calendar-picker/index.js +5 -0
- package/lib/components/card/Card.d.ts +6 -1
- package/lib/components/card/Card.js +1 -1
- package/lib/components/color/ColorSwatch.d.ts +1 -1
- package/lib/components/color-picker/ColorPicker.d.ts +97 -0
- package/lib/components/color-picker/ColorPicker.js +358 -0
- package/lib/components/color-picker/index.d.ts +1 -0
- package/lib/components/color-picker/index.js +5 -0
- package/lib/components/content-loader/ContentLoader.d.ts +1 -1
- package/lib/components/content-loader/ContentLoader.stories.d.ts +1 -1
- package/lib/components/date-picker/DatePicker.d.ts +46 -0
- package/lib/components/date-picker/DatePicker.js +98 -0
- package/lib/components/date-picker/index.d.ts +1 -0
- package/lib/components/date-picker/index.js +5 -0
- package/lib/components/icons/CircleIcon.d.ts +1 -1
- package/lib/components/icons/CircleIcon.stories.d.ts +1 -1
- package/lib/components/index.d.ts +5 -0
- package/lib/components/index.js +5 -0
- package/lib/components/logo/DatalayerText.d.ts +1 -1
- package/lib/components/logo/index.d.ts +0 -1
- package/lib/components/logo/index.js +0 -1
- package/lib/components/side-overlay/SideOverlay.d.ts +5 -5
- package/lib/components/side-overlay/SideOverlay.stories.d.ts +1 -1
- package/lib/components/slider/Slider.d.ts +1 -1
- package/lib/components/slider/Slider.stories.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +1 -1
- package/lib/components/toolbar/Toolbar.d.ts +2 -2
- package/lib/components/toolbar/Toolbar.js +0 -0
- package/lib/components/toolbar/ToolbarButton.d.ts +3 -1
- package/lib/components/toolbar/ToolbarButton.js +54 -22
- package/lib/components/toolbar/ToolbarDivider.d.ts +1 -1
- package/lib/components/toolbar/ToolbarDropdown.d.ts +3 -3
- package/lib/components/toolbar/ToolbarDropdown.js +66 -35
- package/lib/components/toolbar/ToolbarRenderer.d.ts +20 -3
- package/lib/components/toolbar/ToolbarRenderer.js +16 -16
- package/lib/components/toolbar/types.d.ts +15 -8
- package/lib/reactor/AppearancePlugin.d.ts +31 -0
- package/lib/reactor/AppearancePlugin.js +63 -0
- package/lib/reactor/ThemePlugin.d.ts +5 -0
- package/lib/reactor/ThemePlugin.js +98 -0
- package/lib/reactor/index.d.ts +30 -0
- package/lib/reactor/index.js +34 -0
- package/lib/reactor/page-layout/PageLayout.d.ts +148 -0
- package/lib/reactor/page-layout/PageLayout.js +310 -0
- package/lib/reactor/page-layout/PageLayoutPlugin.d.ts +59 -0
- package/lib/reactor/page-layout/PageLayoutPlugin.js +105 -0
- package/lib/reactor/page-layout/PagePanelToggle.d.ts +21 -0
- package/lib/reactor/page-layout/PagePanelToggle.js +29 -0
- package/lib/reactor/page-layout/SlotPageLayout.d.ts +36 -0
- package/lib/reactor/page-layout/SlotPageLayout.js +25 -0
- package/lib/reactor/page-layout/panelState.d.ts +28 -0
- package/lib/reactor/page-layout/panelState.js +49 -0
- package/lib/story-helpers.d.ts +2 -2
- package/lib/theme/DatalayerBrandThemeProvider.d.ts +1 -1
- package/lib/theme/DatalayerThemeProvider.d.ts +5 -5
- package/lib/theme/DatalayerThemeProvider.js +119 -58
- package/lib/theme/css/createThemeCSSVars.d.ts +1 -0
- package/lib/theme/css/createThemeCSSVars.js +9 -0
- package/lib/theme/fontStacks.d.ts +2 -0
- package/lib/theme/fontStacks.js +6 -0
- package/lib/theme/index.d.ts +2 -0
- package/lib/theme/index.js +2 -0
- package/lib/theme/palettes/ColorPalette.d.ts +3 -3
- package/lib/theme/portableTheme.d.ts +32 -0
- package/lib/theme/portableTheme.js +45 -0
- package/lib/utils/Helper.d.ts +2 -2
- package/lib/utils/Portals.d.ts +15 -0
- package/lib/utils/Portals.js +83 -28
- package/lib/utils/Styles.d.ts +1 -1
- package/package.json +15 -14
- package/lib/components/logo/DI.d.ts +0 -19
- package/lib/components/logo/DI.js +0 -20
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* The page layout: the work on a centred sheet, like a document.
|
|
8
|
+
*
|
|
9
|
+
* A quiet canvas fills the view. On it, one sheet — the page, at a reading
|
|
10
|
+
* width, with the margins and the shadow a page has — and a band docked
|
|
11
|
+
* directly above the canvas — or, when the host asks for it, floating over
|
|
12
|
+
* the canvas as a draggable card — like the toolbar of a document
|
|
13
|
+
* application, with chips directly under it — or standing in the side panel
|
|
14
|
+
* under its content, the page bare. With no page to show, the side panel's
|
|
15
|
+
* content is the page: the same sheet, holding it, and no side panel. While
|
|
16
|
+
* something works in the page, a small line at the top of the sheet says
|
|
17
|
+
* what it is doing — "Analyst is adding a cell…" — so the change is seen
|
|
18
|
+
* where it happens. The panel is a column on the right that opens when it
|
|
19
|
+
* is wanted — on either side by `panelSide`, and by `panelMode` a column, an
|
|
20
|
+
* overlay over the page's edge or a small window in its corner; closed, the
|
|
21
|
+
* page is all there is.
|
|
22
|
+
*
|
|
23
|
+
* The sheet is a reading column by default, as tall as what is on it; asked
|
|
24
|
+
* for a paper (`pageSize`: letter, A4, or a free width and height), it takes
|
|
25
|
+
* the paper's width and is at least the paper's height.
|
|
26
|
+
*
|
|
27
|
+
* The band holds one **mount point**, and that is what gives its content its
|
|
28
|
+
* width: the same `sheetWidth` column the sheet gets, centred the same way,
|
|
29
|
+
* inside the same canvas padding. So the band and the page below it are one
|
|
30
|
+
* column with one pair of edges, and they stay one when the panel opens and
|
|
31
|
+
* narrows the canvas — because the band lives inside the page column, not
|
|
32
|
+
* across the whole view. A floating card sizes itself, which is why it never
|
|
33
|
+
* lines up with anything. It hangs at the top of the canvas, over the chips
|
|
34
|
+
* and the sheet, which start where they would with no band at all: the flow
|
|
35
|
+
* keeps no room for the card. What it covers is a drag away, and a strip of
|
|
36
|
+
* canvas held clear for it was a blank band before the page — worse than a
|
|
37
|
+
* card over the first lines.
|
|
38
|
+
*
|
|
39
|
+
* Nothing here decides what the parts do. A host wires them; this arranges
|
|
40
|
+
* them. It grew up as the Loop's page layout, where the page is a notebook or
|
|
41
|
+
* a document, the band is the composer and the panel is the conversation;
|
|
42
|
+
* the names here are the general ones.
|
|
43
|
+
*
|
|
44
|
+
* @module reactor/page-layout/PageLayout
|
|
45
|
+
*/
|
|
46
|
+
import { useEffect } from "react";
|
|
47
|
+
import { Box, Text } from "@primer/react";
|
|
48
|
+
import { useSignalValue } from "@datalayer/reactor/react";
|
|
49
|
+
import { pageLayoutActivity, pageLayoutPanelOpen, pageLayoutSheet, } from "./panelState";
|
|
50
|
+
/** A page reads best at about this width; wider and lines run too long. */
|
|
51
|
+
export const PAGE_SHEET_WIDTH = 920;
|
|
52
|
+
/** The side panel, when open. */
|
|
53
|
+
export const PAGE_PANEL_WIDTH = 400;
|
|
54
|
+
/** The papers, in CSS px (96 to the inch): US Letter 8.5 × 11 in, A4 210 × 297 mm. */
|
|
55
|
+
export const PAGE_SIZE_FORMATS = {
|
|
56
|
+
letter: { width: 816, height: 1056 },
|
|
57
|
+
a4: { width: 794, height: 1123 },
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* The width and least height a `PageSize` asks for: the format's, with any
|
|
61
|
+
* free width or height over it; `sheetWidth` and the content's height when
|
|
62
|
+
* neither says.
|
|
63
|
+
*/
|
|
64
|
+
export function resolvePageSize(size, sheetWidth = PAGE_SHEET_WIDTH) {
|
|
65
|
+
const paper = size?.format && size.format !== "free"
|
|
66
|
+
? PAGE_SIZE_FORMATS[size.format]
|
|
67
|
+
: undefined;
|
|
68
|
+
return {
|
|
69
|
+
width: size?.width ?? paper?.width ?? sheetWidth,
|
|
70
|
+
height: size?.height ?? paper?.height,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The canvas' own gutters, shared by the sheet and the band.
|
|
75
|
+
*
|
|
76
|
+
* One constant rather than two literals: the two are only aligned as long as
|
|
77
|
+
* they are equal, and a column that lines up by coincidence stops lining up
|
|
78
|
+
* the first time one of them is edited.
|
|
79
|
+
*/
|
|
80
|
+
const CANVAS_PX = [2, 3, 4];
|
|
81
|
+
/** What is happening in the page, pinned to the sheet's top edge. */
|
|
82
|
+
function ActivityLine({ label }) {
|
|
83
|
+
return (_jsxs(Box, { "data-page-activity": "", role: "status", sx: {
|
|
84
|
+
position: "sticky",
|
|
85
|
+
top: 0,
|
|
86
|
+
zIndex: 2,
|
|
87
|
+
alignSelf: "flex-start",
|
|
88
|
+
display: "inline-flex",
|
|
89
|
+
alignItems: "center",
|
|
90
|
+
gap: 2,
|
|
91
|
+
px: 2,
|
|
92
|
+
py: "4px",
|
|
93
|
+
borderRadius: "999px",
|
|
94
|
+
border: "1px solid",
|
|
95
|
+
borderColor: "accent.muted",
|
|
96
|
+
bg: "accent.subtle",
|
|
97
|
+
color: "accent.fg",
|
|
98
|
+
fontSize: 0,
|
|
99
|
+
fontWeight: "semibold",
|
|
100
|
+
whiteSpace: "nowrap",
|
|
101
|
+
boxShadow: "0 4px 12px -6px rgba(0,0,0,0.3)",
|
|
102
|
+
}, children: [_jsx(Box, { as: "span", "aria-hidden": "true", sx: {
|
|
103
|
+
width: 7,
|
|
104
|
+
height: 7,
|
|
105
|
+
borderRadius: "50%",
|
|
106
|
+
bg: "accent.fg",
|
|
107
|
+
animation: "page-activity-dot 1.1s ease-in-out infinite",
|
|
108
|
+
"@keyframes page-activity-dot": {
|
|
109
|
+
"0%, 100%": { opacity: 1 },
|
|
110
|
+
"50%": { opacity: 0.3 },
|
|
111
|
+
},
|
|
112
|
+
} }), _jsx(Text, { children: label })] }));
|
|
113
|
+
}
|
|
114
|
+
export function PageLayout({ page, hasPage = page !== undefined && page !== null, panel, band, chips, picker, transient, activity, bandMode = "docked", panelMode = "docked", panelSide = "right", sheetWidth = PAGE_SHEET_WIDTH, pageSize, panelWidth = PAGE_PANEL_WIDTH, }) {
|
|
115
|
+
const floating = bandMode === "floating";
|
|
116
|
+
const bandInPanel = bandMode === "panel";
|
|
117
|
+
const hasBand = band !== undefined && band !== null;
|
|
118
|
+
// One width for the sheet, the band's mount and the chips: the column.
|
|
119
|
+
const { width: pageWidth, height: pageHeight } = resolvePageSize(pageSize, sheetWidth);
|
|
120
|
+
const panelOpen = useSignalValue(pageLayoutPanelOpen);
|
|
121
|
+
// A panel holding the band is on screen whatever the toggle says: closing
|
|
122
|
+
// it would take the composer with it.
|
|
123
|
+
const panelShown = panelOpen || (bandInPanel && hasBand);
|
|
124
|
+
const announced = useSignalValue(pageLayoutActivity);
|
|
125
|
+
const activityLabel = activity ?? announced;
|
|
126
|
+
/*
|
|
127
|
+
* No page — a host without one, or a view that hid it — and the panel's
|
|
128
|
+
* content is what lies on the sheet. Told to the parts that only make
|
|
129
|
+
* sense beside a page, through the shared signal.
|
|
130
|
+
*/
|
|
131
|
+
const sheet = hasPage ? "page" : "panel";
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
pageLayoutSheet.value = sheet;
|
|
134
|
+
}, [sheet]);
|
|
135
|
+
return (_jsxs(Box, { "data-page-layout": "", sx: {
|
|
136
|
+
// The positioned ancestor the out-of-sight parts park against.
|
|
137
|
+
position: "relative",
|
|
138
|
+
flex: "1 1 auto",
|
|
139
|
+
minHeight: 0,
|
|
140
|
+
display: "flex",
|
|
141
|
+
flexDirection: "column",
|
|
142
|
+
}, children: [picker, _jsxs(Box, { sx: {
|
|
143
|
+
flex: "1 1 auto",
|
|
144
|
+
minHeight: 0,
|
|
145
|
+
display: "flex",
|
|
146
|
+
// The canvas the sheet lies on: a step darker than the page, so the
|
|
147
|
+
// sheet reads as paper rather than as a bordered region of the
|
|
148
|
+
// same surface.
|
|
149
|
+
bg: "canvas.inset",
|
|
150
|
+
}, children: [_jsxs(Box, { sx: {
|
|
151
|
+
flex: "1 1 auto",
|
|
152
|
+
minWidth: 0,
|
|
153
|
+
minHeight: 0,
|
|
154
|
+
display: "flex",
|
|
155
|
+
flexDirection: "column",
|
|
156
|
+
}, children: [band && bandMode === "docked" ? (_jsx(Box, { "data-page-band": "", "data-page-prompt-dock": "", sx: {
|
|
157
|
+
flex: "0 0 auto",
|
|
158
|
+
display: "flex",
|
|
159
|
+
justifyContent: "center",
|
|
160
|
+
px: CANVAS_PX,
|
|
161
|
+
// The content brings its own padding, its own border and
|
|
162
|
+
// its own surface; the band only has to carry them the full
|
|
163
|
+
// width of the canvas and close with a rule.
|
|
164
|
+
bg: "canvas.subtle",
|
|
165
|
+
borderBottom: "1px solid",
|
|
166
|
+
borderColor: "border.default",
|
|
167
|
+
}, children: _jsx(Box, { sx: { width: "100%", maxWidth: pageWidth, minWidth: 0 }, children: band }) })) : null, _jsxs(Box, { "data-page-canvas": "", sx: {
|
|
168
|
+
flex: "1 1 auto",
|
|
169
|
+
minWidth: 0,
|
|
170
|
+
minHeight: 0,
|
|
171
|
+
// A page's sheet grows with its content and the canvas
|
|
172
|
+
// scrolls; the panel's sheet fills the canvas and scrolls
|
|
173
|
+
// inside, so a conversation keeps following its stream.
|
|
174
|
+
overflowY: hasPage ? "auto" : "hidden",
|
|
175
|
+
overflowX: "hidden",
|
|
176
|
+
display: "flex",
|
|
177
|
+
flexDirection: "column",
|
|
178
|
+
alignItems: "center",
|
|
179
|
+
// The same under a floating card: the chips and the sheet
|
|
180
|
+
// start at the top, and the card hangs over them.
|
|
181
|
+
pt: 3,
|
|
182
|
+
pb: 6,
|
|
183
|
+
px: CANVAS_PX,
|
|
184
|
+
}, children: [band && chips && !bandInPanel ? (_jsx(Box, { sx: {
|
|
185
|
+
width: "100%",
|
|
186
|
+
maxWidth: pageWidth,
|
|
187
|
+
mb: 3,
|
|
188
|
+
display: "flex",
|
|
189
|
+
justifyContent: "center",
|
|
190
|
+
"& > *": { justifyContent: "center" },
|
|
191
|
+
}, children: chips })) : null, activityLabel ? (_jsx(Box, { sx: {
|
|
192
|
+
width: "100%",
|
|
193
|
+
maxWidth: pageWidth,
|
|
194
|
+
display: "flex",
|
|
195
|
+
justifyContent: "flex-end",
|
|
196
|
+
mb: 2,
|
|
197
|
+
}, children: _jsx(ActivityLine, { label: activityLabel }) })) : null, _jsx(Box, { "data-page-sheet": "", sx: {
|
|
198
|
+
width: "100%",
|
|
199
|
+
maxWidth: pageWidth,
|
|
200
|
+
/*
|
|
201
|
+
A page's sheet is at least as tall as the canvas, and grows
|
|
202
|
+
past it with its content (`1 0 auto`: grow, never shrink).
|
|
203
|
+
It used to be `0 0 auto`, its content's height only — so
|
|
204
|
+
when the layout was given more room (full screen, a taller
|
|
205
|
+
frame, a resized window) the canvas grew and the page did
|
|
206
|
+
not, leaving an empty band of canvas under a short sheet.
|
|
207
|
+
Flex does the following: nothing is measured, so any
|
|
208
|
+
change in the layout's height reaches the page at once.
|
|
209
|
+
*/
|
|
210
|
+
flex: hasPage ? "1 0 auto" : "1 1 auto",
|
|
211
|
+
bg: "canvas.default",
|
|
212
|
+
border: "1px solid",
|
|
213
|
+
borderColor: "border.default",
|
|
214
|
+
borderRadius: "10px",
|
|
215
|
+
boxShadow: "0 1px 2px rgba(0,0,0,0.06), 0 24px 48px -28px rgba(0,0,0,0.35)",
|
|
216
|
+
// The page's own margins. A page fills the sheet and reads
|
|
217
|
+
// against these, as text on paper does; a panel's content
|
|
218
|
+
// brings its own gutters, so it sits closer.
|
|
219
|
+
px: hasPage ? [3, 4, "56px"] : [2, 3, 4],
|
|
220
|
+
py: hasPage ? [3, 4, 4] : 2,
|
|
221
|
+
// What is on the sheet positions its hidden siblings
|
|
222
|
+
// against this.
|
|
223
|
+
position: "relative",
|
|
224
|
+
display: "flex",
|
|
225
|
+
// A paper's height when the size names one: the sheet is at
|
|
226
|
+
// least that tall, and grows past it with its content.
|
|
227
|
+
minHeight: hasPage ? (pageHeight ?? 480) : 0,
|
|
228
|
+
/*
|
|
229
|
+
The page fills the sheet, not the viewport: the sheet grows
|
|
230
|
+
with its content and the canvas scrolls, which is what makes
|
|
231
|
+
it a page.
|
|
232
|
+
*/
|
|
233
|
+
"& > *": { flex: "1 1 auto", minWidth: 0 },
|
|
234
|
+
}, children: hasPage ? page : panel }), hasPage || !page ? null : (_jsx(Box, { "aria-hidden": "true", sx: {
|
|
235
|
+
position: "absolute",
|
|
236
|
+
inset: 0,
|
|
237
|
+
visibility: "hidden",
|
|
238
|
+
pointerEvents: "none",
|
|
239
|
+
zIndex: -1,
|
|
240
|
+
display: "flex",
|
|
241
|
+
}, children: page }))] })] }), !hasPage || !panel ? null : panelShown ? (_jsxs(Box, { "data-page-panel": "", "data-page-panel-mode": panelMode, sx: {
|
|
242
|
+
minWidth: 0,
|
|
243
|
+
minHeight: 0,
|
|
244
|
+
display: "flex",
|
|
245
|
+
flexDirection: "column",
|
|
246
|
+
bg: "canvas.default",
|
|
247
|
+
borderColor: "border.default",
|
|
248
|
+
...(panelMode === "docked"
|
|
249
|
+
? {
|
|
250
|
+
flex: `0 0 ${panelWidth}px`,
|
|
251
|
+
maxWidth: "45%",
|
|
252
|
+
order: panelSide === "left" ? -1 : undefined,
|
|
253
|
+
[panelSide === "left" ? "borderRight" : "borderLeft"]: "1px solid",
|
|
254
|
+
// Over the canvas, and beside the band rather than under
|
|
255
|
+
// it: the band belongs to the page column.
|
|
256
|
+
zIndex: 1,
|
|
257
|
+
}
|
|
258
|
+
: panelMode === "overlay"
|
|
259
|
+
? {
|
|
260
|
+
// Over the page's right edge, the page untouched
|
|
261
|
+
// under it; against the root, as the float is.
|
|
262
|
+
position: "absolute",
|
|
263
|
+
top: 0,
|
|
264
|
+
bottom: 0,
|
|
265
|
+
[panelSide]: 0,
|
|
266
|
+
width: panelWidth,
|
|
267
|
+
maxWidth: "85%",
|
|
268
|
+
[panelSide === "left" ? "borderRight" : "borderLeft"]: "1px solid",
|
|
269
|
+
boxShadow: "shadow.large",
|
|
270
|
+
zIndex: 5,
|
|
271
|
+
}
|
|
272
|
+
: {
|
|
273
|
+
// A window in the page's corner.
|
|
274
|
+
position: "absolute",
|
|
275
|
+
[panelSide]: 16,
|
|
276
|
+
bottom: 16,
|
|
277
|
+
width: panelWidth,
|
|
278
|
+
maxWidth: "calc(100% - 32px)",
|
|
279
|
+
height: "min(70%, 560px)",
|
|
280
|
+
border: "1px solid",
|
|
281
|
+
borderRadius: 2,
|
|
282
|
+
boxShadow: "shadow.large",
|
|
283
|
+
overflow: "hidden",
|
|
284
|
+
zIndex: 5,
|
|
285
|
+
}),
|
|
286
|
+
}, children: [_jsx(Box, { sx: {
|
|
287
|
+
flex: "1 1 auto",
|
|
288
|
+
minHeight: 0,
|
|
289
|
+
display: "flex",
|
|
290
|
+
flexDirection: "column",
|
|
291
|
+
"& > *": { flex: "1 1 auto", minHeight: 0 },
|
|
292
|
+
}, children: panel }), bandInPanel && hasBand ? (_jsxs(Box, { "data-page-panel-band": "", sx: {
|
|
293
|
+
flex: "0 0 auto",
|
|
294
|
+
borderTop: "1px solid",
|
|
295
|
+
borderColor: "border.default",
|
|
296
|
+
"& > *": { justifyContent: "center" },
|
|
297
|
+
}, children: [chips, band] })) : null] })) : (
|
|
298
|
+
// Kept mounted, out of sight: a conversation is where the tools
|
|
299
|
+
// run and the stream lands, and a panel that unmounted it would
|
|
300
|
+
// stop the agent the moment the reader closed it.
|
|
301
|
+
_jsx(Box, { "aria-hidden": "true", sx: {
|
|
302
|
+
position: "absolute",
|
|
303
|
+
inset: 0,
|
|
304
|
+
visibility: "hidden",
|
|
305
|
+
pointerEvents: "none",
|
|
306
|
+
zIndex: -1,
|
|
307
|
+
display: "flex",
|
|
308
|
+
}, children: panel }))] }), transient, floating && hasBand ? band : null] }));
|
|
309
|
+
}
|
|
310
|
+
export default PageLayout;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ReactorReactOutput } from "@datalayer/reactor/react";
|
|
2
|
+
import type { Icon } from "@primer/octicons-react";
|
|
3
|
+
import type { PageSize } from "./PageLayout";
|
|
4
|
+
export declare const PAGE_LAYOUT_PLUGIN_NAME = "@datalayer/primer-page-layout";
|
|
5
|
+
/** The slots the plugin opens, by their default names. */
|
|
6
|
+
export declare const PageLayoutSlots: {
|
|
7
|
+
readonly page: "page";
|
|
8
|
+
readonly band: "page-band";
|
|
9
|
+
readonly panel: "page-panel";
|
|
10
|
+
readonly chips: "page-chips";
|
|
11
|
+
};
|
|
12
|
+
export type PageLayoutPluginConfig = {
|
|
13
|
+
/** The slot the layout renders into. */
|
|
14
|
+
slot: string;
|
|
15
|
+
/** Its place among that slot's components; lower first. */
|
|
16
|
+
order: number;
|
|
17
|
+
/** The slot the panel toggle renders into. Empty for no toggle. */
|
|
18
|
+
headerSlot: string;
|
|
19
|
+
/**
|
|
20
|
+
* The toggle's place among the header's components. Fifty by default:
|
|
21
|
+
* after a view selector, before an appearance menu at a hundred.
|
|
22
|
+
*/
|
|
23
|
+
toggleOrder: number;
|
|
24
|
+
/** What the toggle's label calls the panel: "Show the conversation". */
|
|
25
|
+
panelName: string;
|
|
26
|
+
/** The toggle's glyph; the sidebar octicon by default. */
|
|
27
|
+
toggleIcon?: Icon;
|
|
28
|
+
/** The slot for what lies on the sheet. */
|
|
29
|
+
pageSlot: string;
|
|
30
|
+
/** The slot for the band above the page. */
|
|
31
|
+
bandSlot: string;
|
|
32
|
+
/** The slot for the side panel. */
|
|
33
|
+
panelSlot: string;
|
|
34
|
+
/** The slot for the chips under the band. */
|
|
35
|
+
chipsSlot: string;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the band is docked at the sheet's width, floats over the canvas,
|
|
38
|
+
* or stands in the side panel under its content.
|
|
39
|
+
*/
|
|
40
|
+
band: "docked" | "floating" | "panel";
|
|
41
|
+
/** Where the panel stands: beside the page, over its edge, or in its corner. */
|
|
42
|
+
panel: "docked" | "overlay" | "popup";
|
|
43
|
+
/** Which side it stands on. `right` by default; `left` for a nav sidebar. */
|
|
44
|
+
panelSide: "left" | "right";
|
|
45
|
+
/** Whether the panel is open when the page first draws. */
|
|
46
|
+
panelOpen: boolean;
|
|
47
|
+
/** The sheet's width, in pixels. */
|
|
48
|
+
sheetWidth?: number;
|
|
49
|
+
/**
|
|
50
|
+
* The sheet's size: free (the default) at `sheetWidth`, or a paper —
|
|
51
|
+
* `{ format: "letter" }`, `{ format: "a4" }` — with a free `width` or
|
|
52
|
+
* `height` over it.
|
|
53
|
+
*/
|
|
54
|
+
pageSize?: PageSize;
|
|
55
|
+
/** The open panel's width, in pixels. */
|
|
56
|
+
panelWidth?: number;
|
|
57
|
+
};
|
|
58
|
+
export declare const PageLayoutPlugin: import("@datalayer/reactor").ReactorPlugin<PageLayoutPluginConfig, unknown, ReactorReactOutput>;
|
|
59
|
+
export default PageLayoutPlugin;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `@datalayer/primer-page-layout` — the page layout, as a reactor plugin.
|
|
7
|
+
*
|
|
8
|
+
* A host lists this plugin and renders one slot, `main` by default; the
|
|
9
|
+
* plugin fills it with {@link PageLayout} and opens four slots of its own for
|
|
10
|
+
* the other plugins to fill: `page` for what lies on the sheet, `page-band`
|
|
11
|
+
* for the band docked above it (a composer, a toolbar), `page-panel` for the
|
|
12
|
+
* side panel, `page-chips` for the chips under the band. A button that opens
|
|
13
|
+
* and closes the panel goes in the header — only while something fills the
|
|
14
|
+
* panel slot, so a host with no panel gets no dead button.
|
|
15
|
+
*
|
|
16
|
+
* This is the Loop's page layout with the Loop taken out: there the chat
|
|
17
|
+
* view hands the layout its parts directly through a contribution point of
|
|
18
|
+
* its own, and the Loop keeps that adapter. Here the parts are slots, which
|
|
19
|
+
* is what a plain Reactor host has.
|
|
20
|
+
*
|
|
21
|
+
* @module reactor/page-layout/PageLayoutPlugin
|
|
22
|
+
*/
|
|
23
|
+
import { createElement } from "react";
|
|
24
|
+
import { definePlugin } from "@datalayer/reactor";
|
|
25
|
+
import { SlotPageLayout, SlotPanelToggle } from "./SlotPageLayout";
|
|
26
|
+
import { pageLayoutPanelOpen } from "./panelState";
|
|
27
|
+
export const PAGE_LAYOUT_PLUGIN_NAME = "@datalayer/primer-page-layout";
|
|
28
|
+
/** The slots the plugin opens, by their default names. */
|
|
29
|
+
export const PageLayoutSlots = {
|
|
30
|
+
page: "page",
|
|
31
|
+
band: "page-band",
|
|
32
|
+
panel: "page-panel",
|
|
33
|
+
chips: "page-chips",
|
|
34
|
+
};
|
|
35
|
+
export const PageLayoutPlugin = definePlugin({
|
|
36
|
+
name: PAGE_LAYOUT_PLUGIN_NAME,
|
|
37
|
+
displayName: "Page layout",
|
|
38
|
+
description: "The page on a centred sheet, a band docked above it at the same width or floating over it, and a side panel that opens from the header.",
|
|
39
|
+
octicon: "file",
|
|
40
|
+
emoji: "\u{1F4C4}",
|
|
41
|
+
config: {
|
|
42
|
+
slot: "main",
|
|
43
|
+
order: 0,
|
|
44
|
+
headerSlot: "header",
|
|
45
|
+
toggleOrder: 50,
|
|
46
|
+
panelName: "panel",
|
|
47
|
+
toggleIcon: undefined,
|
|
48
|
+
pageSlot: PageLayoutSlots.page,
|
|
49
|
+
bandSlot: PageLayoutSlots.band,
|
|
50
|
+
panelSlot: PageLayoutSlots.panel,
|
|
51
|
+
chipsSlot: PageLayoutSlots.chips,
|
|
52
|
+
band: "docked",
|
|
53
|
+
panel: "docked",
|
|
54
|
+
panelSide: "right",
|
|
55
|
+
panelOpen: false,
|
|
56
|
+
sheetWidth: undefined,
|
|
57
|
+
pageSize: undefined,
|
|
58
|
+
panelWidth: undefined,
|
|
59
|
+
},
|
|
60
|
+
build: ({ config }) => {
|
|
61
|
+
// A host whose panel is a navigation sidebar wants it open on arrival,
|
|
62
|
+
// not behind the header's toggle. Set once, at build: after that the
|
|
63
|
+
// signal is the reader's.
|
|
64
|
+
if (config.panelOpen) {
|
|
65
|
+
pageLayoutPanelOpen.value = true;
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
components: [
|
|
69
|
+
{
|
|
70
|
+
id: "page-layout",
|
|
71
|
+
slot: config.slot,
|
|
72
|
+
order: config.order,
|
|
73
|
+
Component: (context) => createElement(SlotPageLayout, {
|
|
74
|
+
pageSlot: config.pageSlot,
|
|
75
|
+
bandSlot: config.bandSlot,
|
|
76
|
+
panelSlot: config.panelSlot,
|
|
77
|
+
chipsSlot: config.chipsSlot,
|
|
78
|
+
bandMode: config.band,
|
|
79
|
+
panelMode: config.panel,
|
|
80
|
+
panelSide: config.panelSide,
|
|
81
|
+
sheetWidth: config.sheetWidth,
|
|
82
|
+
pageSize: config.pageSize,
|
|
83
|
+
panelWidth: config.panelWidth,
|
|
84
|
+
context,
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
...(config.headerSlot
|
|
88
|
+
? [
|
|
89
|
+
{
|
|
90
|
+
id: "page-layout-panel-toggle",
|
|
91
|
+
slot: config.headerSlot,
|
|
92
|
+
order: config.toggleOrder,
|
|
93
|
+
Component: () => createElement(SlotPanelToggle, {
|
|
94
|
+
panelSlot: config.panelSlot,
|
|
95
|
+
panelName: config.panelName,
|
|
96
|
+
icon: config.toggleIcon,
|
|
97
|
+
}),
|
|
98
|
+
},
|
|
99
|
+
]
|
|
100
|
+
: []),
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
export default PageLayoutPlugin;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
import { type SxProp } from "@primer/react";
|
|
3
|
+
import { type Icon } from "@primer/octicons-react";
|
|
4
|
+
export type PagePanelToggleProps = {
|
|
5
|
+
/** What the panel holds, for the label: "Show the conversation". */
|
|
6
|
+
panelName?: string;
|
|
7
|
+
/** The button's glyph. */
|
|
8
|
+
icon?: Icon;
|
|
9
|
+
/** Extra styles — an `order` among a header's items, say. */
|
|
10
|
+
sx?: SxProp["sx"];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* The header button that opens and closes the page layout's side panel.
|
|
14
|
+
*
|
|
15
|
+
* It shares the layout's signals, so a host that opens the panel itself —
|
|
16
|
+
* `openPagePanel()` when something lands in it — sees the button follow.
|
|
17
|
+
* With the panel's content on the sheet there is nothing to open, and the
|
|
18
|
+
* button draws nothing.
|
|
19
|
+
*/
|
|
20
|
+
export declare function PagePanelToggle({ panelName, icon, sx, }: PagePanelToggleProps): JSX.Element | null;
|
|
21
|
+
export default PagePanelToggle;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { IconButton } from "@primer/react";
|
|
3
|
+
import { SidebarExpandIcon } from "@primer/octicons-react";
|
|
4
|
+
import { useSignalValue } from "@datalayer/reactor/react";
|
|
5
|
+
import { openPagePanel, pageLayoutPanelOpen, pageLayoutSheet, } from "./panelState";
|
|
6
|
+
/**
|
|
7
|
+
* The header button that opens and closes the page layout's side panel.
|
|
8
|
+
*
|
|
9
|
+
* It shares the layout's signals, so a host that opens the panel itself —
|
|
10
|
+
* `openPagePanel()` when something lands in it — sees the button follow.
|
|
11
|
+
* With the panel's content on the sheet there is nothing to open, and the
|
|
12
|
+
* button draws nothing.
|
|
13
|
+
*/
|
|
14
|
+
export function PagePanelToggle({ panelName = "panel", icon = SidebarExpandIcon, sx, }) {
|
|
15
|
+
const open = useSignalValue(pageLayoutPanelOpen);
|
|
16
|
+
const sheet = useSignalValue(pageLayoutSheet);
|
|
17
|
+
if (sheet === "panel") {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return (_jsx(IconButton, { icon: icon, size: "small", variant: open ? "default" : "invisible", "aria-label": open ? `Hide the ${panelName}` : `Show the ${panelName}`, "aria-pressed": open, onClick: () => {
|
|
21
|
+
if (open) {
|
|
22
|
+
pageLayoutPanelOpen.value = false;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
openPagePanel();
|
|
26
|
+
}
|
|
27
|
+
}, sx: { color: open ? "accent.fg" : "fg.muted", ...sx } }));
|
|
28
|
+
}
|
|
29
|
+
export default PagePanelToggle;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page layout fed from slots, for a host whose parts arrive as plugins.
|
|
3
|
+
*
|
|
4
|
+
* The layout component takes elements; a Reactor host has slot components.
|
|
5
|
+
* This reads four slots — the page, the band, the panel, the chips — and
|
|
6
|
+
* hands each to the layout as a `ReactorSlot`, or as nothing when no plugin
|
|
7
|
+
* filled it, so an empty band draws no band and an empty panel offers no
|
|
8
|
+
* toggle. Whatever the host passed to the layout's own slot goes on to every
|
|
9
|
+
* sub-slot, the way a slot's props always reach its components.
|
|
10
|
+
*
|
|
11
|
+
* @module reactor/page-layout/SlotPageLayout
|
|
12
|
+
*/
|
|
13
|
+
import type { JSX } from "react";
|
|
14
|
+
import type { Icon } from "@primer/octicons-react";
|
|
15
|
+
import { type PageLayoutProps } from "./PageLayout";
|
|
16
|
+
export type SlotPageLayoutProps = Pick<PageLayoutProps, "bandMode" | "panelMode" | "panelSide" | "sheetWidth" | "pageSize" | "panelWidth"> & {
|
|
17
|
+
pageSlot: string;
|
|
18
|
+
bandSlot: string;
|
|
19
|
+
panelSlot: string;
|
|
20
|
+
chipsSlot: string;
|
|
21
|
+
/** What the host handed the layout's slot; passed on to every sub-slot. */
|
|
22
|
+
context?: Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
export declare function SlotPageLayout({ pageSlot, bandSlot, panelSlot, chipsSlot, context, ...layout }: SlotPageLayoutProps): JSX.Element;
|
|
25
|
+
/**
|
|
26
|
+
* The panel toggle, drawn only while some plugin fills the panel slot.
|
|
27
|
+
*
|
|
28
|
+
* Its place in the header is the slot component's `order`, which the slot
|
|
29
|
+
* applies; nothing here sets a CSS order, which would reshuffle the row.
|
|
30
|
+
*/
|
|
31
|
+
export declare function SlotPanelToggle({ panelSlot, panelName, icon, }: {
|
|
32
|
+
panelSlot: string;
|
|
33
|
+
panelName?: string;
|
|
34
|
+
icon?: Icon;
|
|
35
|
+
}): JSX.Element | null;
|
|
36
|
+
export default SlotPageLayout;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ReactorSlot, useOptionalSlotComponents, } from "@datalayer/reactor/react";
|
|
3
|
+
import { PageLayout } from "./PageLayout";
|
|
4
|
+
import { PagePanelToggle } from "./PagePanelToggle";
|
|
5
|
+
export function SlotPageLayout({ pageSlot, bandSlot, panelSlot, chipsSlot, context, ...layout }) {
|
|
6
|
+
const hasPage = useOptionalSlotComponents(pageSlot).length > 0;
|
|
7
|
+
const hasBand = useOptionalSlotComponents(bandSlot).length > 0;
|
|
8
|
+
const hasPanel = useOptionalSlotComponents(panelSlot).length > 0;
|
|
9
|
+
const hasChips = useOptionalSlotComponents(chipsSlot).length > 0;
|
|
10
|
+
return (_jsx(PageLayout, { ...layout, page: hasPage ? _jsx(ReactorSlot, { slot: pageSlot, props: context }) : null, hasPage: hasPage, band: hasBand ? _jsx(ReactorSlot, { slot: bandSlot, props: context }) : null, panel: hasPanel ? _jsx(ReactorSlot, { slot: panelSlot, props: context }) : null, chips: hasChips ? _jsx(ReactorSlot, { slot: chipsSlot, props: context }) : null }));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The panel toggle, drawn only while some plugin fills the panel slot.
|
|
14
|
+
*
|
|
15
|
+
* Its place in the header is the slot component's `order`, which the slot
|
|
16
|
+
* applies; nothing here sets a CSS order, which would reshuffle the row.
|
|
17
|
+
*/
|
|
18
|
+
export function SlotPanelToggle({ panelSlot, panelName, icon, }) {
|
|
19
|
+
const hasPanel = useOptionalSlotComponents(panelSlot).length > 0;
|
|
20
|
+
if (!hasPanel) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return _jsx(PagePanelToggle, { panelName: panelName, icon: icon });
|
|
24
|
+
}
|
|
25
|
+
export default SlotPageLayout;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the side panel is open.
|
|
3
|
+
*
|
|
4
|
+
* Closed to start: the page is what a reader came for, and a panel with
|
|
5
|
+
* nothing in it yet is a column of nothing.
|
|
6
|
+
*/
|
|
7
|
+
export declare const pageLayoutPanelOpen: import("@datalayer/reactor").Signal<boolean>;
|
|
8
|
+
/**
|
|
9
|
+
* What lies on the sheet: the page, or — when there is no page to show — the
|
|
10
|
+
* panel's own content.
|
|
11
|
+
*
|
|
12
|
+
* Written by the layout as it renders, read by the parts that only make
|
|
13
|
+
* sense beside a page: the toggle would open a second copy of what is
|
|
14
|
+
* already on the sheet, and anything hung under a band to echo the panel
|
|
15
|
+
* would repeat it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const pageLayoutSheet: import("@datalayer/reactor").Signal<"page" | "panel">;
|
|
18
|
+
/**
|
|
19
|
+
* What is happening in the page, in a short line — "Analyst is adding a
|
|
20
|
+
* cell…" — or nothing. The layout pins it to the top of the sheet while it
|
|
21
|
+
* is set. A host or another plugin writes it; a layout given an `activity`
|
|
22
|
+
* prop shows that instead.
|
|
23
|
+
*/
|
|
24
|
+
export declare const pageLayoutActivity: import("@datalayer/reactor").Signal<string | undefined>;
|
|
25
|
+
/** Open the panel — for a host that wants its content seen the moment it lands. */
|
|
26
|
+
export declare function openPagePanel(): void;
|
|
27
|
+
export declare function closePagePanel(): void;
|
|
28
|
+
export declare function togglePagePanel(): void;
|