@datalayer/primer-addons 1.0.21 → 1.0.23
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 +4 -7
- package/lib/components/box/sx.js +1 -2
- 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/closeable-flash/CloseableFlash.d.ts +23 -1
- package/lib/components/closeable-flash/CloseableFlash.js +52 -4
- 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.js +4 -0
- package/lib/components/content-loader/ContentLoader.stories.d.ts +1 -1
- package/lib/components/date-picker/DatePicker.d.ts +81 -0
- package/lib/components/date-picker/DatePicker.js +204 -0
- package/lib/components/date-picker/TimeColumns.d.ts +23 -0
- package/lib/components/date-picker/TimeColumns.js +88 -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.js +4 -0
- package/lib/components/icons/CircleIcon.stories.d.ts +1 -1
- package/lib/components/index.d.ts +5 -0
- package/lib/components/index.js +9 -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 +4 -1
- package/lib/components/side-overlay/SideOverlay.d.ts +5 -5
- package/lib/components/side-overlay/SideOverlay.js +4 -0
- package/lib/components/side-overlay/SideOverlay.stories.d.ts +1 -1
- package/lib/components/side-overlay/SideOverlay.stories.js +4 -0
- package/lib/components/slider/Slider.d.ts +1 -1
- package/lib/components/slider/Slider.js +4 -0
- package/lib/components/slider/Slider.stories.d.ts +1 -1
- package/lib/components/slider/Slider.stories.js +4 -0
- package/lib/components/sliding-panel/SlidingPanel.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.js +17 -1
- package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +1 -1
- package/lib/components/sliding-panel/SlidingPanel.stories.js +4 -0
- 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 +8 -8
- package/lib/index.js +4 -0
- 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 +326 -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/palettes/ColorPalette.js +1 -2
- package/lib/theme/palettes/index.js +1 -2
- 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.js +3 -1
- package/lib/utils/Styles.d.ts +1 -1
- package/package.json +21 -14
- package/lib/components/logo/DI.d.ts +0 -19
- package/lib/components/logo/DI.js +0 -20
|
@@ -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;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The page layout's shared state: whether the side panel is open, what the
|
|
7
|
+
* sheet holds, and what is happening in the page.
|
|
8
|
+
*
|
|
9
|
+
* Signals, in a module of their own so the layout, the header toggle, the
|
|
10
|
+
* panel's own contents and a host can all read and write them without any
|
|
11
|
+
* of them importing the others.
|
|
12
|
+
*
|
|
13
|
+
* @module reactor/page-layout/panelState
|
|
14
|
+
*/
|
|
15
|
+
import { signal } from "@datalayer/reactor";
|
|
16
|
+
/**
|
|
17
|
+
* Whether the side panel is open.
|
|
18
|
+
*
|
|
19
|
+
* Closed to start: the page is what a reader came for, and a panel with
|
|
20
|
+
* nothing in it yet is a column of nothing.
|
|
21
|
+
*/
|
|
22
|
+
export const pageLayoutPanelOpen = signal(false);
|
|
23
|
+
/**
|
|
24
|
+
* What lies on the sheet: the page, or — when there is no page to show — the
|
|
25
|
+
* panel's own content.
|
|
26
|
+
*
|
|
27
|
+
* Written by the layout as it renders, read by the parts that only make
|
|
28
|
+
* sense beside a page: the toggle would open a second copy of what is
|
|
29
|
+
* already on the sheet, and anything hung under a band to echo the panel
|
|
30
|
+
* would repeat it.
|
|
31
|
+
*/
|
|
32
|
+
export const pageLayoutSheet = signal("page");
|
|
33
|
+
/**
|
|
34
|
+
* What is happening in the page, in a short line — "Analyst is adding a
|
|
35
|
+
* cell…" — or nothing. The layout pins it to the top of the sheet while it
|
|
36
|
+
* is set. A host or another plugin writes it; a layout given an `activity`
|
|
37
|
+
* prop shows that instead.
|
|
38
|
+
*/
|
|
39
|
+
export const pageLayoutActivity = signal(undefined);
|
|
40
|
+
/** Open the panel — for a host that wants its content seen the moment it lands. */
|
|
41
|
+
export function openPagePanel() {
|
|
42
|
+
pageLayoutPanelOpen.value = true;
|
|
43
|
+
}
|
|
44
|
+
export function closePagePanel() {
|
|
45
|
+
pageLayoutPanelOpen.value = false;
|
|
46
|
+
}
|
|
47
|
+
export function togglePagePanel() {
|
|
48
|
+
pageLayoutPanelOpen.value = !pageLayoutPanelOpen.value;
|
|
49
|
+
}
|
package/lib/story-helpers.d.ts
CHANGED
|
@@ -7,8 +7,8 @@ type StoryContext = Record<string, unknown> & {
|
|
|
7
7
|
};
|
|
8
8
|
parameters: Record<string, unknown>;
|
|
9
9
|
};
|
|
10
|
-
export declare const colormodeFromScheme: (colorScheme: string) => "
|
|
11
|
-
export declare const withThemeProvider: (Story: React.FC<React.PropsWithChildren<StoryContext>>, context: StoryContext) => string | number | boolean | Iterable<React.ReactNode> |
|
|
10
|
+
export declare const colormodeFromScheme: (colorScheme: string) => "light" | "dark";
|
|
11
|
+
export declare const withThemeProvider: (Story: React.FC<React.PropsWithChildren<StoryContext>>, context: StoryContext) => string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<React.ReactNode> | React.JSX.Element | null | undefined;
|
|
12
12
|
export declare const toolbarTypes: {
|
|
13
13
|
colorScheme: {
|
|
14
14
|
name: string;
|
|
@@ -38,4 +38,4 @@ export interface IDatalayerBrandThemeProviderProps {
|
|
|
38
38
|
* </DatalayerBrandThemeProvider>
|
|
39
39
|
* ```
|
|
40
40
|
*/
|
|
41
|
-
export declare function DatalayerBrandThemeProvider({ colorMode, brandTheme, style, children, }: IDatalayerBrandThemeProviderProps): JSX.Element;
|
|
41
|
+
export declare function DatalayerBrandThemeProvider({ colorMode, brandTheme, style, children, }: IDatalayerBrandThemeProviderProps): React.JSX.Element;
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { type CSSProperties } from
|
|
2
|
-
import { ThemeProviderProps } from
|
|
3
|
-
export interface IDatalayerThemeProviderProps extends Omit<ThemeProviderProps,
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
import { ThemeProviderProps } from "@primer/react";
|
|
3
|
+
export interface IDatalayerThemeProviderProps extends Omit<ThemeProviderProps, "theme" | "colorMode"> {
|
|
4
4
|
/**
|
|
5
5
|
* Color mode to use.
|
|
6
6
|
* - `'light'` / `'dark'` — explicit override
|
|
7
7
|
* - `'auto'` — follow the operating system preference (prefers-color-scheme)
|
|
8
8
|
* - Primer's `'day'` / `'night'` are still accepted.
|
|
9
9
|
*/
|
|
10
|
-
colorMode?:
|
|
10
|
+
colorMode?: "light" | "dark" | "auto" | "day" | "night";
|
|
11
11
|
/**
|
|
12
12
|
* Additional base styles merged on top of theme defaults.
|
|
13
13
|
*/
|
|
@@ -33,4 +33,4 @@ export interface IDatalayerThemeProviderProps extends Omit<ThemeProviderProps, '
|
|
|
33
33
|
dark: CSSProperties;
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
|
-
export declare function DatalayerThemeProvider(props: React.PropsWithChildren<IDatalayerThemeProviderProps>): JSX.Element;
|
|
36
|
+
export declare function DatalayerThemeProvider(props: React.PropsWithChildren<IDatalayerThemeProviderProps>): React.JSX.Element;
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/*
|
|
3
3
|
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { useEffect, useMemo } from
|
|
7
|
-
import { BaseStyles, ThemeProvider } from
|
|
8
|
-
import { useSystemColorMode } from
|
|
9
|
-
import { datalayerTheme, datalayerThemeStyles } from
|
|
10
|
-
import {
|
|
6
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
7
|
+
import { BaseStyles, ThemeProvider } from "@primer/react";
|
|
8
|
+
import { useSystemColorMode } from "./useSystemColorMode";
|
|
9
|
+
import { datalayerTheme, datalayerThemeStyles } from "./themes/datalayerTheme";
|
|
10
|
+
import { systemFontStack } from "./fontStacks";
|
|
11
|
+
import { setupPrimerPortals, syncPortalThemeStyles } from "../utils/Portals";
|
|
11
12
|
/**
|
|
12
13
|
* System sans-serif font stack — shared between `fontFamily` and
|
|
13
14
|
* the Primer `--fontStack-*` CSS custom properties so that
|
|
14
15
|
* components using the CSS `font` shorthand (e.g. `Blankslate`)
|
|
15
16
|
* also pick up the themed typeface.
|
|
16
17
|
*/
|
|
17
|
-
const SYSTEM_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"';
|
|
18
18
|
/**
|
|
19
19
|
* Shared typographic rhythm — clean, spacious feel inspired by
|
|
20
20
|
* modern developer-blog aesthetics (generous line-height, open
|
|
@@ -31,70 +31,114 @@ const SYSTEM_FONT = '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, A
|
|
|
31
31
|
*/
|
|
32
32
|
const typographyVars = {
|
|
33
33
|
/* ── Font stacks ───────────────────────────────────────────────── */
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
"--fontStack-monospace": "ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace",
|
|
35
|
+
"--fontStack-sansSerif": systemFontStack,
|
|
36
|
+
"--fontStack-sansSerifDisplay": systemFontStack,
|
|
37
|
+
"--fontStack-system": systemFontStack,
|
|
38
38
|
/* ── Base text weights (from @primer/primitives) ───────────────── */
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
"--base-text-weight-light": "300",
|
|
40
|
+
"--base-text-weight-normal": "400",
|
|
41
|
+
"--base-text-weight-medium": "500",
|
|
42
|
+
"--base-text-weight-semibold": "600",
|
|
43
43
|
/* ── Body sizing ───────────────────────────────────────────────── */
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
"--text-body-size-large": "1rem",
|
|
45
|
+
"--text-body-size-medium": "0.875rem",
|
|
46
|
+
"--text-body-size-small": "0.75rem",
|
|
47
|
+
"--text-body-lineHeight-large": "1.5",
|
|
48
|
+
"--text-body-lineHeight-medium": "1.4285",
|
|
49
|
+
"--text-body-lineHeight-small": "1.6666",
|
|
50
50
|
/* ── Title sizing ──────────────────────────────────────────────── */
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
"--text-title-size-large": "2rem",
|
|
52
|
+
"--text-title-size-medium": "1.25rem",
|
|
53
|
+
"--text-title-size-small": "1rem",
|
|
54
|
+
"--text-title-lineHeight-large": "1.5",
|
|
55
|
+
"--text-title-lineHeight-medium": "1.6",
|
|
56
|
+
"--text-title-lineHeight-small": "1.5",
|
|
57
57
|
/* ── Caption / subtitle / display ──────────────────────────────── */
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
"--text-caption-size": "0.75rem",
|
|
59
|
+
"--text-caption-lineHeight": "1.3333",
|
|
60
|
+
"--text-subtitle-size": "1.25rem",
|
|
61
|
+
"--text-subtitle-lineHeight": "1.6",
|
|
62
|
+
"--text-display-size": "2.5rem",
|
|
63
|
+
"--text-display-lineHeight": "1.4",
|
|
64
64
|
/* ── Font shorthand tokens ─────────────────────────────────────── *
|
|
65
65
|
* These are the tokens consumed by Primer components (Blankslate,
|
|
66
66
|
* etc.) via `font: var(--text-title-shorthand-medium, <fallback>)`.
|
|
67
67
|
* Defining them here prevents the hard-coded fallback from firing.
|
|
68
68
|
* Each one references `var(--fontStack-sansSerif)` or
|
|
69
|
-
*
|
|
69
|
+
* the system font stack directly (no nested `var()`) so that the CSS
|
|
70
70
|
* `font` shorthand parses reliably. Themes that override fonts
|
|
71
71
|
* (e.g. Matrix monospace) replace these shorthand tokens via
|
|
72
72
|
* `buildThemeStyles({ fontFamily })` in their `themeStyles`.
|
|
73
73
|
* ─────────────────────────────────────────────────────────────── */
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
"--text-body-shorthand-large": `400 1rem/1.5 ${systemFontStack}`,
|
|
75
|
+
"--text-body-shorthand-medium": `400 0.875rem/1.4285 ${systemFontStack}`,
|
|
76
|
+
"--text-body-shorthand-small": `400 0.75rem/1.6666 ${systemFontStack}`,
|
|
77
|
+
"--text-title-shorthand-large": `600 2rem/1.5 ${systemFontStack}`,
|
|
78
|
+
"--text-title-shorthand-medium": `600 1.25rem/1.6 ${systemFontStack}`,
|
|
79
|
+
"--text-title-shorthand-small": `600 1rem/1.5 ${systemFontStack}`,
|
|
80
|
+
"--text-caption-shorthand": `400 0.75rem/1.3333 ${systemFontStack}`,
|
|
81
|
+
"--text-subtitle-shorthand": `400 1.25rem/1.6 ${systemFontStack}`,
|
|
82
|
+
"--text-display-shorthand": `500 2.5rem/1.4 ${systemFontStack}`,
|
|
83
83
|
/* ── Custom overrides ──────────────────────────────────────────── */
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
fontFamily:
|
|
88
|
-
WebkitFontSmoothing:
|
|
89
|
-
MozOsxFontSmoothing:
|
|
90
|
-
textRendering:
|
|
84
|
+
"--text-body-lineHeight": "1.7",
|
|
85
|
+
"--text-title-lineHeight": "1.2",
|
|
86
|
+
"--text-title-letterSpacing": "-0.02em",
|
|
87
|
+
fontFamily: systemFontStack,
|
|
88
|
+
WebkitFontSmoothing: "antialiased",
|
|
89
|
+
MozOsxFontSmoothing: "grayscale",
|
|
90
|
+
textRendering: "optimizeLegibility",
|
|
91
91
|
};
|
|
92
92
|
export function DatalayerThemeProvider(props) {
|
|
93
93
|
const { children, colorMode, baseStyles, theme, themeStyles, ...rest } = props;
|
|
94
94
|
// Resolve 'auto' → actual system preference ('light' or 'dark').
|
|
95
95
|
const systemMode = useSystemColorMode();
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
/*
|
|
97
|
+
* Whether this provider sits inside another themed element, and if so what
|
|
98
|
+
* that element says.
|
|
99
|
+
*
|
|
100
|
+
* The portal root is one element shared by the whole page, and every
|
|
101
|
+
* provider used to restamp it from its own mode — so a provider nested in
|
|
102
|
+
* the page (a deck in its own theme, a widget rendered into a notebook cell
|
|
103
|
+
* as a React root of its own) wrote its mode over the application's
|
|
104
|
+
* whenever it mounted, and the next overlay came up in the wrong colour.
|
|
105
|
+
* Nested providers leave the portal root alone. And a nested provider given
|
|
106
|
+
* no `colorMode` wears the mode above it rather than a default of light: a
|
|
107
|
+
* widget inside a dark page has no business being light. Read from the DOM
|
|
108
|
+
* rather than from React context, because those widget roots are separate
|
|
109
|
+
* React trees; what they share with the application is the document.
|
|
110
|
+
*/
|
|
111
|
+
const sentinel = useRef(null);
|
|
112
|
+
const [themedAncestor, setThemedAncestor] = useState(null);
|
|
113
|
+
const [ancestorMode, setAncestorMode] = useState(null);
|
|
114
|
+
useLayoutEffect(() => {
|
|
115
|
+
// The sentinel's parent is this provider's own element; anything themed
|
|
116
|
+
// above *that* is another provider's.
|
|
117
|
+
const own = sentinel.current?.parentElement;
|
|
118
|
+
const ancestor = own?.parentElement?.closest("[data-color-mode]") ?? null;
|
|
119
|
+
setThemedAncestor(ancestor);
|
|
120
|
+
if (!ancestor) {
|
|
121
|
+
setAncestorMode(null);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const read = () => {
|
|
125
|
+
const mode = ancestor.getAttribute("data-color-mode");
|
|
126
|
+
setAncestorMode(mode === "dark" || mode === "night" ? "dark" : "light");
|
|
127
|
+
};
|
|
128
|
+
read();
|
|
129
|
+
// Rewritten on every toggle; follow it.
|
|
130
|
+
const observer = new MutationObserver(read);
|
|
131
|
+
observer.observe(ancestor, {
|
|
132
|
+
attributes: true,
|
|
133
|
+
attributeFilter: ["data-color-mode"],
|
|
134
|
+
});
|
|
135
|
+
return () => observer.disconnect();
|
|
136
|
+
}, []);
|
|
137
|
+
const nested = themedAncestor !== null;
|
|
138
|
+
const resolvedColorMode = colorMode === "auto"
|
|
139
|
+
? systemMode
|
|
140
|
+
: (colorMode ?? (nested ? (ancestorMode ?? "light") : "light"));
|
|
141
|
+
const isDark = resolvedColorMode === "dark" || resolvedColorMode === "night";
|
|
98
142
|
const resolvedTheme = theme ?? datalayerTheme;
|
|
99
143
|
const styles = themeStyles ?? datalayerThemeStyles;
|
|
100
144
|
const resolvedStyles = isDark ? styles.dark : styles.light;
|
|
@@ -109,15 +153,32 @@ export function DatalayerThemeProvider(props) {
|
|
|
109
153
|
// Keep document.body portal-root attributes AND theme styles in sync
|
|
110
154
|
// so that Primer portals (modals, dialogs, overlays) inherit the
|
|
111
155
|
// correct color mode **and** theme tokens (fonts, colors, CSS vars).
|
|
156
|
+
//
|
|
157
|
+
// Only the outermost provider does this. The portal root is one element
|
|
158
|
+
// shared by the whole page, and every provider used to restamp it from its
|
|
159
|
+
// own mode — so a provider nested in the page (a deck in its own theme, a
|
|
160
|
+
// widget jupyter-react renders into a notebook cell as a React root of its
|
|
161
|
+
// own) wrote its mode over the application's whenever it mounted, and the
|
|
162
|
+
// next overlay came up in the wrong colour. Nesting is read from the DOM
|
|
163
|
+
// rather than from React context, because those widget roots are separate
|
|
164
|
+
// React trees: what they share with the application is the document.
|
|
112
165
|
useEffect(() => {
|
|
113
|
-
|
|
166
|
+
if (nested) {
|
|
167
|
+
// Another provider owns the page's portal root; see above.
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
setupPrimerPortals(resolvedColorMode === "night"
|
|
171
|
+
? "dark"
|
|
172
|
+
: resolvedColorMode === "day"
|
|
173
|
+
? "light"
|
|
174
|
+
: resolvedColorMode);
|
|
114
175
|
syncPortalThemeStyles(portalStyles);
|
|
115
|
-
}, [resolvedColorMode, portalStyles]);
|
|
116
|
-
return (_jsx(ThemeProvider, { colorMode: resolvedColorMode, theme: resolvedTheme, ...rest, children:
|
|
117
|
-
lineHeight:
|
|
118
|
-
transition:
|
|
176
|
+
}, [nested, resolvedColorMode, portalStyles]);
|
|
177
|
+
return (_jsx(ThemeProvider, { colorMode: resolvedColorMode, theme: resolvedTheme, ...rest, children: _jsxs(BaseStyles, { style: {
|
|
178
|
+
lineHeight: "1.7",
|
|
179
|
+
transition: "background-color 0.25s ease, color 0.25s ease",
|
|
119
180
|
...typographyVars,
|
|
120
181
|
...resolvedStyles,
|
|
121
182
|
...baseStyles,
|
|
122
|
-
}, children: children }) }));
|
|
183
|
+
}, children: [_jsx("span", { ref: sentinel, hidden: true, "data-datalayer-theme-root": "" }), children] }) }));
|
|
123
184
|
}
|
|
@@ -135,6 +135,7 @@ export interface ThemeColorDefs {
|
|
|
135
135
|
* | Background | `--bgColor-default` |
|
|
136
136
|
* | Foreground | `--fgColor-default` |
|
|
137
137
|
* | Border | `--borderColor-default` |
|
|
138
|
+
* | Field (rest) | `--control-borderColor-rest` |
|
|
138
139
|
* | Link | `--fgColor-link` |
|
|
139
140
|
* | Accent / Success | `--bgColor-accent-emphasis` |
|
|
140
141
|
* | Default button | `--button-default-bgColor-rest` |
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* | Background | `--bgColor-default` |
|
|
20
20
|
* | Foreground | `--fgColor-default` |
|
|
21
21
|
* | Border | `--borderColor-default` |
|
|
22
|
+
* | Field (rest) | `--control-borderColor-rest` |
|
|
22
23
|
* | Link | `--fgColor-link` |
|
|
23
24
|
* | Accent / Success | `--bgColor-accent-emphasis` |
|
|
24
25
|
* | Default button | `--button-default-bgColor-rest` |
|
|
@@ -219,6 +220,14 @@ export function colorDefsToCSS(defs, mode = 'light') {
|
|
|
219
220
|
'--control-transparent-bgColor-selected': defs.btn.selectedBg,
|
|
220
221
|
'--control-transparent-borderColor-hover': 'transparent',
|
|
221
222
|
'--control-transparent-borderColor-active': 'transparent',
|
|
223
|
+
/* ── Fields (rest state) ──────────────────────────────────────
|
|
224
|
+
* `TextInput` / `Textarea` / `Select` paint their inactive border
|
|
225
|
+
* with `var(--control-borderColor-rest, var(--color-btn-border))`
|
|
226
|
+
* — both legacy fallbacks apps that skip @primer/primitives CSS
|
|
227
|
+
* never get — so without this the border shorthand is invalid and
|
|
228
|
+
* the field shows no border at all until it is focused. */
|
|
229
|
+
'--control-borderColor-rest': defs.border?.default ?? defs.btn.border,
|
|
230
|
+
'--control-fgColor-rest': defs.fg.default,
|
|
222
231
|
/* ── Disabled controls / fields ─────────────────────────────── */
|
|
223
232
|
'--control-bgColor-disabled': defs.canvas.subtle ?? defs.btn.bg,
|
|
224
233
|
'--control-borderColor-disabled': defs.border?.default ?? defs.btn.border,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** Default typeface used when a Datalayer theme does not override typography. */
|
|
6
|
+
export const systemFontStack = '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"';
|
package/lib/theme/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export * from './themes/ivoryTheme';
|
|
|
13
13
|
export * from './themes/sunTheme';
|
|
14
14
|
export * from './themes-brand/spatialBrandTheme';
|
|
15
15
|
export * from './themeRegistry';
|
|
16
|
+
export * from './portableTheme';
|
|
17
|
+
export * from './fontStacks';
|
|
16
18
|
export * from './palettes';
|
|
17
19
|
export * from './useSystemColorMode';
|
|
18
20
|
export * from './useThemeStore';
|
package/lib/theme/index.js
CHANGED
|
@@ -17,6 +17,8 @@ export * from './themes/ivoryTheme';
|
|
|
17
17
|
export * from './themes/sunTheme';
|
|
18
18
|
export * from './themes-brand/spatialBrandTheme';
|
|
19
19
|
export * from './themeRegistry';
|
|
20
|
+
export * from './portableTheme';
|
|
21
|
+
export * from './fontStacks';
|
|
20
22
|
export * from './palettes';
|
|
21
23
|
export * from './useSystemColorMode';
|
|
22
24
|
export * from './useThemeStore';
|