@datalayer/primer-addons 1.0.18 → 1.0.19

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 (68) hide show
  1. package/lib/components/appearance/AppearanceControls.d.ts +3 -2
  2. package/lib/components/appearance/AppearanceControls.js +71 -3
  3. package/lib/components/appearance/AppearanceMenu.d.ts +30 -0
  4. package/lib/components/appearance/AppearanceMenu.js +15 -0
  5. package/lib/components/appearance/ColorModeCircle.d.ts +28 -0
  6. package/lib/components/appearance/ColorModeCircle.js +132 -0
  7. package/lib/components/appearance/ThemeCircle.d.ts +21 -0
  8. package/lib/components/appearance/ThemeCircle.js +46 -0
  9. package/lib/components/appearance/ThemePreviewCard.d.ts +12 -0
  10. package/lib/components/appearance/ThemePreviewCard.js +83 -0
  11. package/lib/components/appearance/index.d.ts +4 -0
  12. package/lib/components/appearance/index.js +4 -0
  13. package/lib/components/box/Box.d.ts +2 -1
  14. package/lib/components/closeable-flash/CloseableFlash.js +58 -1
  15. package/lib/components/color/ColorSwatch.d.ts +23 -0
  16. package/lib/components/color/ColorSwatch.js +62 -0
  17. package/lib/components/color/index.d.ts +1 -0
  18. package/lib/components/color/index.js +5 -0
  19. package/lib/components/index.d.ts +6 -0
  20. package/lib/components/index.js +6 -0
  21. package/lib/components/logo/DatalayerLogo.js +49 -1
  22. package/lib/components/side-overlay/SideOverlay.js +1 -1
  23. package/lib/components/sliding-panel/SlidingPanel.d.ts +19 -0
  24. package/lib/components/sliding-panel/SlidingPanel.js +217 -0
  25. package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +16 -0
  26. package/lib/components/sliding-panel/SlidingPanel.stories.js +68 -0
  27. package/lib/theme/colors/datalayerColors.d.ts +12 -0
  28. package/lib/theme/colors/datalayerColors.js +13 -0
  29. package/lib/theme/colors/earthColors.d.ts +12 -0
  30. package/lib/theme/colors/earthColors.js +13 -0
  31. package/lib/theme/colors/index.d.ts +3 -0
  32. package/lib/theme/colors/index.js +9 -0
  33. package/lib/theme/colors/ivoryColors.d.ts +46 -0
  34. package/lib/theme/colors/ivoryColors.js +56 -0
  35. package/lib/theme/colors/lovelyColors.d.ts +12 -0
  36. package/lib/theme/colors/lovelyColors.js +13 -0
  37. package/lib/theme/colors/matrixColors.d.ts +12 -0
  38. package/lib/theme/colors/matrixColors.js +13 -0
  39. package/lib/theme/colors/sandColors.d.ts +45 -0
  40. package/lib/theme/colors/sandColors.js +55 -0
  41. package/lib/theme/colors/spatialColors.d.ts +12 -0
  42. package/lib/theme/colors/spatialColors.js +13 -0
  43. package/lib/theme/colors/sunColors.d.ts +45 -0
  44. package/lib/theme/colors/sunColors.js +55 -0
  45. package/lib/theme/css/createThemeCSSVars.d.ts +25 -1
  46. package/lib/theme/css/createThemeCSSVars.js +56 -3
  47. package/lib/theme/index.d.ts +3 -0
  48. package/lib/theme/index.js +3 -0
  49. package/lib/theme/palettes/ColorPalette.js +121 -1
  50. package/lib/theme/themeRegistry.d.ts +1 -1
  51. package/lib/theme/themeRegistry.js +151 -1
  52. package/lib/theme/themes/datalayerTheme.js +58 -7
  53. package/lib/theme/themes/earthTheme.js +50 -0
  54. package/lib/theme/themes/ivoryTheme.d.ts +1091 -0
  55. package/lib/theme/themes/ivoryTheme.js +257 -0
  56. package/lib/theme/themes/lovelyTheme.js +58 -7
  57. package/lib/theme/themes/matrixTheme.js +57 -8
  58. package/lib/theme/themes/sandTheme.d.ts +1091 -0
  59. package/lib/theme/themes/sandTheme.js +256 -0
  60. package/lib/theme/themes/spatialTheme.js +58 -7
  61. package/lib/theme/themes/sunTheme.d.ts +1091 -0
  62. package/lib/theme/themes/sunTheme.js +257 -0
  63. package/lib/utils/Helper.d.ts +1 -1
  64. package/lib/utils/Portals.d.ts +0 -1
  65. package/lib/utils/Portals.js +0 -1
  66. package/package.json +9 -5
  67. package/lib/App.d.ts +0 -13
  68. package/lib/App.js +0 -25
@@ -1,10 +1,11 @@
1
- import type { ReactElement } from 'react';
1
+ import { type ReactElement } from 'react';
2
2
  import { type ColorMode, type ThemeVariant } from '../../theme';
3
3
  export interface AppearanceControlsProps {
4
4
  colorMode: ColorMode;
5
5
  themeVariant: ThemeVariant;
6
6
  onColorModeChange: (mode: ColorMode) => void;
7
7
  onThemeChange: (theme: ThemeVariant) => void;
8
+ showThemePreviews?: boolean;
8
9
  }
9
- export declare function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, }: AppearanceControlsProps): ReactElement;
10
+ export declare function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, showThemePreviews, }: AppearanceControlsProps): ReactElement;
10
11
  export default AppearanceControls;
@@ -1,13 +1,81 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useState } from 'react';
2
7
  import { SegmentedControl, Tooltip } from '@primer/react';
3
8
  import { MoonIcon, SunIcon, DeviceDesktopIcon, } from '@primer/octicons-react';
4
9
  import { Box } from '../box/Box';
5
10
  import { themeVariants, themeConfigs, } from '../../theme';
6
- export function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, }) {
11
+ import { ThemePreviewCard } from './ThemePreviewCard';
12
+ export function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, showThemePreviews = false, }) {
13
+ const [isPreviewOpen, setIsPreviewOpen] = useState(false);
14
+ const selectedTheme = themeConfigs[themeVariant];
7
15
  return (_jsxs(_Fragment, { children: [_jsx(Box, { sx: { px: 2, py: 2, display: 'flex', justifyContent: 'center' }, children: _jsxs(SegmentedControl, { "aria-label": "Color mode", size: "small", onChange: (index) => {
8
16
  const modes = ['light', 'dark', 'auto'];
9
17
  onColorModeChange(modes[index]);
10
- }, children: [_jsx(SegmentedControl.IconButton, { selected: colorMode === 'light', icon: SunIcon, "aria-label": "Light" }), _jsx(SegmentedControl.IconButton, { selected: colorMode === 'dark', icon: MoonIcon, "aria-label": "Dark" }), _jsx(SegmentedControl.IconButton, { selected: colorMode === 'auto', icon: DeviceDesktopIcon, "aria-label": "Auto" })] }) }), _jsx(Box, { sx: {
18
+ }, children: [_jsx(SegmentedControl.IconButton, { selected: colorMode === 'light', icon: SunIcon, "aria-label": "Light" }), _jsx(SegmentedControl.IconButton, { selected: colorMode === 'dark', icon: MoonIcon, "aria-label": "Dark" }), _jsx(SegmentedControl.IconButton, { selected: colorMode === 'auto', icon: DeviceDesktopIcon, "aria-label": "Auto" })] }) }), showThemePreviews ? (_jsxs(Box, { sx: {
19
+ px: 2,
20
+ pb: 2,
21
+ display: 'grid',
22
+ gap: 2,
23
+ }, children: [_jsx(Box, { sx: {
24
+ display: 'flex',
25
+ justifyContent: 'center',
26
+ gap: 2,
27
+ flexWrap: 'wrap',
28
+ }, children: themeVariants.map((variant) => {
29
+ const cfg = themeConfigs[variant];
30
+ const isSelected = themeVariant === variant;
31
+ return (_jsx(Tooltip, { text: `${cfg.label} — ${cfg.description}`, direction: "s", children: _jsx(Box, { as: "button", "aria-label": cfg.label, "aria-pressed": isSelected, onClick: () => onThemeChange(variant), sx: {
32
+ width: 24,
33
+ height: 24,
34
+ borderRadius: '50%',
35
+ backgroundColor: cfg.brandColor,
36
+ border: '2px solid',
37
+ borderColor: isSelected ? 'accent.fg' : 'border.default',
38
+ cursor: 'pointer',
39
+ padding: 0,
40
+ outline: 'none',
41
+ transition: 'border-color 0.15s ease',
42
+ boxShadow: isSelected
43
+ ? '0 0 0 2px var(--bgColor-accent-muted, rgba(9,105,218,0.3))'
44
+ : 'none',
45
+ '&:hover': {
46
+ borderColor: 'accent.fg',
47
+ },
48
+ '&:focus-visible': {
49
+ boxShadow: '0 0 0 2px var(--bgColor-accent-muted, rgba(9,105,218,0.3))',
50
+ },
51
+ } }) }, variant));
52
+ }) }), _jsxs(Box, { sx: {
53
+ px: 1,
54
+ display: 'grid',
55
+ gap: 1,
56
+ }, children: [_jsx(Box, { sx: { fontSize: 1, fontWeight: 'semibold', color: 'fg.default' }, children: selectedTheme.label }), _jsx(Box, { sx: { fontSize: 0, color: 'fg.muted', lineHeight: 1.5 }, children: selectedTheme.description }), _jsx(Box, { as: "button", type: "button", onClick: () => setIsPreviewOpen((open) => !open), "aria-expanded": isPreviewOpen, sx: {
57
+ mt: 1,
58
+ width: 'fit-content',
59
+ px: 2,
60
+ py: '4px',
61
+ borderRadius: 2,
62
+ border: '1px solid',
63
+ borderColor: 'border.default',
64
+ bg: 'canvas.default',
65
+ color: 'fg.default',
66
+ fontSize: 0,
67
+ fontWeight: 'semibold',
68
+ cursor: 'pointer',
69
+ '&:hover': {
70
+ borderColor: 'accent.fg',
71
+ color: 'accent.fg',
72
+ },
73
+ '&:focus-visible': {
74
+ outline: '2px solid',
75
+ outlineColor: 'accent.fg',
76
+ outlineOffset: 1,
77
+ },
78
+ }, children: isPreviewOpen ? 'Hide preview' : 'Show preview' })] }), isPreviewOpen ? (_jsx(ThemePreviewCard, { variant: themeVariant, colorMode: colorMode, selected: true, compact: true, showDescription: true })) : null] })) : (_jsx(Box, { sx: {
11
79
  px: 2,
12
80
  pb: 2,
13
81
  display: 'flex',
@@ -37,6 +105,6 @@ export function AppearanceControls({ colorMode, themeVariant, onColorModeChange,
37
105
  boxShadow: '0 0 0 2px var(--bgColor-accent-muted, rgba(9,105,218,0.3))',
38
106
  },
39
107
  } }) }, variant));
40
- }) })] }));
108
+ }) }))] }));
41
109
  }
42
110
  export default AppearanceControls;
@@ -0,0 +1,30 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { ColorMode, ThemeVariant } from '../../theme';
3
+ export interface AppearanceMenuProps {
4
+ /** Current color mode. */
5
+ colorMode: ColorMode;
6
+ /** Current theme variant. */
7
+ themeVariant: ThemeVariant;
8
+ /** Called with the next color mode. */
9
+ onColorModeChange: (mode: ColorMode) => void;
10
+ /** Called with the next theme variant. */
11
+ onThemeChange: (theme: ThemeVariant) => void;
12
+ /** Diameter of the trigger circle in pixels. */
13
+ size?: number;
14
+ /** Visual shape of the trigger control. */
15
+ shape?: 'square' | 'circle';
16
+ /** Overlay placement relative to the circle. */
17
+ hoverOverlayPlacement?: 'bottom-start' | 'bottom-end';
18
+ /** Whether the overlay shows the live theme previews. */
19
+ showThemePreviews?: boolean;
20
+ }
21
+ /**
22
+ * A single color-mode circle that reveals, on hover, an appearance overlay
23
+ * with color-mode controls, theme circles and live theme previews.
24
+ *
25
+ * This bundles the {@link ColorModeCircle} trigger together with the
26
+ * {@link AppearanceControls} overlay so the whole appearance menu can be
27
+ * dropped into any header with a single component.
28
+ */
29
+ export declare function AppearanceMenu({ colorMode, themeVariant, onColorModeChange, onThemeChange, size, shape, hoverOverlayPlacement, showThemePreviews, }: AppearanceMenuProps): ReactElement;
30
+ export default AppearanceMenu;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ColorModeCircle } from './ColorModeCircle';
3
+ import { AppearanceControls } from './AppearanceControls';
4
+ /**
5
+ * A single color-mode circle that reveals, on hover, an appearance overlay
6
+ * with color-mode controls, theme circles and live theme previews.
7
+ *
8
+ * This bundles the {@link ColorModeCircle} trigger together with the
9
+ * {@link AppearanceControls} overlay so the whole appearance menu can be
10
+ * dropped into any header with a single component.
11
+ */
12
+ export function AppearanceMenu({ colorMode, themeVariant, onColorModeChange, onThemeChange, size = 26, shape = 'square', hoverOverlayPlacement = 'bottom-end', showThemePreviews = true, }) {
13
+ return (_jsx(ColorModeCircle, { colorMode: colorMode, onColorModeChange: onColorModeChange, cycleOnClick: false, themeVariant: themeVariant, hoverOverlayPlacement: hoverOverlayPlacement, shape: shape, size: size, hoverOverlay: _jsx(AppearanceControls, { colorMode: colorMode, themeVariant: themeVariant, onColorModeChange: onColorModeChange, onThemeChange: onThemeChange, showThemePreviews: showThemePreviews }) }));
14
+ }
15
+ export default AppearanceMenu;
@@ -0,0 +1,28 @@
1
+ import { type ReactElement, type ReactNode } from 'react';
2
+ import { type ColorMode, type ThemeVariant } from '../../theme';
3
+ type HoverOverlayPlacement = 'bottom-start' | 'bottom-end';
4
+ type ColorModeCircleShape = 'square' | 'circle';
5
+ export interface ColorModeCircleProps {
6
+ /** Current color mode. */
7
+ colorMode: ColorMode;
8
+ /** Called with the next color mode when the circle is clicked. */
9
+ onColorModeChange: (mode: ColorMode) => void;
10
+ /** Diameter of the circle in pixels. */
11
+ size?: number;
12
+ /** Optional overlay shown when hovering the circle. */
13
+ hoverOverlay?: ReactNode;
14
+ /** Keep legacy click-to-cycle behavior when an overlay is present. */
15
+ cycleOnClick?: boolean;
16
+ /** Overlay placement relative to the circle. */
17
+ hoverOverlayPlacement?: HoverOverlayPlacement;
18
+ /** Optional theme used to tint the circle border. */
19
+ themeVariant?: ThemeVariant;
20
+ /** Visual shape of the control. */
21
+ shape?: ColorModeCircleShape;
22
+ }
23
+ /**
24
+ * A single clickable circle that cycles the color mode through
25
+ * light → dark → system (auto). The icon reflects the current mode.
26
+ */
27
+ export declare function ColorModeCircle({ colorMode, onColorModeChange, size, hoverOverlay, cycleOnClick, hoverOverlayPlacement, themeVariant, shape, }: ColorModeCircleProps): ReactElement;
28
+ export default ColorModeCircle;
@@ -0,0 +1,132 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useEffect, useRef, useState } from 'react';
7
+ import { IconButton, Tooltip } from '@primer/react';
8
+ import { MoonIcon, SunIcon, DeviceDesktopIcon } from '@primer/octicons-react';
9
+ import { Box } from '../box/Box';
10
+ import { themeConfigs } from '../../theme';
11
+ /** Order in which a click cycles through the color modes. */
12
+ const COLOR_MODE_CYCLE = {
13
+ light: 'dark',
14
+ dark: 'auto',
15
+ auto: 'light',
16
+ };
17
+ /** Display metadata (label + icon) for each color mode. */
18
+ const COLOR_MODE_META = {
19
+ light: { label: 'Light', Icon: SunIcon },
20
+ dark: { label: 'Dark', Icon: MoonIcon },
21
+ auto: { label: 'System', Icon: DeviceDesktopIcon },
22
+ };
23
+ /** Small optical offsets so glyphs look visually centered in the circle. */
24
+ const COLOR_MODE_ICON_Y_OFFSET = {
25
+ light: -0.75,
26
+ dark: 0,
27
+ auto: -0.5,
28
+ };
29
+ /**
30
+ * A single clickable circle that cycles the color mode through
31
+ * light → dark → system (auto). The icon reflects the current mode.
32
+ */
33
+ export function ColorModeCircle({ colorMode, onColorModeChange, size = 28, hoverOverlay, cycleOnClick = true, hoverOverlayPlacement = 'bottom-end', themeVariant, shape = 'square', }) {
34
+ const meta = COLOR_MODE_META[colorMode] ?? COLOR_MODE_META.light;
35
+ const next = COLOR_MODE_CYCLE[colorMode] ?? 'light';
36
+ const nextMeta = COLOR_MODE_META[next];
37
+ const { Icon } = meta;
38
+ const iconYOffset = COLOR_MODE_ICON_Y_OFFSET[colorMode] ?? 0;
39
+ const [isHoverOpen, setIsHoverOpen] = useState(false);
40
+ const closeTimerRef = useRef(null);
41
+ const isBottomStart = hoverOverlayPlacement === 'bottom-start';
42
+ const themeConfig = themeVariant ? themeConfigs[themeVariant] : undefined;
43
+ const themeBorderColor = themeConfig?.brandColor;
44
+ const hasThemeColor = Boolean(themeBorderColor);
45
+ const resolvedMode = colorMode === 'auto'
46
+ ? (typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches
47
+ ? 'dark'
48
+ : 'light')
49
+ : colorMode;
50
+ const modeThemeStyles = themeConfig?.themeStyles?.[resolvedMode];
51
+ const themePrimaryButtonBg = modeThemeStyles?.['--button-primary-bgColor-rest'];
52
+ const themePrimaryButtonFg = modeThemeStyles?.['--button-primary-fgColor-rest'];
53
+ const themedIconColor = themePrimaryButtonFg || themeConfig?.brightPalette?.onGlow || 'fg.default';
54
+ const cancelScheduledClose = () => {
55
+ if (closeTimerRef.current) {
56
+ clearTimeout(closeTimerRef.current);
57
+ closeTimerRef.current = null;
58
+ }
59
+ };
60
+ const openHoverOverlay = () => {
61
+ cancelScheduledClose();
62
+ setIsHoverOpen(true);
63
+ };
64
+ const scheduleCloseHoverOverlay = () => {
65
+ cancelScheduledClose();
66
+ closeTimerRef.current = setTimeout(() => {
67
+ setIsHoverOpen(false);
68
+ closeTimerRef.current = null;
69
+ }, 220);
70
+ };
71
+ useEffect(() => {
72
+ return () => cancelScheduledClose();
73
+ }, []);
74
+ const handleClick = () => {
75
+ if (!hoverOverlay || cycleOnClick) {
76
+ onColorModeChange(next);
77
+ }
78
+ };
79
+ const circle = (_jsx(IconButton, { "aria-label": `Color mode: ${meta.label}. Switch to ${nextMeta.label}.`, onClick: handleClick, icon: () => (_jsx(Box, { as: "span", sx: {
80
+ display: 'inline-flex',
81
+ alignItems: 'center',
82
+ justifyContent: 'center',
83
+ transform: `translateY(${iconYOffset}px)`,
84
+ lineHeight: 0,
85
+ }, children: _jsx(Icon, { size: Math.round(size * 0.5) }) })), size: "small", variant: "invisible", sx: {
86
+ minWidth: size,
87
+ width: size,
88
+ height: size,
89
+ borderRadius: shape === 'circle' ? '50%' : 2,
90
+ display: 'inline-flex',
91
+ alignItems: 'center',
92
+ justifyContent: 'center',
93
+ bg: hasThemeColor ? (themePrimaryButtonBg || themeBorderColor) : 'canvas.default',
94
+ color: hasThemeColor ? themedIconColor : 'fg.default',
95
+ border: '1px solid',
96
+ borderColor: themeBorderColor || 'border.default',
97
+ cursor: 'pointer',
98
+ padding: 0,
99
+ transition: 'border-color 0.15s ease, color 0.15s ease',
100
+ '&:hover': {
101
+ borderColor: hasThemeColor ? themeBorderColor : 'accent.fg',
102
+ color: hasThemeColor ? themedIconColor : 'accent.fg',
103
+ opacity: hasThemeColor ? 0.92 : 1,
104
+ },
105
+ '&:focus-visible': {
106
+ outline: '2px solid',
107
+ outlineColor: 'accent.fg',
108
+ outlineOffset: 1,
109
+ },
110
+ } }));
111
+ if (!hoverOverlay) {
112
+ return (_jsx(Tooltip, { text: `Color mode: ${meta.label} — click for ${nextMeta.label}`, direction: "s", children: circle }));
113
+ }
114
+ return (_jsxs(Box, { sx: { position: 'relative', display: 'inline-flex', alignItems: 'center' }, onMouseEnter: openHoverOverlay, onMouseLeave: scheduleCloseHoverOverlay, children: [circle, _jsx(Box, { sx: {
115
+ position: 'absolute',
116
+ top: `calc(100% + 8px)`,
117
+ left: isBottomStart ? 0 : 'auto',
118
+ right: isBottomStart ? 'auto' : 0,
119
+ zIndex: 220,
120
+ minWidth: 340,
121
+ bg: 'canvas.overlay',
122
+ border: '1px solid',
123
+ borderColor: 'border.default',
124
+ borderRadius: 2,
125
+ boxShadow: 'shadow.large',
126
+ opacity: isHoverOpen ? 1 : 0,
127
+ transform: isHoverOpen ? 'translateY(0)' : 'translateY(-6px)',
128
+ pointerEvents: isHoverOpen ? 'auto' : 'none',
129
+ transition: 'opacity 0.16s ease, transform 0.16s ease',
130
+ }, children: hoverOverlay })] }));
131
+ }
132
+ export default ColorModeCircle;
@@ -0,0 +1,21 @@
1
+ import type { ReactElement } from 'react';
2
+ import { type ThemeVariant } from '../../theme';
3
+ export interface ThemeCircleProps {
4
+ /** Current theme variant. */
5
+ themeVariant: ThemeVariant;
6
+ /** Called with the next theme variant when the circle is clicked. */
7
+ onThemeChange: (theme: ThemeVariant) => void;
8
+ /**
9
+ * Ordered list of themes to cycle through. Defaults to every registered
10
+ * theme variant.
11
+ */
12
+ themes?: ThemeVariant[];
13
+ /** Diameter of the circle in pixels. */
14
+ size?: number;
15
+ }
16
+ /**
17
+ * A single clickable circle, filled with the current theme's brand color,
18
+ * that cycles to the next theme in `themes` on each click.
19
+ */
20
+ export declare function ThemeCircle({ themeVariant, onThemeChange, themes, size, }: ThemeCircleProps): ReactElement;
21
+ export default ThemeCircle;
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Tooltip } from '@primer/react';
3
+ import { Box } from '../box/Box';
4
+ import { themeConfigs, themeVariants } from '../../theme';
5
+ /**
6
+ * A single clickable circle, filled with the current theme's brand color,
7
+ * that cycles to the next theme in `themes` on each click.
8
+ */
9
+ export function ThemeCircle({ themeVariant, onThemeChange, themes, size = 28, }) {
10
+ const list = themes && themes.length > 0 ? themes : themeVariants;
11
+ const currentIndex = Math.max(0, list.indexOf(themeVariant));
12
+ const cfg = themeConfigs[themeVariant] ?? themeConfigs[list[0]];
13
+ const next = list[(currentIndex + 1) % list.length];
14
+ const nextCfg = themeConfigs[next];
15
+ const innerDotSize = Math.max(8, Math.round(size * 0.58));
16
+ return (_jsx(Tooltip, { text: `Theme: ${cfg.label} — click for ${nextCfg.label}`, direction: "s", children: _jsx(Box, { as: "button", type: "button", "aria-label": `Theme: ${cfg.label}. Switch to ${nextCfg.label}.`, onClick: () => onThemeChange(next), sx: {
17
+ width: size,
18
+ height: size,
19
+ borderRadius: '50%',
20
+ boxSizing: 'border-box',
21
+ display: 'inline-flex',
22
+ alignItems: 'center',
23
+ justifyContent: 'center',
24
+ backgroundColor: 'canvas.default',
25
+ appearance: 'none',
26
+ WebkitAppearance: 'none',
27
+ border: '2px solid',
28
+ borderColor: 'border.default',
29
+ cursor: 'pointer',
30
+ padding: 0,
31
+ transition: 'border-color 0.15s ease, transform 0.15s ease',
32
+ '&:hover': { borderColor: 'accent.fg', transform: 'scale(1.05)' },
33
+ '&:active': { transform: 'scale(1.02)' },
34
+ '&:focus-visible': {
35
+ outline: '2px solid',
36
+ outlineColor: 'accent.fg',
37
+ outlineOffset: 1,
38
+ },
39
+ }, children: _jsx(Box, { "aria-hidden": true, sx: {
40
+ width: innerDotSize,
41
+ height: innerDotSize,
42
+ borderRadius: '50%',
43
+ backgroundColor: cfg.brandColor,
44
+ } }) }) }));
45
+ }
46
+ export default ThemeCircle;
@@ -0,0 +1,12 @@
1
+ import type { ReactElement } from 'react';
2
+ import { type ColorMode, type ThemeVariant } from '../../theme';
3
+ export interface ThemePreviewCardProps {
4
+ variant: ThemeVariant;
5
+ colorMode: ColorMode;
6
+ selected?: boolean;
7
+ onSelect?: (variant: ThemeVariant) => void;
8
+ compact?: boolean;
9
+ showDescription?: boolean;
10
+ }
11
+ export declare function ThemePreviewCard({ variant, colorMode, selected, onSelect, compact, showDescription, }: ThemePreviewCardProps): ReactElement;
12
+ export default ThemePreviewCard;
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CheckCircleFillIcon } from '@primer/octicons-react';
3
+ import { Box } from '../box/Box';
4
+ import { matrixColors } from '../../theme/colors';
5
+ import { DatalayerThemeProvider, themeConfigs, useSystemColorMode } from '../../theme';
6
+ export function ThemePreviewCard({ variant, colorMode, selected = false, onSelect, compact = false, showDescription = true, }) {
7
+ const cfg = themeConfigs[variant];
8
+ const systemMode = useSystemColorMode();
9
+ const resolvedMode = colorMode === 'auto' ? systemMode : colorMode;
10
+ return (_jsxs(Box, { as: onSelect ? 'button' : 'div', onClick: onSelect ? () => onSelect(variant) : undefined, sx: {
11
+ position: 'relative',
12
+ width: '100%',
13
+ textAlign: 'left',
14
+ cursor: onSelect ? 'pointer' : 'default',
15
+ bg: 'canvas.default',
16
+ border: '2px solid',
17
+ borderColor: selected ? 'accent.fg' : 'border.default',
18
+ borderRadius: 2,
19
+ p: compact ? 2 : 4,
20
+ transition: 'border-color 0.15s ease, box-shadow 0.15s ease, background-color 0.15s ease',
21
+ boxShadow: selected
22
+ ? '0 0 0 1px var(--fgColor-accent, var(--color-accent-fg))'
23
+ : 'none',
24
+ ':hover': onSelect
25
+ ? {
26
+ borderColor: selected ? 'accent.fg' : 'border.muted',
27
+ bg: 'canvas.default',
28
+ }
29
+ : undefined,
30
+ ':focus-visible': onSelect
31
+ ? {
32
+ outline: '2px solid',
33
+ outlineColor: 'accent.fg',
34
+ outlineOffset: 2,
35
+ }
36
+ : undefined,
37
+ }, children: [selected ? (_jsx(Box, { sx: {
38
+ position: 'absolute',
39
+ top: 2,
40
+ right: 2,
41
+ color: 'accent.fg',
42
+ }, children: _jsx(CheckCircleFillIcon, { size: compact ? 16 : 20 }) })) : null, _jsx(DatalayerThemeProvider, { colorMode: resolvedMode, theme: cfg.primerTheme, themeStyles: cfg.themeStyles, children: _jsxs(Box, { sx: {
43
+ borderRadius: 2,
44
+ border: '1px solid',
45
+ borderColor: 'border.default',
46
+ overflow: 'hidden',
47
+ mb: compact ? 2 : 3,
48
+ }, children: [_jsxs(Box, { sx: {
49
+ display: 'flex',
50
+ alignItems: 'center',
51
+ gap: 1,
52
+ px: compact ? 1.5 : 2,
53
+ py: compact ? '4px' : '6px',
54
+ bg: 'canvas.default',
55
+ borderBottom: '1px solid',
56
+ borderColor: 'border.muted',
57
+ }, children: [_jsx(Box, { sx: { width: 8, height: 8, borderRadius: '50%', bg: 'danger.emphasis' } }), _jsx(Box, { sx: { width: 8, height: 8, borderRadius: '50%', bg: 'attention.emphasis' } }), _jsx(Box, { sx: { width: 8, height: 8, borderRadius: '50%', bg: 'success.emphasis' } })] }), _jsxs(Box, { sx: { p: compact ? 2 : 3, bg: 'canvas.default' }, children: [_jsx(Box, { sx: { display: 'block', fontWeight: 'semibold', fontSize: compact ? 0 : 1, color: 'fg.default', mb: 1 }, children: "The quick brown fox" }), _jsx(Box, { sx: { display: 'block', fontSize: 0, color: 'fg.muted', mb: 2 }, children: "jumps over the lazy dog." }), _jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 1 }, children: ([
58
+ { label: 'accent', bg: 'accent.emphasis' },
59
+ { label: 'success', bg: 'success.emphasis' },
60
+ { label: 'attention', bg: 'attention.emphasis' },
61
+ { label: 'danger', bg: 'danger.emphasis' },
62
+ { label: 'done', bg: 'done.emphasis' },
63
+ { label: 'severe', bg: 'severe.emphasis' },
64
+ ]).map(({ label, bg }) => (_jsx(Box, { sx: {
65
+ px: 2,
66
+ py: '2px',
67
+ borderRadius: '12px',
68
+ bg,
69
+ color: variant === 'matrix' &&
70
+ ((resolvedMode === 'dark' && label === 'accent') ||
71
+ (resolvedMode === 'light' && label === 'attention'))
72
+ ? matrixColors.black
73
+ : 'fg.onEmphasis',
74
+ fontSize: 0,
75
+ lineHeight: '18px',
76
+ }, children: label }, label))) })] })] }) }), _jsx(Box, { sx: {
77
+ display: 'block',
78
+ fontWeight: 'semibold',
79
+ fontSize: compact ? 1 : 2,
80
+ mb: showDescription ? 1 : 0,
81
+ }, children: cfg.label }), showDescription ? (_jsx(Box, { sx: { color: 'fg.muted', fontSize: compact ? 0 : 1 }, children: cfg.description })) : null] }));
82
+ }
83
+ export default ThemePreviewCard;
@@ -1,2 +1,6 @@
1
1
  export * from './AppearanceControls';
2
2
  export * from './AppearanceControlsWithStore';
3
+ export * from './AppearanceMenu';
4
+ export * from './ColorModeCircle';
5
+ export * from './ThemePreviewCard';
6
+ export * from './ThemeCircle';
@@ -4,3 +4,7 @@
4
4
  */
5
5
  export * from './AppearanceControls';
6
6
  export * from './AppearanceControlsWithStore';
7
+ export * from './AppearanceMenu';
8
+ export * from './ColorModeCircle';
9
+ export * from './ThemePreviewCard';
10
+ export * from './ThemeCircle';
@@ -1,6 +1,7 @@
1
1
  import { ComponentProps } from 'react';
2
2
  import type { BackgroundProps, BorderProps, ColorProps, FlexboxProps, GridProps, LayoutProps, PositionProps, ShadowProps, SpaceProps, TypographyProps } from 'styled-system';
3
3
  import { type SxProp } from './sx';
4
- export declare const Box: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "sx" | keyof SpaceProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> | keyof ColorProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> | keyof TypographyProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof LayoutProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof FlexboxProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof GridProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof BackgroundProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Background<import("styled-system").TLengthStyledSystem>> | keyof BorderProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Border<import("styled-system").TLengthStyledSystem>> | keyof PositionProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof ShadowProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>>> & SxProp & SpaceProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> & ColorProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> & TypographyProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & LayoutProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & FlexboxProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & GridProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & BackgroundProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Background<import("styled-system").TLengthStyledSystem>> & BorderProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Border<import("styled-system").TLengthStyledSystem>> & PositionProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & ShadowProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>>, never> & Partial<Pick<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "sx" | keyof SpaceProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> | keyof ColorProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> | keyof TypographyProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof LayoutProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof FlexboxProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof GridProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof BackgroundProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Background<import("styled-system").TLengthStyledSystem>> | keyof BorderProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Border<import("styled-system").TLengthStyledSystem>> | keyof PositionProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> | keyof ShadowProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>>> & SxProp & SpaceProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> & ColorProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, string | number | symbol> & TypographyProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & LayoutProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & FlexboxProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & GridProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & BackgroundProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Background<import("styled-system").TLengthStyledSystem>> & BorderProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>, import("csstype").Property.Border<import("styled-system").TLengthStyledSystem>> & PositionProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>> & ShadowProps<Required<import("styled-system").Theme<import("styled-system").TLengthStyledSystem>>>, never>>> & string;
4
+ type StyledBoxProps = SxProp & SpaceProps & ColorProps & TypographyProps & LayoutProps & FlexboxProps & GridProps & BackgroundProps & BorderProps & PositionProps & ShadowProps;
5
+ export declare const Box: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, StyledBoxProps>> & string;
5
6
  export type BoxProps = ComponentProps<typeof Box>;
6
7
  export default Box;
@@ -2,8 +2,35 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Box, Flash, CircleOcticon, Text } from "@primer/react";
4
4
  import { XIcon } from "@primer/octicons-react";
5
+ const closeButtonToneForVariant = (variant) => {
6
+ switch (variant) {
7
+ case 'success':
8
+ return { fg: 'success.fg', hoverBg: 'rgba(26, 127, 55, 0.14)' };
9
+ case 'warning':
10
+ return { fg: 'attention.fg', hoverBg: 'rgba(154, 103, 0, 0.16)' };
11
+ case 'danger':
12
+ return { fg: 'danger.fg', hoverBg: 'rgba(207, 34, 46, 0.14)' };
13
+ default:
14
+ return { fg: 'fg.default', hoverBg: 'rgba(31, 35, 40, 0.12)' };
15
+ }
16
+ };
17
+ const textColorForVariant = (variant) => {
18
+ switch (variant) {
19
+ case 'success':
20
+ return 'success.fg';
21
+ case 'warning':
22
+ return 'attention.fg';
23
+ case 'danger':
24
+ return 'danger.fg';
25
+ default:
26
+ return 'fg.default';
27
+ }
28
+ };
5
29
  export const CloseableFlash = (props) => {
6
30
  const { leadingVisual, variant, onClose, ...otherProps } = props;
31
+ const resolvedVariant = variant ?? 'default';
32
+ const closeTone = closeButtonToneForVariant(resolvedVariant);
33
+ const flashVariant = resolvedVariant === 'default' ? undefined : resolvedVariant;
7
34
  const [isVisible, setIsVisible] = useState(true);
8
35
  const handleClose = () => {
9
36
  if (onClose) {
@@ -14,5 +41,35 @@ export const CloseableFlash = (props) => {
14
41
  if (!isVisible) {
15
42
  return null;
16
43
  }
17
- return (_jsx(Flash, { variant: variant, ...otherProps, children: _jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [_jsxs(Box, { sx: { flexGrow: 1 }, children: [leadingVisual && _jsx(CircleOcticon, { icon: leadingVisual }), _jsx(Text, { color: `${variant === "default" ? "accent" : (variant === "warning" ? "attention" : variant)}.fg`, children: props.children })] }), _jsx(Box, { onClick: handleClose, sx: { cursor: "pointer" }, children: _jsx(XIcon, { size: 20 }) })] }) }));
44
+ return (_jsx(Flash, { variant: flashVariant, ...otherProps, children: _jsxs(Box, { display: "flex", justifyContent: "space-between", alignItems: "center", children: [_jsxs(Box, { sx: { flexGrow: 1 }, children: [leadingVisual && _jsx(CircleOcticon, { icon: leadingVisual }), _jsx(Text, { color: textColorForVariant(resolvedVariant), children: props.children })] }), _jsx(Box, { as: "button", type: "button", "aria-label": "Close", onClick: handleClose, sx: {
45
+ flex: '0 0 auto',
46
+ p: 0,
47
+ m: 0,
48
+ width: 28,
49
+ height: 28,
50
+ minWidth: 28,
51
+ minHeight: 28,
52
+ boxSizing: 'border-box',
53
+ display: 'grid',
54
+ placeItems: 'center',
55
+ lineHeight: 0,
56
+ border: 0,
57
+ background: 'transparent',
58
+ appearance: 'none',
59
+ cursor: 'pointer',
60
+ color: closeTone.fg,
61
+ borderRadius: 2,
62
+ '&:hover': {
63
+ backgroundColor: closeTone.hoverBg,
64
+ },
65
+ '& > span': {
66
+ width: 16,
67
+ height: 16,
68
+ display: 'grid',
69
+ placeItems: 'center',
70
+ },
71
+ '& svg': {
72
+ display: 'block',
73
+ },
74
+ }, children: _jsx(Box, { as: "span", "aria-hidden": true, children: _jsx(XIcon, { size: 16 }) }) })] }) }));
18
75
  };
@@ -0,0 +1,23 @@
1
+ export interface ColorSwatchProps {
2
+ /**
3
+ * A CSS color value or `var()` expression to render, e.g.
4
+ * `"var(--fgColor-default)"` or `"#ff0000"`. The swatch resolves the
5
+ * computed color at runtime, so `var()` tokens reflect the active theme
6
+ * and color mode.
7
+ */
8
+ color: string;
9
+ /** Optional label shown under the swatch. Defaults to the `color` string. */
10
+ label?: string;
11
+ /** Show the resolved hex value below the label. Defaults to `true`. */
12
+ showValue?: boolean;
13
+ /** Height of the color block in pixels. Defaults to `54`. */
14
+ height?: number;
15
+ }
16
+ /**
17
+ * Theme-aware color swatch. Renders a block filled with `color`, then reads
18
+ * the resolved value via `getComputedStyle` and displays it as a hex string.
19
+ * The resolved value recomputes whenever the active theme or color mode
20
+ * changes, so swatches backed by CSS custom properties stay in sync.
21
+ */
22
+ export declare function ColorSwatch({ color, label, showValue, height, }: ColorSwatchProps): import("react/jsx-runtime").JSX.Element;
23
+ export default ColorSwatch;