@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
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2021-2026 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
import { useRef, useState } from 'react';
|
|
3
7
|
import { Box, Button, Heading } from '@primer/react';
|
|
4
8
|
import { SlidingPanel } from './SlidingPanel';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { ToolbarProps } from
|
|
2
|
-
export declare function Toolbar({ items, extraItems, className, disabled, ariaLabel, overflow, }: ToolbarProps): import("react
|
|
1
|
+
import type { ToolbarProps } from "./types";
|
|
2
|
+
export declare function Toolbar({ items, extraItems, className, disabled, ariaLabel, overflow, }: ToolbarProps): import("react").JSX.Element;
|
|
3
3
|
export default Toolbar;
|
|
Binary file
|
|
@@ -3,6 +3,8 @@ export interface ToolbarButtonProps {
|
|
|
3
3
|
item: ToolbarButtonItem;
|
|
4
4
|
/** Size variant: 'small' for floating toolbar, 'medium' for fixed toolbar */
|
|
5
5
|
size?: 'small' | 'medium';
|
|
6
|
+
/** Print `ariaLabel` beside the icon — see the module doc. Off by default. */
|
|
7
|
+
showLabel?: boolean;
|
|
6
8
|
}
|
|
7
|
-
export declare function ToolbarButton({ item, size }: ToolbarButtonProps): import("react
|
|
9
|
+
export declare function ToolbarButton({ item, size, showLabel, }: ToolbarButtonProps): import("react").JSX.Element;
|
|
8
10
|
export default ToolbarButton;
|
|
@@ -1,4 +1,4 @@
|
|
|
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) 2025-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
@@ -15,11 +15,21 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
15
15
|
* and the tooltip of a disabled action is precisely where the why-disabled
|
|
16
16
|
* explanation lives.
|
|
17
17
|
*
|
|
18
|
+
* `showLabel` prints that same name beside the icon instead of leaving it in
|
|
19
|
+
* the tooltip alone. On the toolbar's own line an icon earns its keep — Bold,
|
|
20
|
+
* Italic, Underline read at a glance, and a name beside each would crowd a
|
|
21
|
+
* strip already short on room. Inside the "..." overflow menu there is a
|
|
22
|
+
* whole row to spend and nothing to read at a glance until the pointer
|
|
23
|
+
* happens to rest on one: a tooltip nobody is hovering says nothing, the way
|
|
24
|
+
* `ariaLabel` says it whether or not anyone points at the row at all. The
|
|
25
|
+
* `Toolbar` sets it on that menu alone; a host wiring `ToolbarButton` up on
|
|
26
|
+
* its own line does not need to know the flag exists.
|
|
27
|
+
*
|
|
18
28
|
* @module components/toolbar/ToolbarButton
|
|
19
29
|
*/
|
|
20
|
-
import { isValidElement } from 'react';
|
|
30
|
+
import { isValidElement, useState } from 'react';
|
|
21
31
|
import { Tooltip } from '@primer/react';
|
|
22
|
-
export function ToolbarButton({ item, size = 'medium' }) {
|
|
32
|
+
export function ToolbarButton({ item, size = 'medium', showLabel = false, }) {
|
|
23
33
|
const { ariaLabel, title, icon, label, isActive, onClick, disabled } = item;
|
|
24
34
|
// Resolve icon → React element.
|
|
25
35
|
// Octicon components use React.forwardRef, which returns an *object*
|
|
@@ -41,34 +51,56 @@ export function ToolbarButton({ item, size = 'medium' }) {
|
|
|
41
51
|
iconElement = _jsx("span", { style: { fontSize: 12, fontWeight: 600, lineHeight: 1 }, children: label });
|
|
42
52
|
}
|
|
43
53
|
const btnSize = size === 'small' ? 28 : 32;
|
|
44
|
-
|
|
54
|
+
// Only when there is a name to show, and only where it was asked for:
|
|
55
|
+
// showLabel with no ariaLabel (should never happen — it is required on
|
|
56
|
+
// the item — but this keeps a bare icon rather than an empty gap) falls
|
|
57
|
+
// back to the icon-only layout below.
|
|
58
|
+
const printLabel = showLabel && Boolean(ariaLabel);
|
|
59
|
+
/*
|
|
60
|
+
* Hover, as state rather than as a style written onto the element.
|
|
61
|
+
*
|
|
62
|
+
* The `Tooltip` around a titled button replays its `onMouseEnter` from a
|
|
63
|
+
* timeout, by which time React has cleared the event's `currentTarget`:
|
|
64
|
+
* writing `e.currentTarget.style` there threw "Cannot read properties of
|
|
65
|
+
* null (reading 'style')". A ref would not do either — the Tooltip clones
|
|
66
|
+
* the button with a ref of its own.
|
|
67
|
+
*/
|
|
68
|
+
const [hovered, setHovered] = useState(false);
|
|
69
|
+
const lit = hovered && !disabled;
|
|
70
|
+
const button = (_jsxs("button", { type: "button", "aria-label": ariaLabel, "aria-disabled": disabled || undefined, onMouseDown: (e) => e.preventDefault(), onClick: disabled ? undefined : onClick, style: {
|
|
45
71
|
display: 'inline-flex',
|
|
46
72
|
alignItems: 'center',
|
|
47
|
-
justifyContent: 'center',
|
|
48
|
-
|
|
73
|
+
justifyContent: printLabel ? 'flex-start' : 'center',
|
|
74
|
+
gap: printLabel ? 6 : 0,
|
|
75
|
+
// No explicit `width` when labelled: sized to its own icon-plus-name
|
|
76
|
+
// content, the same as `ToolbarDropdown`'s trigger — the two sit in
|
|
77
|
+
// the same overflow menu, and a button stretched to fill it beside a
|
|
78
|
+
// dropdown sized to its own text would read as two different kinds
|
|
79
|
+
// of row rather than one consistent list.
|
|
80
|
+
width: printLabel ? undefined : btnSize,
|
|
81
|
+
minWidth: btnSize,
|
|
49
82
|
height: btnSize,
|
|
50
|
-
padding: 0,
|
|
83
|
+
padding: printLabel ? '0 8px' : 0,
|
|
51
84
|
margin: 0,
|
|
52
85
|
border: 'none',
|
|
53
86
|
borderRadius: 6,
|
|
54
87
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
55
|
-
background:
|
|
56
|
-
|
|
88
|
+
background: lit
|
|
89
|
+
? 'var(--bgColor-neutral-muted, rgba(175,184,193,0.2))'
|
|
90
|
+
: isActive
|
|
91
|
+
? 'var(--bgColor-accent-muted, rgba(9,105,218,0.1))'
|
|
92
|
+
: 'transparent',
|
|
93
|
+
color: lit
|
|
94
|
+
? 'var(--fgColor-default, #1f2328)'
|
|
95
|
+
: isActive
|
|
96
|
+
? 'var(--fgColor-accent, #0969da)'
|
|
97
|
+
: 'var(--fgColor-muted, #656d76)',
|
|
57
98
|
opacity: disabled ? 0.5 : 1,
|
|
58
99
|
lineHeight: 1,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
}, onMouseLeave: (e) => {
|
|
65
|
-
e.currentTarget.style.background = isActive
|
|
66
|
-
? 'var(--bgColor-accent-muted, rgba(9,105,218,0.1))'
|
|
67
|
-
: 'transparent';
|
|
68
|
-
e.currentTarget.style.color = isActive
|
|
69
|
-
? 'var(--fgColor-accent, #0969da)'
|
|
70
|
-
: 'var(--fgColor-muted, #656d76)';
|
|
71
|
-
}, children: iconElement }));
|
|
100
|
+
fontSize: size === 'small' ? 12 : 14,
|
|
101
|
+
fontFamily: 'inherit',
|
|
102
|
+
whiteSpace: 'nowrap',
|
|
103
|
+
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), children: [iconElement, printLabel && _jsx("span", { children: ariaLabel })] }));
|
|
72
104
|
// No title: nothing to say, no wrapper to say it with.
|
|
73
105
|
return title ? (_jsx(Tooltip, { text: title, direction: "s", children: button })) : (button);
|
|
74
106
|
}
|
|
@@ -2,5 +2,5 @@ export interface ToolbarDividerProps {
|
|
|
2
2
|
/** Orientation: vertical for horizontal toolbars, horizontal for vertical */
|
|
3
3
|
orientation?: 'vertical' | 'horizontal';
|
|
4
4
|
}
|
|
5
|
-
export declare function ToolbarDivider({ orientation }: ToolbarDividerProps): import("react
|
|
5
|
+
export declare function ToolbarDivider({ orientation }: ToolbarDividerProps): import("react").JSX.Element;
|
|
6
6
|
export default ToolbarDivider;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { ToolbarDropdownItem } from
|
|
1
|
+
import type { ToolbarDropdownItem } from "./types";
|
|
2
2
|
export interface ToolbarDropdownProps {
|
|
3
3
|
item: ToolbarDropdownItem;
|
|
4
4
|
/** Size variant */
|
|
5
|
-
size?:
|
|
5
|
+
size?: "small" | "medium";
|
|
6
6
|
}
|
|
7
|
-
export declare function ToolbarDropdown({ item, size }: ToolbarDropdownProps): import("react
|
|
7
|
+
export declare function ToolbarDropdown({ item, size, }: ToolbarDropdownProps): import("react").JSX.Element;
|
|
8
8
|
export default ToolbarDropdown;
|
|
@@ -16,9 +16,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
*
|
|
17
17
|
* @module components/toolbar/ToolbarDropdown
|
|
18
18
|
*/
|
|
19
|
-
import { isValidElement, useMemo } from
|
|
20
|
-
import { ActionMenu, ActionList, Text } from
|
|
21
|
-
import { Box } from
|
|
19
|
+
import { isValidElement, useMemo, useState } from "react";
|
|
20
|
+
import { ActionMenu, ActionList, Text } from "@primer/react";
|
|
21
|
+
import { Box } from "../box/Box";
|
|
22
22
|
/**
|
|
23
23
|
* Resolve an icon prop to a ReactNode.
|
|
24
24
|
*
|
|
@@ -38,55 +38,86 @@ function renderIcon(icon) {
|
|
|
38
38
|
}
|
|
39
39
|
/** Whether any dropdown option in the list has an icon. */
|
|
40
40
|
function hasAnyIcon(options) {
|
|
41
|
-
return options.some(o => !!o.icon);
|
|
41
|
+
return options.some((o) => !!o.icon);
|
|
42
42
|
}
|
|
43
|
-
export function ToolbarDropdown({ item, size =
|
|
43
|
+
export function ToolbarDropdown({ item, size = "medium", }) {
|
|
44
44
|
const { ariaLabel, icon, label, minWidth, options, disabled } = item;
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
const [open, setOpen] = useState(false);
|
|
46
|
+
/*
|
|
47
|
+
* When the trigger is highlighted.
|
|
48
|
+
*
|
|
49
|
+
* Not simply "an option is active". A dropdown that *selects* something —
|
|
50
|
+
* the cell type, say — always has exactly one active option, so that rule
|
|
51
|
+
* painted it in the accent colour permanently: a control that looked
|
|
52
|
+
* switched on before it had been touched, in a blue that was Primer's
|
|
53
|
+
* rather than the theme's whenever the accent variable did not reach it.
|
|
54
|
+
*
|
|
55
|
+
* A labelled dropdown already shows its current value in the label, so the
|
|
56
|
+
* highlight tells the reader nothing they cannot see. Only a label-less one
|
|
57
|
+
* — an icon that means "filter on" — has a state worth colouring, and any
|
|
58
|
+
* dropdown is worth marking while its menu is open.
|
|
59
|
+
*/
|
|
60
|
+
const anyActive = useMemo(() => !label && options.some((o) => o.isActive), [label, options]);
|
|
47
61
|
// If any option has an icon we reserve a leading-visual column for all rows.
|
|
48
62
|
const showLeadingVisual = useMemo(() => hasAnyIcon(options), [options]);
|
|
49
|
-
const btnSize = size ===
|
|
50
|
-
return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsxs("button", { type: "button", "aria-label": ariaLabel, title: ariaLabel, disabled: disabled, style: {
|
|
51
|
-
display:
|
|
52
|
-
alignItems:
|
|
53
|
-
justifyContent:
|
|
63
|
+
const btnSize = size === "small" ? 28 : 32;
|
|
64
|
+
return (_jsxs(ActionMenu, { open: open, onOpenChange: setOpen, children: [_jsx(ActionMenu.Anchor, { children: _jsxs("button", { type: "button", "aria-label": ariaLabel, title: ariaLabel, disabled: disabled, style: {
|
|
65
|
+
display: "inline-flex",
|
|
66
|
+
alignItems: "center",
|
|
67
|
+
justifyContent: "center",
|
|
54
68
|
gap: 4,
|
|
55
69
|
minWidth: btnSize,
|
|
56
70
|
height: btnSize,
|
|
57
|
-
padding: label ?
|
|
71
|
+
padding: label ? "0 8px" : 0,
|
|
58
72
|
margin: 0,
|
|
59
|
-
border:
|
|
73
|
+
border: "none",
|
|
60
74
|
borderRadius: 6,
|
|
61
|
-
cursor: disabled ?
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
76
|
+
/*
|
|
77
|
+
Neutral while the menu is open, not accented.
|
|
78
|
+
|
|
79
|
+
An accent here is a colour the host has to have defined for this
|
|
80
|
+
subtree, and where it has not the browser falls back to Primer's
|
|
81
|
+
blue — so the control turned blue the moment it was clicked, in a
|
|
82
|
+
hue belonging to no theme on the page. The neutral pair is the
|
|
83
|
+
same one the hover state uses, which the theme does define.
|
|
84
|
+
*/
|
|
85
|
+
background: anyActive || open
|
|
86
|
+
? "var(--bgColor-neutral-muted, rgba(175,184,193,0.2))"
|
|
87
|
+
: "transparent",
|
|
88
|
+
color: anyActive || open
|
|
89
|
+
? "var(--fgColor-default, #1f2328)"
|
|
90
|
+
: "var(--fgColor-muted, #656d76)",
|
|
68
91
|
opacity: disabled ? 0.5 : 1,
|
|
69
|
-
fontSize: size ===
|
|
70
|
-
fontWeight:
|
|
71
|
-
fontFamily:
|
|
92
|
+
fontSize: size === "small" ? 12 : 14,
|
|
93
|
+
fontWeight: "normal",
|
|
94
|
+
fontFamily: "inherit",
|
|
72
95
|
lineHeight: 1,
|
|
73
96
|
}, onMouseEnter: (e) => {
|
|
74
97
|
if (!disabled) {
|
|
75
98
|
e.currentTarget.style.background =
|
|
76
|
-
|
|
77
|
-
e.currentTarget.style.color =
|
|
99
|
+
"var(--bgColor-neutral-muted, rgba(175,184,193,0.2))";
|
|
100
|
+
e.currentTarget.style.color = "var(--fgColor-default, #1f2328)";
|
|
78
101
|
}
|
|
79
102
|
}, onMouseLeave: (e) => {
|
|
80
|
-
e.currentTarget.style.background =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
e.currentTarget.style.background =
|
|
104
|
+
anyActive || open
|
|
105
|
+
? "var(--bgColor-neutral-muted, rgba(175,184,193,0.2))"
|
|
106
|
+
: "transparent";
|
|
107
|
+
e.currentTarget.style.color =
|
|
108
|
+
anyActive || open
|
|
109
|
+
? "var(--fgColor-default, #1f2328)"
|
|
110
|
+
: "var(--fgColor-muted, #656d76)";
|
|
86
111
|
}, children: [renderIcon(icon), label && (_jsx("span", { style: {
|
|
87
|
-
display:
|
|
112
|
+
display: "inline-block",
|
|
88
113
|
minWidth: minWidth ? minWidth : undefined,
|
|
89
|
-
textAlign:
|
|
90
|
-
}, children: label }))] }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: options.map(option => (_jsxs(ActionList.Item, { onSelect: option.onClick, disabled: option.disabled, active: option.isActive, children: [showLeadingVisual && (_jsx(ActionList.LeadingVisual, { children: option.icon ? renderIcon(option.icon) : _jsx(Box, { sx: { width: 16 } }) })), _jsxs(Box, { sx: {
|
|
114
|
+
textAlign: "left",
|
|
115
|
+
}, children: label }))] }) }), _jsx(ActionMenu.Overlay, { width: "auto", children: _jsx(ActionList, { children: options.map((option) => (_jsxs(ActionList.Item, { onSelect: option.onClick, disabled: option.disabled, active: option.isActive, children: [showLeadingVisual && (_jsx(ActionList.LeadingVisual, { children: option.icon ? (renderIcon(option.icon)) : (_jsx(Box, { sx: { width: 16 } })) })), _jsxs(Box, { sx: {
|
|
116
|
+
display: "flex",
|
|
117
|
+
justifyContent: "space-between",
|
|
118
|
+
alignItems: "center",
|
|
119
|
+
width: "100%",
|
|
120
|
+
gap: 3,
|
|
121
|
+
}, children: [_jsx(Text, { children: option.label }), option.shortcut && (_jsx(Text, { sx: { color: "fg.subtle", fontSize: 0, fontFamily: "mono" }, children: option.shortcut }))] })] }, option.key))) }) })] }));
|
|
91
122
|
}
|
|
92
123
|
export default ToolbarDropdown;
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import type { ToolbarItem } from
|
|
1
|
+
import type { ToolbarItem } from "./types";
|
|
2
2
|
export interface ToolbarRendererProps {
|
|
3
3
|
items: ToolbarItem[];
|
|
4
4
|
disabled?: boolean;
|
|
5
|
-
size?:
|
|
5
|
+
size?: "small" | "medium";
|
|
6
|
+
/**
|
|
7
|
+
* Print a button's name beside its icon instead of leaving it in the
|
|
8
|
+
* tooltip alone — see `ToolbarButton`. Meaningless for a dropdown, which
|
|
9
|
+
* already shows its own label wherever it is drawn.
|
|
10
|
+
*/
|
|
11
|
+
showLabel?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* How the caller lays the items out: along a row (a toolbar line, the
|
|
14
|
+
* default) or down a column (the "..." overflow menu).
|
|
15
|
+
*
|
|
16
|
+
* Only dividers read it. A divider is drawn across the direction of travel
|
|
17
|
+
* — a vertical bar between items on a row, a horizontal rule between groups
|
|
18
|
+
* in a column. Left vertical in a column it became a 20px-tall empty row
|
|
19
|
+
* with a 1px line down its left edge, which is exactly what showed under
|
|
20
|
+
* the last item of every group in the overflow menu.
|
|
21
|
+
*/
|
|
22
|
+
direction?: "row" | "column";
|
|
6
23
|
}
|
|
7
24
|
/**
|
|
8
25
|
* Sort items by order, then render each one according to its type.
|
|
9
26
|
*/
|
|
10
|
-
export declare function ToolbarRenderer({ items, disabled, size }: ToolbarRendererProps): import("react
|
|
27
|
+
export declare function ToolbarRenderer({ items, disabled, size, showLabel, direction, }: ToolbarRendererProps): import("react").JSX.Element;
|
|
11
28
|
export default ToolbarRenderer;
|
|
@@ -10,30 +10,30 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
10
10
|
*
|
|
11
11
|
* @module components/toolbar/ToolbarRenderer
|
|
12
12
|
*/
|
|
13
|
-
import { Fragment } from
|
|
14
|
-
import { Box } from
|
|
15
|
-
import { ToolbarButton } from
|
|
16
|
-
import { ToolbarDropdown } from
|
|
17
|
-
import { ToolbarDivider } from
|
|
13
|
+
import { Fragment } from "react";
|
|
14
|
+
import { Box } from "../box/Box";
|
|
15
|
+
import { ToolbarButton } from "./ToolbarButton";
|
|
16
|
+
import { ToolbarDropdown } from "./ToolbarDropdown";
|
|
17
|
+
import { ToolbarDivider } from "./ToolbarDivider";
|
|
18
18
|
/**
|
|
19
19
|
* Sort items by order, then render each one according to its type.
|
|
20
20
|
*/
|
|
21
|
-
export function ToolbarRenderer({ items, disabled, size =
|
|
21
|
+
export function ToolbarRenderer({ items, disabled, size = "medium", showLabel = false, direction = "row", }) {
|
|
22
22
|
const sorted = [...items]
|
|
23
|
-
.filter(item => !item.hidden)
|
|
23
|
+
.filter((item) => !item.hidden)
|
|
24
24
|
.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
25
|
-
return (_jsx(_Fragment, { children: sorted.map(item => {
|
|
25
|
+
return (_jsx(_Fragment, { children: sorted.map((item) => {
|
|
26
26
|
const itemDisabled = disabled || item.disabled;
|
|
27
27
|
switch (item.type) {
|
|
28
|
-
case
|
|
29
|
-
return (_jsx(Fragment, { children: _jsx(ToolbarButton, { item: { ...item, disabled: itemDisabled }, size: size }) }, item.key));
|
|
30
|
-
case
|
|
28
|
+
case "button":
|
|
29
|
+
return (_jsx(Fragment, { children: _jsx(ToolbarButton, { item: { ...item, disabled: itemDisabled }, size: size, showLabel: showLabel }) }, item.key));
|
|
30
|
+
case "dropdown":
|
|
31
31
|
return (_jsx(Fragment, { children: _jsx(ToolbarDropdown, { item: { ...item, disabled: itemDisabled }, size: size }) }, item.key));
|
|
32
|
-
case
|
|
33
|
-
return (_jsx(Fragment, { children: _jsx(ToolbarDivider, {}) }, item.key));
|
|
34
|
-
case
|
|
35
|
-
return (_jsx(Box, { sx: { flex:
|
|
36
|
-
case
|
|
32
|
+
case "divider":
|
|
33
|
+
return (_jsx(Fragment, { children: _jsx(ToolbarDivider, { orientation: direction === "column" ? "horizontal" : "vertical" }) }, item.key));
|
|
34
|
+
case "spacer":
|
|
35
|
+
return (_jsx(Box, { sx: { flex: "1 1 auto", minWidth: 8 } }, item.key));
|
|
36
|
+
case "custom":
|
|
37
37
|
return _jsx(Fragment, { children: item.render() }, item.key);
|
|
38
38
|
default:
|
|
39
39
|
return null;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type { ReactNode } from
|
|
2
|
-
import type { IconProps } from
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { IconProps } from "@primer/octicons-react";
|
|
3
3
|
/**
|
|
4
4
|
* Toolbar item types that can be registered in a toolbar.
|
|
5
5
|
*/
|
|
6
|
-
export type ToolbarItemType =
|
|
6
|
+
export type ToolbarItemType = "button" | "dropdown" | "divider" | "spacer" | "custom";
|
|
7
7
|
/**
|
|
8
8
|
* Base toolbar item definition.
|
|
9
9
|
*/
|
|
@@ -25,7 +25,7 @@ export interface ToolbarItemBase {
|
|
|
25
25
|
* Toolbar button item definition.
|
|
26
26
|
*/
|
|
27
27
|
export interface ToolbarButtonItem extends ToolbarItemBase {
|
|
28
|
-
type:
|
|
28
|
+
type: "button";
|
|
29
29
|
/** Accessible label */
|
|
30
30
|
ariaLabel: string;
|
|
31
31
|
/** Tooltip text */
|
|
@@ -64,7 +64,7 @@ export interface ToolbarDropdownOption {
|
|
|
64
64
|
* Toolbar dropdown item definition.
|
|
65
65
|
*/
|
|
66
66
|
export interface ToolbarDropdownItem extends ToolbarItemBase {
|
|
67
|
-
type:
|
|
67
|
+
type: "dropdown";
|
|
68
68
|
/** Accessible label */
|
|
69
69
|
ariaLabel: string;
|
|
70
70
|
/** Tooltip text */
|
|
@@ -86,7 +86,7 @@ export interface ToolbarDropdownItem extends ToolbarItemBase {
|
|
|
86
86
|
* Toolbar divider item definition.
|
|
87
87
|
*/
|
|
88
88
|
export interface ToolbarDividerItem extends ToolbarItemBase {
|
|
89
|
-
type:
|
|
89
|
+
type: "divider";
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
92
92
|
* Toolbar spacer item definition.
|
|
@@ -95,13 +95,13 @@ export interface ToolbarDividerItem extends ToolbarItemBase {
|
|
|
95
95
|
* the trailing edge — e.g. a status indicator kept away from the actions.
|
|
96
96
|
*/
|
|
97
97
|
export interface ToolbarSpacerItem extends ToolbarItemBase {
|
|
98
|
-
type:
|
|
98
|
+
type: "spacer";
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* Toolbar custom item definition.
|
|
102
102
|
*/
|
|
103
103
|
export interface ToolbarCustomItem extends ToolbarItemBase {
|
|
104
|
-
type:
|
|
104
|
+
type: "custom";
|
|
105
105
|
/** Custom render function */
|
|
106
106
|
render: () => ReactNode;
|
|
107
107
|
}
|
package/lib/index.js
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReactorReactOutput } from "@datalayer/reactor/react";
|
|
2
|
+
export declare const APPEARANCE_PLUGIN_NAME = "@datalayer/primer-appearance";
|
|
3
|
+
/** The id of the slot component, for a host that looks it up. */
|
|
4
|
+
export declare const APPEARANCE_MENU_COMPONENT_ID = "appearance-menu";
|
|
5
|
+
export type AppearancePluginConfig = {
|
|
6
|
+
/** The slot the menu renders into. */
|
|
7
|
+
slot: string;
|
|
8
|
+
/**
|
|
9
|
+
* Its place among that slot's components: lower first. High by default, so
|
|
10
|
+
* the menu sits at the trailing edge of a header, after a view selector.
|
|
11
|
+
*/
|
|
12
|
+
order: number;
|
|
13
|
+
/** The trigger's size in pixels. */
|
|
14
|
+
size: number;
|
|
15
|
+
/** The trigger's shape. */
|
|
16
|
+
shape: "square" | "circle";
|
|
17
|
+
/** Which way the overlay opens from the trigger. */
|
|
18
|
+
placement: "bottom-start" | "bottom-end";
|
|
19
|
+
/**
|
|
20
|
+
* Whether the overlay carries the chosen theme's description and its
|
|
21
|
+
* preview, or only the circles.
|
|
22
|
+
*/
|
|
23
|
+
showThemePreviews: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether choosing a theme also switches to its default color mode. Off,
|
|
26
|
+
* as in the public header: a person who chose dark keeps dark.
|
|
27
|
+
*/
|
|
28
|
+
applyThemeColorMode: boolean;
|
|
29
|
+
};
|
|
30
|
+
export declare const AppearancePlugin: import("@datalayer/reactor").ReactorPlugin<AppearancePluginConfig, unknown, ReactorReactOutput>;
|
|
31
|
+
export default AppearancePlugin;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `@datalayer/primer-appearance` — the appearance menu, as a reactor plugin.
|
|
7
|
+
*
|
|
8
|
+
* The control the public Datalayer header wears at its right edge: a small
|
|
9
|
+
* square showing the color mode which, hovered, opens the appearance overlay
|
|
10
|
+
* — light, dark and auto as a segmented control; one circle per theme; the
|
|
11
|
+
* chosen theme's name and description; and a "Show preview" button that
|
|
12
|
+
* unfolds a live card of it. That is {@link AppearanceMenuWithStore}, and
|
|
13
|
+
* every reactor host that wanted it wrote the same slot component around it.
|
|
14
|
+
* This plugin is that component, written once: a host lists the plugin, names
|
|
15
|
+
* a slot if the default is not `header`, and the menu is there.
|
|
16
|
+
*
|
|
17
|
+
* It depends on {@link ThemePlugin}: the menu changes the color mode, and the
|
|
18
|
+
* theme plugin is what keeps Primer's portals — a palette, a menu — in the
|
|
19
|
+
* mode chosen. Listing this plugin brings that one along.
|
|
20
|
+
*
|
|
21
|
+
* @module reactor/AppearancePlugin
|
|
22
|
+
*/
|
|
23
|
+
import { createElement } from "react";
|
|
24
|
+
import { definePlugin } from "@datalayer/reactor";
|
|
25
|
+
import { AppearanceMenuWithStore } from "../components/appearance/AppearanceMenuWithStore";
|
|
26
|
+
import { ThemePlugin } from "./ThemePlugin";
|
|
27
|
+
export const APPEARANCE_PLUGIN_NAME = "@datalayer/primer-appearance";
|
|
28
|
+
/** The id of the slot component, for a host that looks it up. */
|
|
29
|
+
export const APPEARANCE_MENU_COMPONENT_ID = "appearance-menu";
|
|
30
|
+
export const AppearancePlugin = definePlugin({
|
|
31
|
+
name: APPEARANCE_PLUGIN_NAME,
|
|
32
|
+
displayName: "Appearance",
|
|
33
|
+
description: "The color mode and theme chooser, in the header.",
|
|
34
|
+
octicon: "paintbrush",
|
|
35
|
+
emoji: "\u{1F3A8}",
|
|
36
|
+
dependencies: [ThemePlugin],
|
|
37
|
+
config: {
|
|
38
|
+
slot: "header",
|
|
39
|
+
order: 100,
|
|
40
|
+
size: 26,
|
|
41
|
+
shape: "square",
|
|
42
|
+
placement: "bottom-end",
|
|
43
|
+
showThemePreviews: true,
|
|
44
|
+
applyThemeColorMode: false,
|
|
45
|
+
},
|
|
46
|
+
build: ({ config }) => ({
|
|
47
|
+
components: [
|
|
48
|
+
{
|
|
49
|
+
id: APPEARANCE_MENU_COMPONENT_ID,
|
|
50
|
+
slot: config.slot,
|
|
51
|
+
order: config.order,
|
|
52
|
+
Component: () => createElement(AppearanceMenuWithStore, {
|
|
53
|
+
size: config.size,
|
|
54
|
+
shape: config.shape,
|
|
55
|
+
hoverOverlayPlacement: config.placement,
|
|
56
|
+
showThemePreviews: config.showThemePreviews,
|
|
57
|
+
applyThemeColorMode: config.applyThemeColorMode,
|
|
58
|
+
}),
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}),
|
|
62
|
+
});
|
|
63
|
+
export default AppearancePlugin;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const THEME_PLUGIN_NAME = "@datalayer/primer-theme";
|
|
2
|
+
/** The command's id, for hosts that want to invoke or rebind it. */
|
|
3
|
+
export declare const TOGGLE_COLOR_MODE_COMMAND = "theme.toggleColorMode";
|
|
4
|
+
export declare const ThemePlugin: import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
5
|
+
export default ThemePlugin;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* `@datalayer/primer-theme` — the Primer theme, as a reactor plugin.
|
|
7
|
+
*
|
|
8
|
+
* Every reactor application that portals Primer overlays — a command palette,
|
|
9
|
+
* a menu — needs the same two lines of glue: `setupPrimerPortals()` before
|
|
10
|
+
* anything portals, and again on every theme change so the portal root's
|
|
11
|
+
* `data-color-mode` follows the application. Each host wrote them for itself
|
|
12
|
+
* (the music example at module load, the LOOP palette inside its build), and
|
|
13
|
+
* each copy was one theme-change subscription short: a palette opened after a
|
|
14
|
+
* toggle wore the mode the page started in.
|
|
15
|
+
*
|
|
16
|
+
* This plugin is that glue, written once where the theme actually lives. It
|
|
17
|
+
* sets the portal root up when it registers, follows the shared theme store
|
|
18
|
+
* and the OS scheme (for `auto`) for as long as it runs, and contributes the
|
|
19
|
+
* command the copies never had: cycle the color mode, from the palette or by
|
|
20
|
+
* keystroke.
|
|
21
|
+
*
|
|
22
|
+
* It lives beside the theme store rather than in the reactor repo because the
|
|
23
|
+
* store is the thing being followed — the reactor knows nothing of Primer,
|
|
24
|
+
* and should not learn.
|
|
25
|
+
*
|
|
26
|
+
* @module reactor/ThemePlugin
|
|
27
|
+
*/
|
|
28
|
+
import { definePlugin } from "@datalayer/reactor";
|
|
29
|
+
import { setupPrimerPortals } from "../utils/Portals";
|
|
30
|
+
import { useThemeStore } from "../theme/useThemeStore";
|
|
31
|
+
export const THEME_PLUGIN_NAME = "@datalayer/primer-theme";
|
|
32
|
+
/** The command's id, for hosts that want to invoke or rebind it. */
|
|
33
|
+
export const TOGGLE_COLOR_MODE_COMMAND = "theme.toggleColorMode";
|
|
34
|
+
export const ThemePlugin = definePlugin({
|
|
35
|
+
name: THEME_PLUGIN_NAME,
|
|
36
|
+
displayName: "Theme",
|
|
37
|
+
description: "Keeps Primer portals in the application’s color mode, and toggles it.",
|
|
38
|
+
octicon: "sun",
|
|
39
|
+
emoji: "\u{1F317}",
|
|
40
|
+
commands: [
|
|
41
|
+
{
|
|
42
|
+
id: TOGGLE_COLOR_MODE_COMMAND,
|
|
43
|
+
name: "Toggle the color mode",
|
|
44
|
+
description: "Switch between light and dark",
|
|
45
|
+
emoji: "\u{1F317}",
|
|
46
|
+
category: "Appearance",
|
|
47
|
+
// Not Mod+Alt+T: on GNOME that is "launch a terminal", grabbed by the
|
|
48
|
+
// desktop before the browser sees the key, so the command never ran.
|
|
49
|
+
keybinding: "Mod+Alt+C",
|
|
50
|
+
execute: () => {
|
|
51
|
+
// A visible change on every press. The store's own `toggleColorMode`
|
|
52
|
+
// cycles light → dark → auto, and the step onto `auto` (or off it)
|
|
53
|
+
// shows nothing when `auto` resolves to the mode already on screen —
|
|
54
|
+
// so every third press looked like a dud, and from `auto` it took
|
|
55
|
+
// two. Resolve what is showing and go to the opposite explicit mode;
|
|
56
|
+
// `auto` remains a choice for the picker.
|
|
57
|
+
const store = useThemeStore.getState();
|
|
58
|
+
const showing = store.colorMode === "auto"
|
|
59
|
+
? typeof window !== "undefined" &&
|
|
60
|
+
window.matchMedia?.("(prefers-color-scheme: dark)").matches
|
|
61
|
+
? "dark"
|
|
62
|
+
: "light"
|
|
63
|
+
: store.colorMode;
|
|
64
|
+
store.setColorMode(showing === "dark" ? "light" : "dark");
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
register: () => {
|
|
69
|
+
// Nothing to portal into on a server.
|
|
70
|
+
if (typeof document === "undefined") {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Before anything portals: the root has to exist, in the right mode,
|
|
74
|
+
// by the time the first overlay looks for it.
|
|
75
|
+
setupPrimerPortals(useThemeStore.getState().colorMode);
|
|
76
|
+
// The user changing their mind. `setupPrimerPortals` is idempotent and
|
|
77
|
+
// re-reads the theme markers, so following the store is one call.
|
|
78
|
+
const unsubscribe = useThemeStore.subscribe((state, previous) => {
|
|
79
|
+
if (state.colorMode !== previous.colorMode) {
|
|
80
|
+
setupPrimerPortals(state.colorMode);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
// The OS changing its mind, which only matters on `auto`: the store's
|
|
84
|
+
// value has not moved, but what it resolves to has.
|
|
85
|
+
const scheme = window.matchMedia("(prefers-color-scheme: dark)");
|
|
86
|
+
const followScheme = () => {
|
|
87
|
+
if (useThemeStore.getState().colorMode === "auto") {
|
|
88
|
+
setupPrimerPortals("auto");
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
scheme.addEventListener("change", followScheme);
|
|
92
|
+
return () => {
|
|
93
|
+
unsubscribe();
|
|
94
|
+
scheme.removeEventListener("change", followScheme);
|
|
95
|
+
};
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
export default ThemePlugin;
|