@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.
Files changed (100) hide show
  1. package/README.md +14 -0
  2. package/lib/components/appearance/AppearanceMenuWithStore.d.ts +26 -0
  3. package/lib/components/appearance/AppearanceMenuWithStore.js +21 -0
  4. package/lib/components/appearance/CollaboratorPalette.d.ts +65 -0
  5. package/lib/components/appearance/CollaboratorPalette.js +160 -0
  6. package/lib/components/appearance/index.d.ts +2 -0
  7. package/lib/components/appearance/index.js +2 -0
  8. package/lib/components/box/Box.d.ts +17 -5
  9. package/lib/components/box/Box.js +4 -7
  10. package/lib/components/box/sx.js +1 -2
  11. package/lib/components/calendar-picker/CalendarPicker.d.ts +35 -0
  12. package/lib/components/calendar-picker/CalendarPicker.js +146 -0
  13. package/lib/components/calendar-picker/index.d.ts +1 -0
  14. package/lib/components/calendar-picker/index.js +5 -0
  15. package/lib/components/card/Card.d.ts +6 -1
  16. package/lib/components/card/Card.js +1 -1
  17. package/lib/components/closeable-flash/CloseableFlash.d.ts +23 -1
  18. package/lib/components/closeable-flash/CloseableFlash.js +52 -4
  19. package/lib/components/color/ColorSwatch.d.ts +1 -1
  20. package/lib/components/color-picker/ColorPicker.d.ts +97 -0
  21. package/lib/components/color-picker/ColorPicker.js +358 -0
  22. package/lib/components/color-picker/index.d.ts +1 -0
  23. package/lib/components/color-picker/index.js +5 -0
  24. package/lib/components/content-loader/ContentLoader.d.ts +1 -1
  25. package/lib/components/content-loader/ContentLoader.js +4 -0
  26. package/lib/components/content-loader/ContentLoader.stories.d.ts +1 -1
  27. package/lib/components/date-picker/DatePicker.d.ts +81 -0
  28. package/lib/components/date-picker/DatePicker.js +204 -0
  29. package/lib/components/date-picker/TimeColumns.d.ts +23 -0
  30. package/lib/components/date-picker/TimeColumns.js +88 -0
  31. package/lib/components/date-picker/index.d.ts +1 -0
  32. package/lib/components/date-picker/index.js +5 -0
  33. package/lib/components/icons/CircleIcon.d.ts +1 -1
  34. package/lib/components/icons/CircleIcon.js +4 -0
  35. package/lib/components/icons/CircleIcon.stories.d.ts +1 -1
  36. package/lib/components/index.d.ts +5 -0
  37. package/lib/components/index.js +9 -0
  38. package/lib/components/logo/DatalayerText.d.ts +1 -1
  39. package/lib/components/logo/index.d.ts +0 -1
  40. package/lib/components/logo/index.js +4 -1
  41. package/lib/components/side-overlay/SideOverlay.d.ts +5 -5
  42. package/lib/components/side-overlay/SideOverlay.js +4 -0
  43. package/lib/components/side-overlay/SideOverlay.stories.d.ts +1 -1
  44. package/lib/components/side-overlay/SideOverlay.stories.js +4 -0
  45. package/lib/components/slider/Slider.d.ts +1 -1
  46. package/lib/components/slider/Slider.js +4 -0
  47. package/lib/components/slider/Slider.stories.d.ts +1 -1
  48. package/lib/components/slider/Slider.stories.js +4 -0
  49. package/lib/components/sliding-panel/SlidingPanel.d.ts +1 -1
  50. package/lib/components/sliding-panel/SlidingPanel.js +17 -1
  51. package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +1 -1
  52. package/lib/components/sliding-panel/SlidingPanel.stories.js +4 -0
  53. package/lib/components/toolbar/Toolbar.d.ts +2 -2
  54. package/lib/components/toolbar/Toolbar.js +0 -0
  55. package/lib/components/toolbar/ToolbarButton.d.ts +3 -1
  56. package/lib/components/toolbar/ToolbarButton.js +54 -22
  57. package/lib/components/toolbar/ToolbarDivider.d.ts +1 -1
  58. package/lib/components/toolbar/ToolbarDropdown.d.ts +3 -3
  59. package/lib/components/toolbar/ToolbarDropdown.js +66 -35
  60. package/lib/components/toolbar/ToolbarRenderer.d.ts +20 -3
  61. package/lib/components/toolbar/ToolbarRenderer.js +16 -16
  62. package/lib/components/toolbar/types.d.ts +8 -8
  63. package/lib/index.js +4 -0
  64. package/lib/reactor/AppearancePlugin.d.ts +31 -0
  65. package/lib/reactor/AppearancePlugin.js +63 -0
  66. package/lib/reactor/ThemePlugin.d.ts +5 -0
  67. package/lib/reactor/ThemePlugin.js +98 -0
  68. package/lib/reactor/index.d.ts +30 -0
  69. package/lib/reactor/index.js +34 -0
  70. package/lib/reactor/page-layout/PageLayout.d.ts +148 -0
  71. package/lib/reactor/page-layout/PageLayout.js +326 -0
  72. package/lib/reactor/page-layout/PageLayoutPlugin.d.ts +59 -0
  73. package/lib/reactor/page-layout/PageLayoutPlugin.js +105 -0
  74. package/lib/reactor/page-layout/PagePanelToggle.d.ts +21 -0
  75. package/lib/reactor/page-layout/PagePanelToggle.js +29 -0
  76. package/lib/reactor/page-layout/SlotPageLayout.d.ts +36 -0
  77. package/lib/reactor/page-layout/SlotPageLayout.js +25 -0
  78. package/lib/reactor/page-layout/panelState.d.ts +28 -0
  79. package/lib/reactor/page-layout/panelState.js +49 -0
  80. package/lib/story-helpers.d.ts +2 -2
  81. package/lib/theme/DatalayerBrandThemeProvider.d.ts +1 -1
  82. package/lib/theme/DatalayerThemeProvider.d.ts +5 -5
  83. package/lib/theme/DatalayerThemeProvider.js +119 -58
  84. package/lib/theme/css/createThemeCSSVars.d.ts +1 -0
  85. package/lib/theme/css/createThemeCSSVars.js +9 -0
  86. package/lib/theme/fontStacks.d.ts +2 -0
  87. package/lib/theme/fontStacks.js +6 -0
  88. package/lib/theme/index.d.ts +2 -0
  89. package/lib/theme/index.js +2 -0
  90. package/lib/theme/palettes/ColorPalette.d.ts +3 -3
  91. package/lib/theme/palettes/ColorPalette.js +1 -2
  92. package/lib/theme/palettes/index.js +1 -2
  93. package/lib/theme/portableTheme.d.ts +32 -0
  94. package/lib/theme/portableTheme.js +45 -0
  95. package/lib/utils/Helper.d.ts +2 -2
  96. package/lib/utils/Portals.js +3 -1
  97. package/lib/utils/Styles.d.ts +1 -1
  98. package/package.json +21 -14
  99. package/lib/components/logo/DI.d.ts +0 -19
  100. 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 './types';
2
- export declare function Toolbar({ items, extraItems, className, disabled, ariaLabel, overflow, }: ToolbarProps): import("react/jsx-runtime").JSX.Element;
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/jsx-runtime").JSX.Element;
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
- const button = (_jsx("button", { type: "button", "aria-label": ariaLabel, "aria-disabled": disabled || undefined, onMouseDown: (e) => e.preventDefault(), onClick: disabled ? undefined : onClick, style: {
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
- width: btnSize,
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: isActive ? 'var(--bgColor-accent-muted, rgba(9,105,218,0.1))' : 'transparent',
56
- color: isActive ? 'var(--fgColor-accent, #0969da)' : 'var(--fgColor-muted, #656d76)',
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
- }, onMouseEnter: (e) => {
60
- if (!disabled) {
61
- e.currentTarget.style.background = 'var(--bgColor-neutral-muted, rgba(175,184,193,0.2))';
62
- e.currentTarget.style.color = 'var(--fgColor-default, #1f2328)';
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/jsx-runtime").JSX.Element;
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 './types';
1
+ import type { ToolbarDropdownItem } from "./types";
2
2
  export interface ToolbarDropdownProps {
3
3
  item: ToolbarDropdownItem;
4
4
  /** Size variant */
5
- size?: 'small' | 'medium';
5
+ size?: "small" | "medium";
6
6
  }
7
- export declare function ToolbarDropdown({ item, size }: ToolbarDropdownProps): import("react/jsx-runtime").JSX.Element;
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 'react';
20
- import { ActionMenu, ActionList, Text } from '@primer/react';
21
- import { Box } from '../box/Box';
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 = 'medium' }) {
43
+ export function ToolbarDropdown({ item, size = "medium", }) {
44
44
  const { ariaLabel, icon, label, minWidth, options, disabled } = item;
45
- // Highlight the trigger when any option is active.
46
- const anyActive = useMemo(() => options.some(o => o.isActive), [options]);
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 === 'small' ? 28 : 32;
50
- return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsxs("button", { type: "button", "aria-label": ariaLabel, title: ariaLabel, disabled: disabled, style: {
51
- display: 'inline-flex',
52
- alignItems: 'center',
53
- justifyContent: 'center',
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 ? '0 8px' : 0,
71
+ padding: label ? "0 8px" : 0,
58
72
  margin: 0,
59
- border: 'none',
73
+ border: "none",
60
74
  borderRadius: 6,
61
- cursor: disabled ? 'not-allowed' : 'pointer',
62
- background: anyActive
63
- ? 'var(--bgColor-accent-muted, rgba(9,105,218,0.1))'
64
- : 'transparent',
65
- color: anyActive
66
- ? 'var(--fgColor-accent, #0969da)'
67
- : 'var(--fgColor-muted, #656d76)',
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 === 'small' ? 12 : 14,
70
- fontWeight: 'normal',
71
- fontFamily: 'inherit',
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
- 'var(--bgColor-neutral-muted, rgba(175,184,193,0.2))';
77
- e.currentTarget.style.color = 'var(--fgColor-default, #1f2328)';
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 = anyActive
81
- ? 'var(--bgColor-accent-muted, rgba(9,105,218,0.1))'
82
- : 'transparent';
83
- e.currentTarget.style.color = anyActive
84
- ? 'var(--fgColor-accent, #0969da)'
85
- : 'var(--fgColor-muted, #656d76)';
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: 'inline-block',
112
+ display: "inline-block",
88
113
  minWidth: minWidth ? minWidth : undefined,
89
- textAlign: 'left',
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: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%', gap: 3 }, children: [_jsx(Text, { children: option.label }), option.shortcut && (_jsx(Text, { sx: { color: 'fg.subtle', fontSize: 0, fontFamily: 'mono' }, children: option.shortcut }))] })] }, option.key))) }) })] }));
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 './types';
1
+ import type { ToolbarItem } from "./types";
2
2
  export interface ToolbarRendererProps {
3
3
  items: ToolbarItem[];
4
4
  disabled?: boolean;
5
- size?: 'small' | 'medium';
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/jsx-runtime").JSX.Element;
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 'react';
14
- import { Box } from '../box/Box';
15
- import { ToolbarButton } from './ToolbarButton';
16
- import { ToolbarDropdown } from './ToolbarDropdown';
17
- import { ToolbarDivider } from './ToolbarDivider';
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 = 'medium' }) {
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 'button':
29
- return (_jsx(Fragment, { children: _jsx(ToolbarButton, { item: { ...item, disabled: itemDisabled }, size: size }) }, item.key));
30
- case 'dropdown':
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 'divider':
33
- return (_jsx(Fragment, { children: _jsx(ToolbarDivider, {}) }, item.key));
34
- case 'spacer':
35
- return (_jsx(Box, { sx: { flex: '1 1 auto', minWidth: 8 } }, item.key));
36
- case 'custom':
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 'react';
2
- import type { IconProps } from '@primer/octicons-react';
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 = 'button' | 'dropdown' | 'divider' | 'spacer' | 'custom';
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: 'button';
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: 'dropdown';
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: 'divider';
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: 'spacer';
98
+ type: "spacer";
99
99
  }
100
100
  /**
101
101
  * Toolbar custom item definition.
102
102
  */
103
103
  export interface ToolbarCustomItem extends ToolbarItemBase {
104
- type: 'custom';
104
+ type: "custom";
105
105
  /** Custom render function */
106
106
  render: () => ReactNode;
107
107
  }
package/lib/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ /*
2
+ * Copyright (c) 2021-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
1
5
  export * from "./components";
2
6
  export * from "./utils";
3
7
  export * from './theme';
@@ -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;