@datalayer/primer-addons 1.0.11 → 1.0.12

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 (31) hide show
  1. package/lib/components/appearance/AppearanceControls.d.ts +10 -0
  2. package/lib/components/appearance/AppearanceControls.js +42 -0
  3. package/lib/components/appearance/AppearanceControlsWithStore.d.ts +8 -0
  4. package/lib/{theme/ThemeSwitcher.js → components/appearance/AppearanceControlsWithStore.js} +5 -3
  5. package/lib/components/appearance/index.d.ts +2 -0
  6. package/lib/components/appearance/index.js +6 -0
  7. package/lib/components/index.d.ts +4 -0
  8. package/lib/components/index.js +4 -0
  9. package/lib/components/logo/DatalayerLogo.d.ts +18 -0
  10. package/lib/components/logo/DatalayerLogo.js +63 -0
  11. package/lib/components/logo/DatalayerLogoText.d.ts +22 -0
  12. package/lib/components/logo/DatalayerLogoText.js +29 -0
  13. package/lib/components/logo/DatalayerText.d.ts +16 -0
  14. package/lib/components/logo/DatalayerText.js +16 -0
  15. package/lib/components/logo/index.d.ts +3 -0
  16. package/lib/components/logo/index.js +3 -0
  17. package/lib/index.js +0 -1
  18. package/lib/theme/Palette.d.ts +8 -4
  19. package/lib/theme/Palette.js +13 -6
  20. package/lib/theme/colors/index.d.ts +1 -0
  21. package/lib/theme/colors/index.js +1 -0
  22. package/lib/theme/colors/indicatorColors.d.ts +50 -0
  23. package/lib/theme/colors/indicatorColors.js +68 -0
  24. package/lib/theme/index.d.ts +0 -1
  25. package/lib/theme/index.js +0 -1
  26. package/lib/utils/Portals.d.ts +4 -0
  27. package/lib/utils/Portals.js +61 -9
  28. package/package.json +1 -1
  29. package/lib/_utils/story-helpers.d.ts +0 -31
  30. package/lib/_utils/story-helpers.js +0 -105
  31. package/lib/theme/ThemeSwitcher.d.ts +0 -17
@@ -0,0 +1,10 @@
1
+ import type { ReactElement } from 'react';
2
+ import { type ColorMode, type ThemeVariant } from '../../theme';
3
+ export interface AppearanceControlsProps {
4
+ colorMode: ColorMode;
5
+ themeVariant: ThemeVariant;
6
+ onColorModeChange: (mode: ColorMode) => void;
7
+ onThemeChange: (theme: ThemeVariant) => void;
8
+ }
9
+ export declare function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, }: AppearanceControlsProps): ReactElement;
10
+ export default AppearanceControls;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { SegmentedControl } from '@primer/react';
3
+ import { MoonIcon, SunIcon, DeviceDesktopIcon, } from '@primer/octicons-react';
4
+ import { Box } from '../box/Box';
5
+ import { themeVariants, themeConfigs, } from '../../theme';
6
+ export function AppearanceControls({ colorMode, themeVariant, onColorModeChange, onThemeChange, }) {
7
+ 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
+ const modes = ['light', 'dark', 'auto'];
9
+ 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: {
11
+ px: 2,
12
+ pb: 2,
13
+ display: 'flex',
14
+ justifyContent: 'center',
15
+ gap: 2,
16
+ }, children: themeVariants.map((variant) => {
17
+ const cfg = themeConfigs[variant];
18
+ const isSelected = themeVariant === variant;
19
+ return (_jsx(Box, { as: "button", "aria-label": cfg.label, "aria-pressed": isSelected, onClick: () => onThemeChange(variant), sx: {
20
+ width: 24,
21
+ height: 24,
22
+ borderRadius: '50%',
23
+ backgroundColor: cfg.brandColor,
24
+ border: '2px solid',
25
+ borderColor: isSelected ? 'accent.fg' : 'border.default',
26
+ cursor: 'pointer',
27
+ padding: 0,
28
+ outline: 'none',
29
+ transition: 'border-color 0.15s ease',
30
+ boxShadow: isSelected
31
+ ? '0 0 0 2px var(--bgColor-accent-muted, rgba(9,105,218,0.3))'
32
+ : 'none',
33
+ '&:hover': {
34
+ borderColor: 'accent.fg',
35
+ },
36
+ '&:focus-visible': {
37
+ boxShadow: '0 0 0 2px var(--bgColor-accent-muted, rgba(9,105,218,0.3))',
38
+ },
39
+ } }, variant));
40
+ }) })] }));
41
+ }
42
+ export default AppearanceControls;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { UseBoundStore, StoreApi } from 'zustand';
3
+ import type { ThemeState } from '../../theme/useThemeStore';
4
+ export interface AppearanceControlsWithStoreProps {
5
+ useStore: UseBoundStore<StoreApi<ThemeState>>;
6
+ }
7
+ export declare const AppearanceControlsWithStore: React.FC<AppearanceControlsWithStoreProps>;
8
+ export default AppearanceControlsWithStore;
@@ -1,8 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, IconButton, SegmentedControl, Tooltip } from '@primer/react';
2
+ import { IconButton, SegmentedControl, Tooltip } from '@primer/react';
3
3
  import { SunIcon, MoonIcon, DeviceDesktopIcon, CircleIcon, } from '@primer/octicons-react';
4
- import { themeConfigs, themeVariants } from './themeRegistry';
5
- export const ThemeSwitcher = ({ useStore }) => {
4
+ import { Box } from '../box/Box';
5
+ import { themeConfigs, themeVariants } from '../../theme/themeRegistry';
6
+ export const AppearanceControlsWithStore = ({ useStore }) => {
6
7
  const { colorMode, theme: themeVariant } = useStore();
7
8
  return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 3 }, children: [_jsx(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: themeVariants.map(variant => {
8
9
  const tcfg = themeConfigs[variant];
@@ -17,3 +18,4 @@ export const ThemeSwitcher = ({ useStore }) => {
17
18
  useStore.getState().setColorMode(modes[index]);
18
19
  }, 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" })] })] }));
19
20
  };
21
+ export default AppearanceControlsWithStore;
@@ -0,0 +1,2 @@
1
+ export * from './AppearanceControls';
2
+ export * from './AppearanceControlsWithStore';
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from './AppearanceControls';
6
+ export * from './AppearanceControlsWithStore';
@@ -1,3 +1,5 @@
1
+ export * from "./appearance/AppearanceControls";
2
+ export * from "./appearance/AppearanceControlsWithStore";
1
3
  export * from "./box/Box";
2
4
  export * from "./card/Card";
3
5
  export * from "./content-loader/ContentLoader";
@@ -5,4 +7,6 @@ export * from "./closeable-flash/CloseableFlash";
5
7
  export * from "./icons/CircleIcon";
6
8
  export * from "./overlay/Overlay";
7
9
  export * from "./slider/Slider";
10
+ export * from "./logo";
11
+ export * from "./appearance";
8
12
  export * from "./toolbar";
@@ -1,3 +1,5 @@
1
+ export * from "./appearance/AppearanceControls";
2
+ export * from "./appearance/AppearanceControlsWithStore";
1
3
  export * from "./box/Box";
2
4
  export * from "./card/Card";
3
5
  export * from "./content-loader/ContentLoader";
@@ -5,4 +7,6 @@ export * from "./closeable-flash/CloseableFlash";
5
7
  export * from "./icons/CircleIcon";
6
8
  export * from "./overlay/Overlay";
7
9
  export * from "./slider/Slider";
10
+ export * from "./logo";
11
+ export * from "./appearance";
8
12
  export * from "./toolbar";
@@ -0,0 +1,18 @@
1
+ import { type SVGProps } from 'react';
2
+ import { type ThemeVariant } from '../../theme';
3
+ export interface LogoColorPair {
4
+ primary: string;
5
+ secondary: string;
6
+ }
7
+ export declare const THEME_LOGO_COLORS: Record<ThemeVariant, {
8
+ light: LogoColorPair;
9
+ dark: LogoColorPair;
10
+ }>;
11
+ export declare function getLogoColors(variant?: ThemeVariant, colorMode?: 'light' | 'dark' | 'auto'): LogoColorPair;
12
+ export interface DatalayerLogoProps extends Omit<SVGProps<SVGSVGElement>, 'ref'> {
13
+ size?: number;
14
+ primaryColor?: string;
15
+ secondaryColor?: string;
16
+ }
17
+ export declare const DatalayerLogo: import("react").ForwardRefExoticComponent<DatalayerLogoProps & import("react").RefAttributes<SVGSVGElement>>;
18
+ export default DatalayerLogo;
@@ -0,0 +1,63 @@
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 { forwardRef } from 'react';
7
+ import { datalayerColors, spatialColors, lovelyColors, matrixColors, } from '../../theme';
8
+ export const THEME_LOGO_COLORS = {
9
+ datalayer: {
10
+ light: {
11
+ primary: datalayerColors.greenBrand,
12
+ secondary: datalayerColors.greenText,
13
+ },
14
+ dark: {
15
+ primary: datalayerColors.greenBright,
16
+ secondary: datalayerColors.greenAccent,
17
+ },
18
+ },
19
+ spatial: {
20
+ light: {
21
+ primary: spatialColors.indigoBrand,
22
+ secondary: spatialColors.indigoText,
23
+ },
24
+ dark: {
25
+ primary: spatialColors.indigoBright,
26
+ secondary: spatialColors.indigoAccent,
27
+ },
28
+ },
29
+ lovely: {
30
+ light: {
31
+ primary: lovelyColors.roseBrand,
32
+ secondary: lovelyColors.roseText,
33
+ },
34
+ dark: {
35
+ primary: lovelyColors.roseBright,
36
+ secondary: lovelyColors.roseAccent,
37
+ },
38
+ },
39
+ matrix: {
40
+ light: {
41
+ primary: matrixColors.greenBrand,
42
+ secondary: matrixColors.greenText,
43
+ },
44
+ dark: {
45
+ primary: matrixColors.greenGlow,
46
+ secondary: matrixColors.greenPhosphor,
47
+ },
48
+ },
49
+ };
50
+ export function getLogoColors(variant = 'datalayer', colorMode = 'light') {
51
+ const mode = colorMode === 'auto' ? 'light' : colorMode;
52
+ return THEME_LOGO_COLORS[variant]?.[mode] ?? THEME_LOGO_COLORS.datalayer.light;
53
+ }
54
+ const BAR_HEIGHT = 25;
55
+ const BARS = [
56
+ { y: 0, secondaryWidth: 30, primaryWidth: 70 },
57
+ { y: 37.5, secondaryWidth: 50, primaryWidth: 50 },
58
+ { y: 75, secondaryWidth: 70, primaryWidth: 30 },
59
+ ];
60
+ export const DatalayerLogo = forwardRef(function DatalayerLogo({ size = 16, primaryColor = 'currentColor', secondaryColor = 'currentColor', ...svgProps }, ref) {
61
+ return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 100 100", width: size, height: size, "aria-hidden": "true", ref: ref, ...svgProps, children: BARS.map(({ y, secondaryWidth, primaryWidth }) => (_jsxs("g", { children: [_jsx("rect", { x: 0, y: y, width: secondaryWidth, height: BAR_HEIGHT, fill: secondaryColor }), _jsx("rect", { x: secondaryWidth, y: y, width: primaryWidth, height: BAR_HEIGHT, fill: primaryColor })] }, y))) }));
62
+ });
63
+ export default DatalayerLogo;
@@ -0,0 +1,22 @@
1
+ import { type SVGProps } from 'react';
2
+ import type { ThemeVariant } from '../../theme';
3
+ export interface DatalayerLogoTextProps extends Omit<SVGProps<SVGSVGElement>, 'ref'> {
4
+ size?: number;
5
+ scale?: number;
6
+ colorMode?: 'light' | 'dark' | 'auto';
7
+ variant?: ThemeVariant;
8
+ primaryColor?: string;
9
+ secondaryColor?: string;
10
+ textSizeMultiplier?: number;
11
+ /**
12
+ * When true, render logo mark first then text.
13
+ * When false (default), render text first then logo mark.
14
+ */
15
+ inverse?: boolean;
16
+ }
17
+ /**
18
+ * Datalayer icon + wordmark based on official brand proportions.
19
+ * Uses themed colors by default and allows explicit color overrides.
20
+ */
21
+ export declare const DatalayerLogoText: import("react").ForwardRefExoticComponent<DatalayerLogoTextProps & import("react").RefAttributes<SVGSVGElement>>;
22
+ export default DatalayerLogoText;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { forwardRef } from 'react';
7
+ import { getLogoColors } from './DatalayerLogo';
8
+ import { DatalayerText } from './DatalayerText';
9
+ /**
10
+ * Datalayer icon + wordmark based on official brand proportions.
11
+ * Uses themed colors by default and allows explicit color overrides.
12
+ */
13
+ export const DatalayerLogoText = forwardRef(function DatalayerLogoText({ size = 25, scale = 1, colorMode = 'light', variant = 'datalayer', primaryColor, secondaryColor,
14
+ // 2.380655 ~= 25 / (0.7 * 15.00187)
15
+ // This makes the wordmark glyph height match the 25-unit icon height.
16
+ textSizeMultiplier = 2.380655, inverse = false, ...svgProps }, ref) {
17
+ const themed = getLogoColors(variant, colorMode);
18
+ const primary = primaryColor ?? themed.primary;
19
+ const secondary = secondaryColor ?? themed.secondary;
20
+ const baseHeight = size;
21
+ const baseWidth = (149.672 / 25) * size;
22
+ const height = baseHeight * scale;
23
+ const width = baseWidth * scale;
24
+ const iconGroup = (_jsxs("g", { transform: "translate(0 0)", children: [_jsx("rect", { x: "0", y: "0", width: "10", height: "6.25", fill: secondary }), _jsx("rect", { x: "10", y: "0", width: "15", height: "6.25", fill: primary }), _jsx("rect", { x: "0", y: "9.375", width: "15", height: "6.25", fill: secondary }), _jsx("rect", { x: "15", y: "9.375", width: "10", height: "6.25", fill: primary }), _jsx("rect", { x: "0", y: "18.75", width: "20", height: "6.25", fill: secondary }), _jsx("rect", { x: "20", y: "18.75", width: "5", height: "6.25", fill: primary })] }));
25
+ const logoFirst = (_jsxs(_Fragment, { children: [iconGroup, _jsx(DatalayerText, { primaryColor: primary, secondaryColor: secondary, sizeMultiplier: textSizeMultiplier, x: 16 })] }));
26
+ const textFirst = (_jsxs(_Fragment, { children: [_jsx(DatalayerText, { primaryColor: primary, secondaryColor: secondary, sizeMultiplier: textSizeMultiplier, x: 0 }), _jsxs("g", { transform: "translate(124.672 0)", children: [_jsx("rect", { x: "0", y: "0", width: "10", height: "6.25", fill: secondary }), _jsx("rect", { x: "10", y: "0", width: "15", height: "6.25", fill: primary }), _jsx("rect", { x: "0", y: "9.375", width: "15", height: "6.25", fill: secondary }), _jsx("rect", { x: "15", y: "9.375", width: "10", height: "6.25", fill: primary }), _jsx("rect", { x: "0", y: "18.75", width: "20", height: "6.25", fill: secondary }), _jsx("rect", { x: "20", y: "18.75", width: "5", height: "6.25", fill: primary })] })] }));
27
+ return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 149.672 25", width: width, height: height, role: "img", "aria-label": "Datalayer", ref: ref, ...svgProps, children: inverse ? logoFirst : textFirst }));
28
+ });
29
+ export default DatalayerLogoText;
@@ -0,0 +1,16 @@
1
+ import type { SVGProps } from 'react';
2
+ export interface DatalayerTextProps extends Omit<SVGProps<SVGGElement>, 'ref'> {
3
+ primaryColor?: string;
4
+ secondaryColor?: string;
5
+ /** Multiplies the base wordmark size. 1.0 keeps current dimensions. */
6
+ sizeMultiplier?: number;
7
+ /** X translation in the parent SVG coordinate system. */
8
+ x?: number;
9
+ /** Y translation in the parent SVG coordinate system. */
10
+ y?: number;
11
+ }
12
+ /**
13
+ * Official Datalayer wordmark outlines (BebasNeue converted to paths).
14
+ */
15
+ export declare const DatalayerText: ({ primaryColor, secondaryColor, sizeMultiplier, x, y, ...groupProps }: DatalayerTextProps) => JSX.Element;
16
+ export default DatalayerText;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Official Datalayer wordmark outlines (BebasNeue converted to paths).
4
+ */
5
+ export const DatalayerText = ({ primaryColor = 'currentColor', secondaryColor = 'currentColor', sizeMultiplier = 1, x = 20, y = -190.15, ...groupProps }) => {
6
+ const baseScale = 0.7;
7
+ const scale = baseScale * sizeMultiplier;
8
+ // Keep the wordmark anchored when scaling so larger multipliers do not
9
+ // move the paths out of the parent SVG viewBox.
10
+ const anchorX = 17.35761;
11
+ const anchorY = 289.499065;
12
+ const translateX = x + anchorX * (baseScale - scale);
13
+ const translateY = y + anchorY * (baseScale - scale);
14
+ return (_jsxs("g", { transform: `matrix(${scale},0,0,${scale},${translateX},${translateY})`, ...groupProps, children: [_jsx("path", { d: "m 17.35761,297 h 3.729038 c 2.357437,0 3.514725,-1.30731 3.514725,-3.70761 v -7.58666 c 0,-2.4003 -1.157288,-3.7076 -3.514725,-3.7076 H 17.35761 Z m 3.686175,-12.85875 c 0.750094,0 1.20015,0.38576 1.20015,1.45733 v 7.80097 c 0,1.07156 -0.450056,1.45733 -1.20015,1.45733 h -1.328737 v -10.71563 z", fill: secondaryColor }), _jsx("path", { d: "M 31.29328,281.99813 H 27.842849 L 25.442549,297 h 2.164556 l 0.407194,-2.72177 h 2.893219 L 31.314711,297 h 2.378869 z m -1.864519,2.65747 h 0.04286 l 1.114425,7.58666 h -2.271713 z", fill: secondaryColor }), _jsx("path", { d: "m 33.2894,284.14125 h 2.464594 V 297 h 2.357438 v -12.85875 h 2.464593 v -2.14312 H 33.2894 Z", fill: secondaryColor }), _jsx("path", { d: "M 46.027265,281.99813 H 42.576834 L 40.176534,297 h 2.164556 l 0.407194,-2.72177 h 2.893219 L 46.048696,297 h 2.378869 z m -1.864519,2.65747 h 0.04286 l 1.114425,7.58666 h -2.271713 z", fill: secondaryColor }), _jsx("path", { d: "m 51.175594,297 h 6.236494 v -2.14312 h -3.879056 v -12.85875 h -2.357438 z", fill: primaryColor }), _jsx("path", { d: "M 63.688095,281.99813 H 60.237664 L 57.837364,297 h 2.164556 l 0.407194,-2.72177 h 2.893219 L 63.709526,297 h 2.378869 z m -1.864519,2.65747 h 0.04286 l 1.114425,7.58666 h -2.271713 z", fill: primaryColor }), _jsx("path", { d: "m 68.326286,297 h 2.357438 v -4.97205 l 2.978944,-10.02982 h -2.250282 l -1.778793,6.83656 h -0.04286 l -1.778794,-6.83656 h -2.464593 l 2.978943,10.02982 z", fill: primaryColor }), _jsx("path", { d: "m 76.973461,284.14125 h 4.071938 v -2.14312 H 74.616024 V 297 h 6.429375 v -2.14312 h -4.071938 v -4.39341 h 3.236119 v -2.14313 h -3.236119 z", fill: primaryColor }), _jsx("path", { d: "m 89.814797,297 c -0.235744,-0.55721 -0.257175,-1.09299 -0.257175,-1.82166 v -2.31457 c 0,-1.56448 -0.385763,-2.67891 -1.564481,-3.17183 v -0.0429 c 1.050131,-0.49292 1.543049,-1.47875 1.543049,-3.0218 v -1.17872 c 0,-2.31458 -1.050131,-3.45043 -3.493293,-3.45043 H 82.485309 V 297 h 2.357438 v -6.10791 h 0.814387 c 1.071563,0 1.54305,0.51435 1.54305,1.90739 v 2.35743 c 0,1.22158 0.08573,1.45733 0.214313,1.84309 z m -3.836194,-12.85875 c 0.835819,0 1.20015,0.47149 1.20015,1.54305 v 1.47876 c 0,1.20015 -0.535781,1.58591 -1.414462,1.58591 h -0.921544 v -4.60772 z", fill: primaryColor })] }));
15
+ };
16
+ export default DatalayerText;
@@ -0,0 +1,3 @@
1
+ export * from './DatalayerLogo';
2
+ export * from './DatalayerLogoText';
3
+ export * from './DatalayerText';
@@ -0,0 +1,3 @@
1
+ export * from './DatalayerLogo';
2
+ export * from './DatalayerLogoText';
3
+ export * from './DatalayerText';
package/lib/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  export * from "./components";
2
2
  export * from "./utils";
3
- // Export Theme.
4
3
  export * from './theme';
@@ -1,4 +1,8 @@
1
- export declare const RESERVATION_CIRCLE_COLOR = "#656D76";
2
- export declare const CREDITS_CIRCLE_COLOR = "#16A085";
3
- export declare const JOINED_INVITE_COLOR = "#0366d6";
4
- export declare const PENDING_INVITE_COLOR = "#cfd3d7";
1
+ /** @deprecated Use `defaultIndicatorColors.reservation` */
2
+ export declare const RESERVATION_CIRCLE_COLOR: string;
3
+ /** @deprecated Use `defaultIndicatorColors.credits` */
4
+ export declare const CREDITS_CIRCLE_COLOR: string;
5
+ /** @deprecated Use `defaultIndicatorColors.inviteJoined` */
6
+ export declare const JOINED_INVITE_COLOR: string;
7
+ /** @deprecated Use `defaultIndicatorColors.invitePending` */
8
+ export declare const PENDING_INVITE_COLOR: string;
@@ -2,9 +2,16 @@
2
2
  * Copyright (c) 2023-2025 Datalayer, Inc.
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
- // export const RESERVATION_CIRCLE_COLOR_VAR = '--data-yellow-color';
6
- export const RESERVATION_CIRCLE_COLOR = '#656D76';
7
- // export const CREDITS_CIRCLE_COLOR_VAR = '--data-blue-color';
8
- export const CREDITS_CIRCLE_COLOR = '#16A085';
9
- export const JOINED_INVITE_COLOR = '#0366d6';
10
- export const PENDING_INVITE_COLOR = '#cfd3d7';
5
+ /**
6
+ * @deprecated Use `indicatorColors` / `defaultIndicatorColors` from
7
+ * `@datalayer/primer-addons/lib/theme` instead.
8
+ */
9
+ import { defaultIndicatorColors } from './colors/indicatorColors';
10
+ /** @deprecated Use `defaultIndicatorColors.reservation` */
11
+ export const RESERVATION_CIRCLE_COLOR = defaultIndicatorColors.reservation;
12
+ /** @deprecated Use `defaultIndicatorColors.credits` */
13
+ export const CREDITS_CIRCLE_COLOR = defaultIndicatorColors.credits;
14
+ /** @deprecated Use `defaultIndicatorColors.inviteJoined` */
15
+ export const JOINED_INVITE_COLOR = defaultIndicatorColors.inviteJoined;
16
+ /** @deprecated Use `defaultIndicatorColors.invitePending` */
17
+ export const PENDING_INVITE_COLOR = defaultIndicatorColors.invitePending;
@@ -2,3 +2,4 @@ export { datalayerColors } from './datalayerColors';
2
2
  export { spatialColors } from './spatialColors';
3
3
  export { lovelyColors } from './lovelyColors';
4
4
  export { matrixColors } from './matrixColors';
5
+ export { indicatorColors, defaultIndicatorColors, type IndicatorColors, } from './indicatorColors';
@@ -6,3 +6,4 @@ export { datalayerColors } from './datalayerColors';
6
6
  export { spatialColors } from './spatialColors';
7
7
  export { lovelyColors } from './lovelyColors';
8
8
  export { matrixColors } from './matrixColors';
9
+ export { indicatorColors, defaultIndicatorColors, } from './indicatorColors';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Indicator Color Palette — Semantic status colours for indicators,
3
+ * badges, progress rings, and status dots.
4
+ *
5
+ * Each theme provides its own `IndicatorColors` so consumers pick up
6
+ * the correct tones automatically. The palette contains both
7
+ * **status colours** (success, warning, danger …) and
8
+ * **domain-specific** colours (reservation, credits, invite …).
9
+ *
10
+ * Usage:
11
+ * ```ts
12
+ * import { indicatorColors } from '@datalayer/primer-addons/lib/theme';
13
+ * const colors = indicatorColors.datalayer; // or .spatial, .lovely, .matrix
14
+ * <Box sx={{ bg: colors.success }} />
15
+ * ```
16
+ */
17
+ export interface IndicatorColors {
18
+ /** Neutral / default — nothing notable */
19
+ neutral: string;
20
+ /** Muted — defined but inactive */
21
+ muted: string;
22
+ /** In-progress / loading */
23
+ pending: string;
24
+ /** Warning / attention-needed */
25
+ warning: string;
26
+ /** Error / failure */
27
+ danger: string;
28
+ /** Healthy / success */
29
+ success: string;
30
+ /** Informational / accent */
31
+ info: string;
32
+ /** Reservation indicator (e.g. usage rings) */
33
+ reservation: string;
34
+ /** Credits indicator (e.g. usage rings) */
35
+ credits: string;
36
+ /** Accepted / joined invite */
37
+ inviteJoined: string;
38
+ /** Pending invite */
39
+ invitePending: string;
40
+ }
41
+ export declare const indicatorColors: {
42
+ readonly datalayer: IndicatorColors;
43
+ readonly spatial: IndicatorColors;
44
+ readonly lovely: IndicatorColors;
45
+ readonly matrix: IndicatorColors;
46
+ };
47
+ /**
48
+ * Convenience: default indicator palette (Datalayer theme).
49
+ */
50
+ export declare const defaultIndicatorColors: IndicatorColors;
@@ -0,0 +1,68 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /* ── Per-theme palettes ────────────────────────────────── */
6
+ const datalayer = {
7
+ neutral: '#656D76',
8
+ muted: '#8b949e',
9
+ pending: '#d29922',
10
+ warning: '#d29922',
11
+ danger: '#d1242f',
12
+ success: '#16A085',
13
+ info: '#0969da',
14
+ reservation: '#656D76',
15
+ credits: '#16A085',
16
+ inviteJoined: '#0969da',
17
+ invitePending: '#cfd3d7',
18
+ };
19
+ const spatial = {
20
+ neutral: '#8892B0',
21
+ muted: '#6B7394',
22
+ pending: '#E2B340',
23
+ warning: '#E2B340',
24
+ danger: '#FF5252',
25
+ success: '#4F46E5',
26
+ info: '#448AFF',
27
+ reservation: '#8892B0',
28
+ credits: '#4F46E5',
29
+ inviteJoined: '#448AFF',
30
+ invitePending: '#6B7394',
31
+ };
32
+ const lovely = {
33
+ neutral: '#9D7A8F',
34
+ muted: '#C9A2B8',
35
+ pending: '#FFD740',
36
+ warning: '#FFD740',
37
+ danger: '#FF1744',
38
+ success: '#DB2777',
39
+ info: '#536DFE',
40
+ reservation: '#9D7A8F',
41
+ credits: '#DB2777',
42
+ inviteJoined: '#536DFE',
43
+ invitePending: '#C9A2B8',
44
+ };
45
+ const matrix = {
46
+ neutral: '#5A6E3A',
47
+ muted: '#7A9A50',
48
+ pending: '#FFD600',
49
+ warning: '#FFD600',
50
+ danger: '#FF3D00',
51
+ success: '#00C853',
52
+ info: '#00E676',
53
+ reservation: '#5A6E3A',
54
+ credits: '#00C853',
55
+ inviteJoined: '#00E676',
56
+ invitePending: '#7A9A50',
57
+ };
58
+ /* ── Public map ────────────────────────────────────────── */
59
+ export const indicatorColors = {
60
+ datalayer,
61
+ spatial,
62
+ lovely,
63
+ matrix,
64
+ };
65
+ /**
66
+ * Convenience: default indicator palette (Datalayer theme).
67
+ */
68
+ export const defaultIndicatorColors = datalayer;
@@ -11,5 +11,4 @@ export * from './themeRegistry';
11
11
  export * from './Palette';
12
12
  export * from './useSystemColorMode';
13
13
  export * from './useThemeStore';
14
- export * from './ThemeSwitcher';
15
14
  export * from './ThemedProvider';
@@ -15,5 +15,4 @@ export * from './themeRegistry';
15
15
  export * from './Palette';
16
16
  export * from './useSystemColorMode';
17
17
  export * from './useThemeStore';
18
- export * from './ThemeSwitcher';
19
18
  export * from './ThemedProvider';
@@ -4,6 +4,10 @@ type Colormode = 'light' | 'dark' | 'auto';
4
4
  /**
5
5
  * Ensure we define a root for Primer portal root.
6
6
  *
7
+ * Creates a dedicated `<div>` appended to `<body>` with a high z-index
8
+ * so that portaled content (overlays, tooltips, action menus) renders
9
+ * above other positioned UI elements such as chat panels.
10
+ *
7
11
  * @see https://github.com/primer/react/blob/main/packages/react/src/Portal/Portal.tsx#L23
8
12
  * @see https://github.com/primer/react/blob/030fe020b48b7f12c2994c6614e5d4191fe764ee/src/Portal/Portal.tsx#L33
9
13
  */
@@ -19,21 +19,73 @@ const resolveColormode = (colormode) => {
19
19
  }
20
20
  return colormode;
21
21
  };
22
+ /**
23
+ * Infer current app color mode from existing DOM theme markers.
24
+ *
25
+ * This avoids accidentally forcing light mode when callers invoke
26
+ * setupPrimerPortals() without passing an explicit mode.
27
+ */
28
+ const inferCurrentColormode = () => {
29
+ const portalRoot = document.getElementById(PRIMER_PORTAL_ROOT_ID);
30
+ const portalMode = portalRoot?.getAttribute('data-color-mode');
31
+ if (portalMode === 'light' || portalMode === 'dark' || portalMode === 'auto') {
32
+ return portalMode;
33
+ }
34
+ const htmlMode = document.documentElement.getAttribute('data-color-mode');
35
+ if (htmlMode === 'light' || htmlMode === 'dark' || htmlMode === 'auto') {
36
+ return htmlMode;
37
+ }
38
+ const bodyMode = document.body.getAttribute('data-color-mode');
39
+ if (bodyMode === 'light' || bodyMode === 'dark' || bodyMode === 'auto') {
40
+ return bodyMode;
41
+ }
42
+ return 'auto';
43
+ };
44
+ /**
45
+ * Infer Primer light/dark theme names from existing DOM markers.
46
+ */
47
+ const inferCurrentThemeNames = () => {
48
+ const portalRoot = document.getElementById(PRIMER_PORTAL_ROOT_ID);
49
+ const lightTheme = portalRoot?.getAttribute('data-light-theme') ||
50
+ document.documentElement.getAttribute('data-light-theme') ||
51
+ document.body.getAttribute('data-light-theme') ||
52
+ 'light';
53
+ const darkTheme = portalRoot?.getAttribute('data-dark-theme') ||
54
+ document.documentElement.getAttribute('data-dark-theme') ||
55
+ document.body.getAttribute('data-dark-theme') ||
56
+ 'dark';
57
+ return { lightTheme, darkTheme };
58
+ };
22
59
  /**
23
60
  * Ensure we define a root for Primer portal root.
24
61
  *
62
+ * Creates a dedicated `<div>` appended to `<body>` with a high z-index
63
+ * so that portaled content (overlays, tooltips, action menus) renders
64
+ * above other positioned UI elements such as chat panels.
65
+ *
25
66
  * @see https://github.com/primer/react/blob/main/packages/react/src/Portal/Portal.tsx#L23
26
67
  * @see https://github.com/primer/react/blob/030fe020b48b7f12c2994c6614e5d4191fe764ee/src/Portal/Portal.tsx#L33
27
68
  */
28
- export const setupPrimerPortals = (colormode = 'light') => {
29
- const resolved = resolveColormode(colormode);
30
- const body = document.body;
31
- body.dataset['portalRoot'] = 'true';
32
- body.dataset['colorMode'] = resolved;
33
- body.dataset['lightTheme'] = 'light';
34
- body.dataset['darkTheme'] = 'dark';
35
- body.id = PRIMER_PORTAL_ROOT_ID;
36
- registerPortalRoot(body);
69
+ export const setupPrimerPortals = (colormode) => {
70
+ const effectiveColormode = colormode ?? inferCurrentColormode();
71
+ const resolved = resolveColormode(effectiveColormode);
72
+ const { lightTheme, darkTheme } = inferCurrentThemeNames();
73
+ // Create or reuse a dedicated portal root div.
74
+ let portalRoot = document.getElementById(PRIMER_PORTAL_ROOT_ID);
75
+ if (!portalRoot) {
76
+ portalRoot = document.createElement('div');
77
+ portalRoot.id = PRIMER_PORTAL_ROOT_ID;
78
+ document.body.appendChild(portalRoot);
79
+ }
80
+ // Primer theme attributes — required for proper theming inside portals.
81
+ portalRoot.dataset['portalRoot'] = 'true';
82
+ portalRoot.dataset['colorMode'] = resolved;
83
+ portalRoot.dataset['lightTheme'] = lightTheme;
84
+ portalRoot.dataset['darkTheme'] = darkTheme;
85
+ // High z-index so overlays render above positioned UI (e.g. chat z-index 1001).
86
+ portalRoot.style.position = 'relative';
87
+ portalRoot.style.zIndex = '9999';
88
+ registerPortalRoot(portalRoot);
37
89
  };
38
90
  /* ─── camelCase → kebab-case ─────────────────────────────────────────── */
39
91
  const camelToKebab = (s) => s.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/primer-addons",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "license": "BSD-3-Clause",
5
5
  "scripts": {
6
6
  "build": "tsc && vite build",
@@ -1,31 +0,0 @@
1
- import React from 'react';
2
- import { ArgTypes } from '@storybook/react';
3
- import { Icon } from '@primer/octicons-react';
4
- type StoryContext = Record<string, unknown> & {
5
- globals: {
6
- colorScheme: string;
7
- };
8
- parameters: Record<string, unknown>;
9
- };
10
- export declare const colormodeFromScheme: (colorScheme: string) => "dark" | "light";
11
- export declare const withThemeProvider: (Story: React.FC<React.PropsWithChildren<StoryContext>>, context: StoryContext) => string | number | boolean | Iterable<React.ReactNode> | import("react/jsx-runtime").JSX.Element | null | undefined;
12
- export declare const toolbarTypes: {
13
- colorScheme: {
14
- name: string;
15
- description: string;
16
- defaultValue: string;
17
- toolbar: {
18
- icon: string;
19
- items: string[];
20
- title: string;
21
- };
22
- };
23
- };
24
- export declare const inputWrapperArgTypes: ArgTypes;
25
- export declare const getTextInputArgTypes: (category?: string) => Record<string, unknown>;
26
- export declare const OcticonArgType: (iconList: Icon[]) => {
27
- options: string[];
28
- control: string;
29
- mapping: Record<string, Icon>;
30
- };
31
- export {};
@@ -1,105 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createGlobalStyle } from 'styled-components';
3
- import { ThemeProvider, themeGet, BaseStyles, theme } from '@primer/react';
4
- // set global theme styles for each story
5
- const GlobalStyle = createGlobalStyle `
6
- body,
7
- .docs-story {
8
- background-color: ${themeGet('colors.canvas.default')};
9
- color: ${themeGet('colors.fg.default')};
10
- }
11
- `;
12
- export const colormodeFromScheme = (colorScheme) => {
13
- return colorScheme.startsWith('light') ? 'light' : 'dark';
14
- };
15
- export const withThemeProvider = (Story, context) => {
16
- // used for testing ThemeProvider.stories.tsx
17
- if (context.parameters.disableThemeDecorator)
18
- return Story(context);
19
- const { colorScheme } = context.globals;
20
- return (_jsxs(ThemeProvider, { theme: theme, colorMode: colormodeFromScheme(colorScheme), dayScheme: colorScheme, nightScheme: colorScheme, children: [colorScheme.startsWith('light') ? (_jsx(GlobalStyle, { "$lightTheme": true })) : (_jsx(GlobalStyle, {})), _jsx(BaseStyles, { children: _jsx("div", { id: "html-addon-root", children: Story(context) }) })] }));
21
- };
22
- export const toolbarTypes = {
23
- colorScheme: {
24
- name: 'Color Scheme',
25
- description: 'Switch color scheme',
26
- defaultValue: 'light',
27
- toolbar: {
28
- icon: 'photo',
29
- items: [...Object.keys(theme.colorSchemes)],
30
- title: 'Color Scheme',
31
- },
32
- },
33
- };
34
- export const inputWrapperArgTypes = {
35
- block: {
36
- defaultValue: false,
37
- control: 'boolean',
38
- },
39
- contrast: {
40
- defaultValue: false,
41
- control: 'boolean',
42
- },
43
- disabled: {
44
- defaultValue: false,
45
- control: 'boolean',
46
- },
47
- placeholder: {
48
- defaultValue: '',
49
- control: 'text',
50
- },
51
- size: {
52
- name: 'size (input)', // TODO: remove '(input)'
53
- defaultValue: 'medium',
54
- options: ['small', 'medium', 'large'],
55
- control: 'radio',
56
- },
57
- validationStatus: {
58
- defaultValue: undefined,
59
- options: ['error', 'success', undefined],
60
- control: 'radio',
61
- },
62
- };
63
- const textInputArgTypesUnsorted = {
64
- ...inputWrapperArgTypes,
65
- loading: {
66
- defaultValue: false,
67
- control: 'boolean',
68
- },
69
- loaderPosition: {
70
- defaultValue: 'auto',
71
- options: ['auto', 'leading', 'trailing'],
72
- control: 'radio',
73
- },
74
- monospace: {
75
- defaultValue: false,
76
- control: 'boolean',
77
- },
78
- };
79
- // Alphabetize and optionally categorize the props
80
- export const getTextInputArgTypes = (category) => Object.keys(textInputArgTypesUnsorted)
81
- .sort()
82
- .reduce((obj, key) => {
83
- obj[key] = category
84
- ? {
85
- // have to do weird type casting so we can spread the object
86
- ...textInputArgTypesUnsorted[key],
87
- table: {
88
- category,
89
- },
90
- }
91
- : textInputArgTypesUnsorted[key];
92
- return obj;
93
- }, {});
94
- // Use this function for icon options in the controls. Desired icons are passed in as an array of Octicons
95
- export const OcticonArgType = (iconList) => {
96
- const icons = iconList.reduce((obj, icon) => {
97
- obj[icon.displayName || 'Icon'] = icon;
98
- return obj;
99
- }, {});
100
- return {
101
- options: Object.keys(icons),
102
- control: 'select',
103
- mapping: icons,
104
- };
105
- };
@@ -1,17 +0,0 @@
1
- /**
2
- * ThemeSwitcher – Themed colour circles + light/dark/auto segmented control.
3
- *
4
- * The component requires an external Zustand `ThemeState` store (created
5
- * with `createThemeStore`). Consumers pass the `useStore` hook as a prop
6
- * so the switcher is completely decoupled from any specific app's store.
7
- *
8
- * @module theme/ThemeSwitcher
9
- */
10
- import React from 'react';
11
- import type { ThemeState } from './useThemeStore';
12
- import type { UseBoundStore, StoreApi } from 'zustand';
13
- export interface ThemeSwitcherProps {
14
- /** A Zustand store hook created via `createThemeStore()`. */
15
- useStore: UseBoundStore<StoreApi<ThemeState>>;
16
- }
17
- export declare const ThemeSwitcher: React.FC<ThemeSwitcherProps>;