@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,7 +1,40 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * `CloseableFlash`: a Primer `Flash` the reader can dismiss.
8
+ *
9
+ * Primer's own variants are `default`, `success`, `warning` and `danger`. Two
10
+ * more are added here, for the levels a platform announce is written at:
11
+ * `info`, which is the neutral blue Primer already draws for `default` and is
12
+ * worth having under the name people say, and `critical`, which is `danger`
13
+ * wearing its emphasis colour — an incident has to look different from a
14
+ * validation error, and a reader who sees everything red stops reading red.
15
+ *
16
+ * @module components/closeable-flash/CloseableFlash
17
+ */
2
18
  import { useState } from "react";
3
19
  import { Box, Flash, CircleOcticon, Text } from "@primer/react";
4
20
  import { XIcon } from "@primer/octicons-react";
21
+ /**
22
+ * The Primer variant that draws a given level.
23
+ *
24
+ * `info` is Primer's `default` — already the accent blue — and `critical` is
25
+ * `danger`; what sets the two dangers apart is the emphasis below, not the
26
+ * variant, because Primer has no louder red to ask for.
27
+ */
28
+ const flashVariantForVariant = (variant) => {
29
+ switch (variant) {
30
+ case 'info':
31
+ return 'default';
32
+ case 'critical':
33
+ return 'danger';
34
+ default:
35
+ return variant;
36
+ }
37
+ };
5
38
  const closeButtonToneForVariant = (variant) => {
6
39
  switch (variant) {
7
40
  case 'success':
@@ -9,7 +42,10 @@ const closeButtonToneForVariant = (variant) => {
9
42
  case 'warning':
10
43
  return { fg: 'attention.fg', hoverBg: 'rgba(154, 103, 0, 0.16)' };
11
44
  case 'danger':
45
+ case 'critical':
12
46
  return { fg: 'danger.fg', hoverBg: 'rgba(207, 34, 46, 0.14)' };
47
+ case 'info':
48
+ return { fg: 'accent.fg', hoverBg: 'rgba(9, 105, 218, 0.12)' };
13
49
  default:
14
50
  return { fg: 'fg.default', hoverBg: 'rgba(31, 35, 40, 0.12)' };
15
51
  }
@@ -21,16 +57,21 @@ const textColorForVariant = (variant) => {
21
57
  case 'warning':
22
58
  return 'attention.fg';
23
59
  case 'danger':
60
+ case 'critical':
24
61
  return 'danger.fg';
62
+ case 'info':
63
+ return 'accent.fg';
25
64
  default:
26
65
  return 'fg.default';
27
66
  }
28
67
  };
29
68
  export const CloseableFlash = (props) => {
30
- const { leadingVisual, variant, onClose, ...otherProps } = props;
69
+ const { leadingVisual, variant, onClose, sx, dismissible = true, ...otherProps } = props;
31
70
  const resolvedVariant = variant ?? 'default';
32
71
  const closeTone = closeButtonToneForVariant(resolvedVariant);
33
- const flashVariant = resolvedVariant === 'default' ? undefined : resolvedVariant;
72
+ const primerVariant = flashVariantForVariant(resolvedVariant);
73
+ const flashVariant = primerVariant === 'default' ? undefined : primerVariant;
74
+ const isCritical = resolvedVariant === 'critical';
34
75
  const [isVisible, setIsVisible] = useState(true);
35
76
  const handleClose = () => {
36
77
  if (onClose) {
@@ -41,7 +82,14 @@ export const CloseableFlash = (props) => {
41
82
  if (!isVisible) {
42
83
  return null;
43
84
  }
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: {
85
+ return (_jsx(Flash, { variant: flashVariant, sx: {
86
+ // A critical flash is the one a reader must not scroll past: the
87
+ // border carries the emphasis colour rather than the muted one.
88
+ ...(isCritical
89
+ ? { borderColor: 'danger.emphasis', borderWidth: 2, borderStyle: 'solid' }
90
+ : {}),
91
+ ...sx,
92
+ }, ...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), sx: isCritical ? { fontWeight: 'bold' } : undefined, children: props.children })] }), dismissible && (_jsx(Box, { as: "button", type: "button", "aria-label": "Close", onClick: handleClose, sx: {
45
93
  flex: '0 0 auto',
46
94
  p: 0,
47
95
  m: 0,
@@ -71,5 +119,5 @@ export const CloseableFlash = (props) => {
71
119
  '& svg': {
72
120
  display: 'block',
73
121
  },
74
- }, children: _jsx(Box, { as: "span", "aria-hidden": true, children: _jsx(XIcon, { size: 16 }) }) })] }) }));
122
+ }, children: _jsx(Box, { as: "span", "aria-hidden": true, children: _jsx(XIcon, { size: 16 }) }) }))] }) }));
75
123
  };
@@ -19,5 +19,5 @@ export interface ColorSwatchProps {
19
19
  * The resolved value recomputes whenever the active theme or color mode
20
20
  * changes, so swatches backed by CSS custom properties stay in sync.
21
21
  */
22
- export declare function ColorSwatch({ color, label, showValue, height, }: ColorSwatchProps): import("react/jsx-runtime").JSX.Element;
22
+ export declare function ColorSwatch({ color, label, showValue, height, }: ColorSwatchProps): import("react").JSX.Element;
23
23
  export default ColorSwatch;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * A colour, picked.
3
+ *
4
+ * A saturation-and-brightness field, a hue rail under it, a hex field and a
5
+ * row of presets — the shape a colour picker has had since Photoshop, drawn
6
+ * with Primer's form controls and Primer's custom properties so it sits in a
7
+ * Primer page without a stylesheet of its own.
8
+ *
9
+ * The chrome follows the theme (`--borderColor-default`, `--bgColor-muted`).
10
+ * The colour surfaces do not: a hue rail is the hue rail in every theme, and
11
+ * tinting it would lie about the colour being picked.
12
+ *
13
+ * Pointer events throughout, so a drag works the same with a mouse, a finger
14
+ * or a stylus, and keeps following the pointer outside the element.
15
+ *
16
+ * @module components/color-picker/ColorPicker
17
+ */
18
+ import type React from "react";
19
+ import { type ThemeVariant } from "../../theme/themeRegistry";
20
+ export interface RGB {
21
+ r: number;
22
+ g: number;
23
+ b: number;
24
+ }
25
+ export interface HSV {
26
+ /** 0–360 */
27
+ h: number;
28
+ /** 0–100 */
29
+ s: number;
30
+ /** 0–100 */
31
+ v: number;
32
+ }
33
+ /** `#rgb`, `#rrggbb` or `rgb(r, g, b)` as channels; black when unreadable. */
34
+ export declare function toRgb(input: string): RGB;
35
+ export declare function toHex({ r, g, b }: RGB): string;
36
+ export declare function rgbToHsv({ r, g, b }: RGB): HSV;
37
+ export declare function hsvToRgb({ h, s, v }: HSV): RGB;
38
+ /** Whether text reads as a colour this picker can hold. */
39
+ export declare function isColor(input: string): boolean;
40
+ /** Black or white, whichever stays legible on the colour given. */
41
+ export declare function readableOn(color: RGB): string;
42
+ /** A spread of hues and neutrals to start from. */
43
+ export declare const DEFAULT_COLOR_PRESETS: string[];
44
+ /**
45
+ * A named row of swatches under the field.
46
+ *
47
+ * More than one can be offered at a time — the theme's own colours, the
48
+ * colours a room gives its collaborators, a product's brand set — each under
49
+ * its own heading, so a person picking a colour can reach for one that
50
+ * already belongs somewhere rather than inventing a hex value.
51
+ */
52
+ export interface ColorPresetGroup {
53
+ /** The heading over the row. */
54
+ label: string;
55
+ /** The colours, as anything CSS accepts. */
56
+ colors: readonly string[];
57
+ }
58
+ /**
59
+ * The active theme's own colours, as a group.
60
+ *
61
+ * Surfaces and text first, then the semantic four, then the vivid palette the
62
+ * theme keeps for illustration — the order someone reaches for them in.
63
+ */
64
+ export declare function themeColorPresets(variant?: ThemeVariant, colorMode?: "light" | "dark" | "auto"): ColorPresetGroup;
65
+ /** The colours a room gives its collaborators, as a group. */
66
+ export declare function collaboratorColorPresets(variant?: ThemeVariant, colorMode?: "light" | "dark" | "auto"): ColorPresetGroup;
67
+ export interface ColorPickerProps {
68
+ /** The colour held, as `#rrggbb` or `rgb(r, g, b)`. */
69
+ color: string;
70
+ /** Told the new colour, as `#rrggbb`, while it is dragged and when it is set. */
71
+ onChange?: (color: string) => void;
72
+ /** The swatches offered under the field. {@link DEFAULT_COLOR_PRESETS} by default. */
73
+ presets?: readonly string[];
74
+ /** Hide the preset row. */
75
+ showPresets?: boolean;
76
+ /**
77
+ * Further rows under the presets, each with its own heading. Give it
78
+ * {@link themeColorPresets} or {@link collaboratorColorPresets}, or rows of
79
+ * your own.
80
+ */
81
+ presetGroups?: readonly ColorPresetGroup[];
82
+ /**
83
+ * Offer the active theme's own colours as a row. On by default: a colour
84
+ * picked in a themed page usually wants to be one of the theme's.
85
+ */
86
+ showThemeColors?: boolean;
87
+ /** Offer the collaborator colours as a row. Off by default. */
88
+ showCollaboratorColors?: boolean;
89
+ /** Hide the hex field. */
90
+ showInput?: boolean;
91
+ /** How wide the whole picker is, in pixels. */
92
+ width?: number;
93
+ /** The picker's accessible name. */
94
+ "aria-label"?: string;
95
+ }
96
+ export declare function ColorPicker({ color, onChange, presets, showPresets, presetGroups, showThemeColors, showCollaboratorColors, showInput, width, "aria-label": ariaLabel, }: ColorPickerProps): React.JSX.Element;
97
+ export default ColorPicker;
@@ -0,0 +1,358 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
3
+ import { Box, Text, TextInput } from "@primer/react";
4
+ import { getBrightPalette, themeConfigs, } from "../../theme/themeRegistry";
5
+ import { useThemeStore } from "../../theme/useThemeStore";
6
+ import { useSystemColorMode } from "../../theme/useSystemColorMode";
7
+ import { collaboratorColors } from "../appearance/CollaboratorPalette";
8
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
9
+ /** `#rgb`, `#rrggbb` or `rgb(r, g, b)` as channels; black when unreadable. */
10
+ export function toRgb(input) {
11
+ const text = input.trim();
12
+ const fromFunction = /^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i.exec(text);
13
+ if (fromFunction) {
14
+ const [, r, g, b] = fromFunction;
15
+ return { r: Number(r), g: Number(g), b: Number(b) };
16
+ }
17
+ let hex = text.replace(/^#/, "");
18
+ if (hex.length === 3) {
19
+ hex = hex
20
+ .split("")
21
+ .map((channel) => channel + channel)
22
+ .join("");
23
+ }
24
+ if (!/^[0-9a-f]{6}$/i.test(hex)) {
25
+ return { r: 0, g: 0, b: 0 };
26
+ }
27
+ return {
28
+ r: parseInt(hex.slice(0, 2), 16),
29
+ g: parseInt(hex.slice(2, 4), 16),
30
+ b: parseInt(hex.slice(4, 6), 16),
31
+ };
32
+ }
33
+ export function toHex({ r, g, b }) {
34
+ const channel = (value) => clamp(Math.round(value), 0, 255).toString(16).padStart(2, "0");
35
+ return `#${channel(r)}${channel(g)}${channel(b)}`;
36
+ }
37
+ export function rgbToHsv({ r, g, b }) {
38
+ const red = r / 255;
39
+ const green = g / 255;
40
+ const blue = b / 255;
41
+ const max = Math.max(red, green, blue);
42
+ const min = Math.min(red, green, blue);
43
+ const span = max - min;
44
+ let h = 0;
45
+ if (span !== 0) {
46
+ if (max === red) {
47
+ h = 60 * (((green - blue) / span) % 6);
48
+ }
49
+ else if (max === green) {
50
+ h = 60 * ((blue - red) / span + 2);
51
+ }
52
+ else {
53
+ h = 60 * ((red - green) / span + 4);
54
+ }
55
+ }
56
+ if (h < 0) {
57
+ h += 360;
58
+ }
59
+ return { h, s: max === 0 ? 0 : (span / max) * 100, v: max * 100 };
60
+ }
61
+ export function hsvToRgb({ h, s, v }) {
62
+ const saturation = s / 100;
63
+ const value = v / 100;
64
+ const chroma = value * saturation;
65
+ const second = chroma * (1 - Math.abs(((h / 60) % 2) - 1));
66
+ const base = value - chroma;
67
+ const sextant = Math.floor((((h % 360) + 360) % 360) / 60);
68
+ const [r, g, b] = [
69
+ [chroma, second, 0],
70
+ [second, chroma, 0],
71
+ [0, chroma, second],
72
+ [0, second, chroma],
73
+ [second, 0, chroma],
74
+ [chroma, 0, second],
75
+ ][sextant];
76
+ return {
77
+ r: Math.round((r + base) * 255),
78
+ g: Math.round((g + base) * 255),
79
+ b: Math.round((b + base) * 255),
80
+ };
81
+ }
82
+ /** Whether text reads as a colour this picker can hold. */
83
+ export function isColor(input) {
84
+ const text = input.trim();
85
+ return (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(text) ||
86
+ /^rgba?\(\s*\d+[,\s]+\d+[,\s]+\d+/i.test(text));
87
+ }
88
+ /** Black or white, whichever stays legible on the colour given. */
89
+ export function readableOn(color) {
90
+ // Rec. 601 luma, the usual quick test for "is this a light colour".
91
+ const luma = (color.r * 299 + color.g * 587 + color.b * 114) / 1000;
92
+ return luma > 150 ? "#000000" : "#ffffff";
93
+ }
94
+ /* ── The picker ────────────────────────────────────────────────────── */
95
+ /** A spread of hues and neutrals to start from. */
96
+ export const DEFAULT_COLOR_PRESETS = [
97
+ "#cf222e",
98
+ "#bc4c00",
99
+ "#9a6700",
100
+ "#1a7f37",
101
+ "#0969da",
102
+ "#8250df",
103
+ "#bf3989",
104
+ "#24292f",
105
+ "#57606a",
106
+ "#8c959f",
107
+ "#d0d7de",
108
+ "#ffffff",
109
+ ];
110
+ /**
111
+ * The active theme's own colours, as a group.
112
+ *
113
+ * Surfaces and text first, then the semantic four, then the vivid palette the
114
+ * theme keeps for illustration — the order someone reaches for them in.
115
+ */
116
+ export function themeColorPresets(variant = "datalayer", colorMode = "light") {
117
+ const bright = getBrightPalette(variant, colorMode);
118
+ const config = themeConfigs[variant] ?? themeConfigs.datalayer;
119
+ const mode = colorMode === "auto"
120
+ ? typeof window !== "undefined" &&
121
+ window.matchMedia("(prefers-color-scheme: dark)").matches
122
+ ? "dark"
123
+ : "light"
124
+ : colorMode;
125
+ const styles = (mode === "dark" ? config.themeStyles.dark : config.themeStyles.light);
126
+ const fromStyles = (name) => styles[name];
127
+ return {
128
+ label: config.label,
129
+ colors: [
130
+ fromStyles("--bgColor-default"),
131
+ fromStyles("--bgColor-muted"),
132
+ fromStyles("--fgColor-default"),
133
+ fromStyles("--fgColor-muted"),
134
+ fromStyles("--fgColor-accent"),
135
+ fromStyles("--fgColor-success"),
136
+ fromStyles("--fgColor-attention"),
137
+ fromStyles("--fgColor-danger"),
138
+ bright.glow,
139
+ bright.pop,
140
+ bright.spark,
141
+ bright.blaze,
142
+ bright.surge,
143
+ bright.flame,
144
+ bright.gold,
145
+ ].filter(Boolean),
146
+ };
147
+ }
148
+ /** The colours a room gives its collaborators, as a group. */
149
+ export function collaboratorColorPresets(variant = "datalayer", colorMode = "dark") {
150
+ return {
151
+ label: "Collaborators",
152
+ colors: collaboratorColors(variant, colorMode),
153
+ };
154
+ }
155
+ const FIELD_HEIGHT = 150;
156
+ const RAIL_HEIGHT = 12;
157
+ /**
158
+ * Follows a pointer across an element, in fractions of its own box.
159
+ *
160
+ * The listeners go on the window once a drag starts, so the pointer can leave
161
+ * the element — down the page, out of the window — without the handle being
162
+ * dropped where it was last seen.
163
+ */
164
+ function useDrag(onMove) {
165
+ const ref = useRef(null);
166
+ const moving = useRef(false);
167
+ const report = useCallback((event) => {
168
+ const element = ref.current;
169
+ if (!element) {
170
+ return;
171
+ }
172
+ const box = element.getBoundingClientRect();
173
+ onMove(clamp((event.clientX - box.left) / box.width, 0, 1), clamp((event.clientY - box.top) / box.height, 0, 1));
174
+ }, [onMove]);
175
+ useEffect(() => {
176
+ const move = (event) => moving.current && report(event);
177
+ const up = () => {
178
+ moving.current = false;
179
+ };
180
+ window.addEventListener("pointermove", move);
181
+ window.addEventListener("pointerup", up);
182
+ window.addEventListener("pointercancel", up);
183
+ return () => {
184
+ window.removeEventListener("pointermove", move);
185
+ window.removeEventListener("pointerup", up);
186
+ window.removeEventListener("pointercancel", up);
187
+ };
188
+ }, [report]);
189
+ const onPointerDown = (event) => {
190
+ moving.current = true;
191
+ report(event);
192
+ };
193
+ return { ref, onPointerDown };
194
+ }
195
+ export function ColorPicker({ color, onChange, presets = DEFAULT_COLOR_PRESETS, showPresets = true, presetGroups, showThemeColors = true, showCollaboratorColors = false, showInput = true, width = 232, "aria-label": ariaLabel = "Colour picker", }) {
196
+ const [hsv, setHsv] = useState(() => rgbToHsv(toRgb(color)));
197
+ const [text, setText] = useState(() => toHex(toRgb(color)));
198
+ /* A drag reports a colour, which comes back as a prop; without this the
199
+ answer would overwrite the hue whenever grey is picked (grey has none). */
200
+ const own = useRef(false);
201
+ useEffect(() => {
202
+ if (own.current) {
203
+ own.current = false;
204
+ return;
205
+ }
206
+ const rgb = toRgb(color);
207
+ setHsv(rgbToHsv(rgb));
208
+ setText(toHex(rgb));
209
+ }, [color]);
210
+ const rgb = useMemo(() => hsvToRgb(hsv), [hsv]);
211
+ const hex = useMemo(() => toHex(rgb), [rgb]);
212
+ const announce = useCallback((next) => {
213
+ own.current = true;
214
+ setHsv(next);
215
+ const value = toHex(hsvToRgb(next));
216
+ setText(value);
217
+ onChange?.(value);
218
+ }, [onChange]);
219
+ const field = useDrag(useCallback((x, y) => announce({ ...hsv, s: x * 100, v: (1 - y) * 100 }), [announce, hsv]));
220
+ const rail = useDrag(useCallback((x) => announce({ ...hsv, h: x * 360 }), [announce, hsv]));
221
+ /*
222
+ The rows under the field: the caller's presets, then the theme's own
223
+ colours, then the collaborators' — each only when asked for, and each
224
+ read from whichever theme is on rather than from a fixed list.
225
+ */
226
+ const { theme: activeTheme, colorMode: activeMode } = useThemeStore();
227
+ const systemMode = useSystemColorMode();
228
+ const resolvedMode = activeMode === "auto" ? systemMode : activeMode;
229
+ const rows = useMemo(() => {
230
+ const groups = [];
231
+ if (showPresets && presets.length > 0) {
232
+ groups.push({ label: "Presets", colors: presets });
233
+ }
234
+ if (showThemeColors) {
235
+ groups.push(themeColorPresets(activeTheme, resolvedMode));
236
+ }
237
+ if (showCollaboratorColors) {
238
+ groups.push(collaboratorColorPresets(activeTheme, resolvedMode));
239
+ }
240
+ return groups.concat(presetGroups ?? []);
241
+ }, [
242
+ activeTheme,
243
+ presetGroups,
244
+ presets,
245
+ resolvedMode,
246
+ showCollaboratorColors,
247
+ showPresets,
248
+ showThemeColors,
249
+ ]);
250
+ /* Each row's heading names its own swatches, so the ids must not collide. */
251
+ const rowIdPrefix = useId();
252
+ const setFromText = (entry) => {
253
+ setText(entry);
254
+ if (isColor(entry)) {
255
+ const next = rgbToHsv(toRgb(entry));
256
+ own.current = true;
257
+ setHsv(next);
258
+ onChange?.(toHex(toRgb(entry)));
259
+ }
260
+ };
261
+ const nudge = (event, axis) => {
262
+ const step = event.shiftKey ? 10 : 1;
263
+ const back = event.key === "ArrowLeft" || event.key === "ArrowDown";
264
+ const forward = event.key === "ArrowRight" || event.key === "ArrowUp";
265
+ if (!back && !forward) {
266
+ return;
267
+ }
268
+ event.preventDefault();
269
+ const by = forward ? step : -step;
270
+ const limit = axis === "h" ? 360 : 100;
271
+ announce({ ...hsv, [axis]: clamp(hsv[axis] + by, 0, limit) });
272
+ };
273
+ const handle = {
274
+ position: "absolute",
275
+ width: 14,
276
+ height: 14,
277
+ borderRadius: "50%",
278
+ border: "2px solid #ffffff",
279
+ boxShadow: "0 0 0 1px rgba(0, 0, 0, 0.35)",
280
+ transform: "translate(-50%, -50%)",
281
+ pointerEvents: "none",
282
+ };
283
+ return (_jsxs(Box, { role: "group", "aria-label": ariaLabel, sx: { display: "grid", gap: 2, width, minWidth: 0 }, children: [_jsx(Box, { ref: field.ref, role: "slider", tabIndex: 0, "aria-label": "Saturation and brightness", "aria-valuetext": `saturation ${Math.round(hsv.s)}%, brightness ${Math.round(hsv.v)}%`, onPointerDown: field.onPointerDown, onKeyDown: (event) => nudge(event, event.key === "ArrowUp" || event.key === "ArrowDown" ? "v" : "s"), sx: {
284
+ position: "relative",
285
+ height: FIELD_HEIGHT,
286
+ borderRadius: 2,
287
+ border: "1px solid",
288
+ borderColor: "var(--borderColor-default)",
289
+ cursor: "crosshair",
290
+ touchAction: "none",
291
+ backgroundColor: toHex(hsvToRgb({ h: hsv.h, s: 100, v: 100 })),
292
+ backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)",
293
+ "&:focus-visible": {
294
+ outline: "2px solid var(--borderColor-accent-emphasis)",
295
+ outlineOffset: "1px",
296
+ },
297
+ }, children: _jsx(Box, { sx: {
298
+ ...handle,
299
+ left: `${hsv.s}%`,
300
+ top: `${100 - hsv.v}%`,
301
+ backgroundColor: hex,
302
+ } }) }), _jsx(Box, { ref: rail.ref, role: "slider", tabIndex: 0, "aria-label": "Hue", "aria-valuemin": 0, "aria-valuemax": 360, "aria-valuenow": Math.round(hsv.h), onPointerDown: rail.onPointerDown, onKeyDown: (event) => nudge(event, "h"), sx: {
303
+ position: "relative",
304
+ height: RAIL_HEIGHT,
305
+ borderRadius: 6,
306
+ border: "1px solid",
307
+ borderColor: "var(--borderColor-default)",
308
+ cursor: "ew-resize",
309
+ touchAction: "none",
310
+ backgroundImage: "linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%)",
311
+ "&:focus-visible": {
312
+ outline: "2px solid var(--borderColor-accent-emphasis)",
313
+ outlineOffset: "1px",
314
+ },
315
+ }, children: _jsx(Box, { sx: {
316
+ ...handle,
317
+ left: `${(hsv.h / 360) * 100}%`,
318
+ top: "50%",
319
+ backgroundColor: toHex(hsvToRgb({ h: hsv.h, s: 100, v: 100 })),
320
+ } }) }), showInput ? (_jsxs(Box, { sx: { display: "flex", gap: 2, alignItems: "center" }, children: [_jsx(Box, { "aria-hidden": "true", sx: {
321
+ width: 28,
322
+ height: 28,
323
+ flex: "0 0 auto",
324
+ borderRadius: 2,
325
+ border: "1px solid",
326
+ borderColor: "var(--borderColor-default)",
327
+ backgroundColor: hex,
328
+ } }), _jsx(TextInput, { "aria-label": "Colour, as hex", value: text, onChange: (event) => setFromText(event.target.value), onBlur: () => setText(hex), size: "small", sx: { flex: "1 1 auto", fontFamily: "mono" }, validationStatus: isColor(text) ? undefined : "error" })] })) : null, rows.map((row) => (_jsxs(Box, { children: [_jsx(Text, { id: `${rowIdPrefix}-${row.label}`, sx: {
329
+ fontSize: 0,
330
+ color: "var(--fgColor-muted)",
331
+ display: "block",
332
+ mb: 1,
333
+ }, children: row.label }), _jsx(Box, { role: "group", "aria-labelledby": `${rowIdPrefix}-${row.label}`, sx: {
334
+ display: "grid",
335
+ gridTemplateColumns: "repeat(auto-fill, minmax(22px, 1fr))",
336
+ gap: 1,
337
+ }, children: row.colors.map((preset, index) => {
338
+ const chosen = preset.toLowerCase() === hex.toLowerCase();
339
+ return (_jsx(Box, { as: "button", type: "button", "aria-label": preset, "aria-pressed": chosen, onClick: () => setFromText(preset), sx: {
340
+ height: 22,
341
+ borderRadius: 2,
342
+ cursor: "pointer",
343
+ backgroundColor: preset,
344
+ border: "1px solid",
345
+ borderColor: chosen
346
+ ? "var(--borderColor-accent-emphasis)"
347
+ : "var(--borderColor-default)",
348
+ boxShadow: chosen
349
+ ? "inset 0 0 0 2px var(--bgColor-default)"
350
+ : "none",
351
+ "&:focus-visible": {
352
+ outline: "2px solid var(--borderColor-accent-emphasis)",
353
+ outlineOffset: "1px",
354
+ },
355
+ } }, `${preset}-${index}`));
356
+ }) })] }, row.label)))] }));
357
+ }
358
+ export default ColorPicker;
@@ -0,0 +1 @@
1
+ export * from "./ColorPicker";
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2023-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from "./ColorPicker";
@@ -2,5 +2,5 @@ import 'react-loading-skeleton/dist/skeleton.css';
2
2
  export interface ContentLoaderProps {
3
3
  count?: number;
4
4
  }
5
- export declare const ContentLoader: (props: ContentLoaderProps) => import("react/jsx-runtime").JSX.Element;
5
+ export declare const ContentLoader: (props: ContentLoaderProps) => import("react").JSX.Element;
6
6
  export default ContentLoader;
@@ -1,4 +1,8 @@
1
1
  import { jsx as _jsx } 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 { Box, useTheme } from "@primer/react";
3
7
  import Skeleton, { SkeletonTheme } from "react-loading-skeleton";
4
8
  import 'react-loading-skeleton/dist/skeleton.css';
@@ -2,7 +2,7 @@ import type { StoryObj } from '@storybook/react';
2
2
  import { ContentLoaderProps } from '../..';
3
3
  declare const meta: {
4
4
  title: string;
5
- component: (props: ContentLoaderProps) => import("react/jsx-runtime").JSX.Element;
5
+ component: (props: ContentLoaderProps) => import("react").JSX.Element;
6
6
  tags: string[];
7
7
  parameters: {
8
8
  layout: string;