@datalayer/primer-addons 1.0.18 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/components/appearance/AppearanceControls.d.ts +3 -2
- package/lib/components/appearance/AppearanceControls.js +71 -3
- package/lib/components/appearance/AppearanceMenu.d.ts +30 -0
- package/lib/components/appearance/AppearanceMenu.js +15 -0
- package/lib/components/appearance/ColorModeCircle.d.ts +28 -0
- package/lib/components/appearance/ColorModeCircle.js +132 -0
- package/lib/components/appearance/ThemeCircle.d.ts +21 -0
- package/lib/components/appearance/ThemeCircle.js +46 -0
- package/lib/components/appearance/ThemePreviewCard.d.ts +12 -0
- package/lib/components/appearance/ThemePreviewCard.js +83 -0
- package/lib/components/appearance/index.d.ts +4 -0
- package/lib/components/appearance/index.js +4 -0
- package/lib/components/box/Box.d.ts +2 -1
- package/lib/components/closeable-flash/CloseableFlash.js +58 -1
- package/lib/components/color/ColorSwatch.d.ts +23 -0
- package/lib/components/color/ColorSwatch.js +62 -0
- package/lib/components/color/index.d.ts +1 -0
- package/lib/components/color/index.js +5 -0
- package/lib/components/index.d.ts +6 -0
- package/lib/components/index.js +6 -0
- package/lib/components/logo/DatalayerLogo.js +49 -1
- package/lib/components/logo/DatalayerTextAI.d.ts +4 -4
- package/lib/components/logo/DatalayerTextAI.js +6 -6
- package/lib/components/logo/index.d.ts +5 -7
- package/lib/components/logo/index.js +5 -7
- package/lib/components/side-overlay/SideOverlay.js +1 -1
- package/lib/components/sliding-panel/SlidingPanel.d.ts +19 -0
- package/lib/components/sliding-panel/SlidingPanel.js +217 -0
- package/lib/components/sliding-panel/SlidingPanel.stories.d.ts +16 -0
- package/lib/components/sliding-panel/SlidingPanel.stories.js +68 -0
- package/lib/components/toolbar/Toolbar.js +18 -1
- package/lib/components/toolbar/ToolbarButton.js +12 -7
- package/lib/components/toolbar/ToolbarRenderer.js +3 -0
- package/lib/components/toolbar/types.d.ts +11 -2
- package/lib/theme/colors/datalayerColors.d.ts +12 -0
- package/lib/theme/colors/datalayerColors.js +13 -0
- package/lib/theme/colors/earthColors.d.ts +12 -0
- package/lib/theme/colors/earthColors.js +13 -0
- package/lib/theme/colors/index.d.ts +3 -0
- package/lib/theme/colors/index.js +9 -0
- package/lib/theme/colors/ivoryColors.d.ts +46 -0
- package/lib/theme/colors/ivoryColors.js +56 -0
- package/lib/theme/colors/lovelyColors.d.ts +12 -0
- package/lib/theme/colors/lovelyColors.js +13 -0
- package/lib/theme/colors/matrixColors.d.ts +12 -0
- package/lib/theme/colors/matrixColors.js +13 -0
- package/lib/theme/colors/sandColors.d.ts +45 -0
- package/lib/theme/colors/sandColors.js +55 -0
- package/lib/theme/colors/spatialColors.d.ts +12 -0
- package/lib/theme/colors/spatialColors.js +13 -0
- package/lib/theme/colors/sunColors.d.ts +45 -0
- package/lib/theme/colors/sunColors.js +55 -0
- package/lib/theme/css/createThemeCSSVars.d.ts +25 -1
- package/lib/theme/css/createThemeCSSVars.js +56 -3
- package/lib/theme/index.d.ts +3 -0
- package/lib/theme/index.js +3 -0
- package/lib/theme/palettes/ColorPalette.js +121 -1
- package/lib/theme/themeRegistry.d.ts +1 -1
- package/lib/theme/themeRegistry.js +151 -1
- package/lib/theme/themes/datalayerTheme.js +58 -7
- package/lib/theme/themes/earthTheme.js +50 -0
- package/lib/theme/themes/ivoryTheme.d.ts +1091 -0
- package/lib/theme/themes/ivoryTheme.js +257 -0
- package/lib/theme/themes/lovelyTheme.js +58 -7
- package/lib/theme/themes/matrixTheme.js +57 -8
- package/lib/theme/themes/sandTheme.d.ts +1091 -0
- package/lib/theme/themes/sandTheme.js +256 -0
- package/lib/theme/themes/spatialTheme.js +58 -7
- package/lib/theme/themes/sunTheme.d.ts +1091 -0
- package/lib/theme/themes/sunTheme.js +257 -0
- package/lib/utils/Helper.d.ts +1 -1
- package/lib/utils/Portals.d.ts +0 -1
- package/lib/utils/Portals.js +0 -1
- package/package.json +9 -5
- package/lib/App.d.ts +0 -13
- package/lib/App.js +0 -25
- package/lib/components/logo/AI.d.ts +0 -17
- package/lib/components/logo/AI.js +0 -18
- package/lib/components/logo/AI2.d.ts +0 -20
- package/lib/components/logo/AI2.js +0 -23
|
@@ -0,0 +1,62 @@
|
|
|
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 { useLayoutEffect, useRef, useState } from "react";
|
|
7
|
+
import { Box, Text } from "@primer/react";
|
|
8
|
+
import { useThemeStore } from "../../theme/useThemeStore";
|
|
9
|
+
/** Convert an `rgb()` / `rgba()` string to a `#rrggbb` hex value. */
|
|
10
|
+
function rgbToHex(rgb) {
|
|
11
|
+
const match = rgb.match(/rgba?\(([^)]+)\)/);
|
|
12
|
+
if (!match) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const parts = match[1].split(",").map((part) => part.trim());
|
|
16
|
+
if (parts.length < 3) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const [r, g, b] = parts.map((part) => parseInt(part, 10));
|
|
20
|
+
if ([r, g, b].some((channel) => Number.isNaN(channel))) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const toHex = (channel) => channel.toString(16).padStart(2, "0");
|
|
24
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Theme-aware color swatch. Renders a block filled with `color`, then reads
|
|
28
|
+
* the resolved value via `getComputedStyle` and displays it as a hex string.
|
|
29
|
+
* The resolved value recomputes whenever the active theme or color mode
|
|
30
|
+
* changes, so swatches backed by CSS custom properties stay in sync.
|
|
31
|
+
*/
|
|
32
|
+
export function ColorSwatch({ color, label, showValue = true, height = 54, }) {
|
|
33
|
+
const ref = useRef(null);
|
|
34
|
+
const { theme, colorMode } = useThemeStore();
|
|
35
|
+
const [hex, setHex] = useState(null);
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
const raf = requestAnimationFrame(() => {
|
|
38
|
+
if (!ref.current) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const rgb = getComputedStyle(ref.current).backgroundColor;
|
|
42
|
+
setHex(rgbToHex(rgb));
|
|
43
|
+
});
|
|
44
|
+
return () => cancelAnimationFrame(raf);
|
|
45
|
+
}, [color, theme, colorMode]);
|
|
46
|
+
return (_jsxs(Box, { sx: {
|
|
47
|
+
display: "grid",
|
|
48
|
+
gridTemplateRows: `${height}px auto`,
|
|
49
|
+
border: "1px solid",
|
|
50
|
+
borderColor: "var(--borderColor-default)",
|
|
51
|
+
borderRadius: 2,
|
|
52
|
+
overflow: "hidden",
|
|
53
|
+
minWidth: 0,
|
|
54
|
+
}, children: [_jsx(Box, { ref: ref, style: { backgroundColor: color } }), _jsxs(Box, { sx: { p: 2, display: "grid", gap: 1 }, children: [_jsx(Text, { sx: {
|
|
55
|
+
fontSize: 0,
|
|
56
|
+
color: "fg.default",
|
|
57
|
+
whiteSpace: "nowrap",
|
|
58
|
+
overflow: "hidden",
|
|
59
|
+
textOverflow: "ellipsis",
|
|
60
|
+
}, title: label ?? color, children: label ?? color }), showValue && hex ? (_jsx(Text, { sx: { fontSize: 0, color: "fg.muted", fontFamily: "mono" }, children: hex })) : null] })] }));
|
|
61
|
+
}
|
|
62
|
+
export default ColorSwatch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./ColorSwatch";
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export * from "./appearance/AppearanceControls";
|
|
2
2
|
export * from "./appearance/AppearanceControlsWithStore";
|
|
3
|
+
export * from "./appearance/AppearanceMenu";
|
|
4
|
+
export * from "./appearance/ColorModeCircle";
|
|
5
|
+
export * from "./appearance/ThemePreviewCard";
|
|
6
|
+
export * from "./appearance/ThemeCircle";
|
|
3
7
|
export * from "./box/Box";
|
|
4
8
|
export * from "./card/Card";
|
|
9
|
+
export * from "./color";
|
|
5
10
|
export * from "./content-loader/ContentLoader";
|
|
6
11
|
export * from "./closeable-flash/CloseableFlash";
|
|
7
12
|
export * from "./icons/CircleIcon";
|
|
8
13
|
export * from "./side-overlay/SideOverlay";
|
|
14
|
+
export * from "./sliding-panel/SlidingPanel";
|
|
9
15
|
export * from "./slider/Slider";
|
|
10
16
|
export * from "./logo";
|
|
11
17
|
export * from "./appearance";
|
package/lib/components/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
export * from "./appearance/AppearanceControls";
|
|
2
2
|
export * from "./appearance/AppearanceControlsWithStore";
|
|
3
|
+
export * from "./appearance/AppearanceMenu";
|
|
4
|
+
export * from "./appearance/ColorModeCircle";
|
|
5
|
+
export * from "./appearance/ThemePreviewCard";
|
|
6
|
+
export * from "./appearance/ThemeCircle";
|
|
3
7
|
export * from "./box/Box";
|
|
4
8
|
export * from "./card/Card";
|
|
9
|
+
export * from "./color";
|
|
5
10
|
export * from "./content-loader/ContentLoader";
|
|
6
11
|
export * from "./closeable-flash/CloseableFlash";
|
|
7
12
|
export * from "./icons/CircleIcon";
|
|
8
13
|
export * from "./side-overlay/SideOverlay";
|
|
14
|
+
export * from "./sliding-panel/SlidingPanel";
|
|
9
15
|
export * from "./slider/Slider";
|
|
10
16
|
export * from "./logo";
|
|
11
17
|
export * from "./appearance";
|
|
@@ -4,7 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
6
|
import { forwardRef, useId } from 'react';
|
|
7
|
-
import { datalayerColors, spatialColors, lovelyColors, matrixColors, earthColors, } from '../../theme';
|
|
7
|
+
import { datalayerColors, spatialColors, lovelyColors, matrixColors, earthColors, sandColors, ivoryColors, sunColors, } from '../../theme';
|
|
8
8
|
/**
|
|
9
9
|
* Gradient pattern derived from the original brand logo SVG:
|
|
10
10
|
* - Left bars: accent → brand (bright → mid)
|
|
@@ -93,6 +93,54 @@ export const THEME_LOGO_COLORS = {
|
|
|
93
93
|
secondaryGradient: [earthColors.oceanHover, earthColors.oceanAccent],
|
|
94
94
|
},
|
|
95
95
|
},
|
|
96
|
+
sand: {
|
|
97
|
+
light: {
|
|
98
|
+
primary: sandColors.sandBrand,
|
|
99
|
+
secondary: sandColors.sandText,
|
|
100
|
+
textColor: sandColors.gray,
|
|
101
|
+
primaryGradient: [sandColors.sandAccent, sandColors.sandBrand],
|
|
102
|
+
secondaryGradient: [sandColors.sandHover, sandColors.sandText],
|
|
103
|
+
},
|
|
104
|
+
dark: {
|
|
105
|
+
primary: sandColors.sandBright,
|
|
106
|
+
secondary: sandColors.sandAccent,
|
|
107
|
+
textColor: sandColors.gray,
|
|
108
|
+
primaryGradient: [sandColors.sandBright, sandColors.sandBrand],
|
|
109
|
+
secondaryGradient: [sandColors.sandHover, sandColors.sandAccent],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
ivory: {
|
|
113
|
+
light: {
|
|
114
|
+
primary: ivoryColors.ivoryBrand,
|
|
115
|
+
secondary: ivoryColors.ivoryText,
|
|
116
|
+
textColor: ivoryColors.gray,
|
|
117
|
+
primaryGradient: [ivoryColors.ivoryAccent, ivoryColors.ivoryBrand],
|
|
118
|
+
secondaryGradient: [ivoryColors.ivoryHover, ivoryColors.ivoryText],
|
|
119
|
+
},
|
|
120
|
+
dark: {
|
|
121
|
+
primary: ivoryColors.ivoryBright,
|
|
122
|
+
secondary: ivoryColors.ivoryAccent,
|
|
123
|
+
textColor: ivoryColors.gray,
|
|
124
|
+
primaryGradient: [ivoryColors.ivoryBright, ivoryColors.ivoryBrand],
|
|
125
|
+
secondaryGradient: [ivoryColors.ivoryHover, ivoryColors.ivoryAccent],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
sun: {
|
|
129
|
+
light: {
|
|
130
|
+
primary: sunColors.sunBrand,
|
|
131
|
+
secondary: sunColors.sunText,
|
|
132
|
+
textColor: sunColors.gray,
|
|
133
|
+
primaryGradient: [sunColors.sunAccent, sunColors.sunBrand],
|
|
134
|
+
secondaryGradient: [sunColors.sunHover, sunColors.sunText],
|
|
135
|
+
},
|
|
136
|
+
dark: {
|
|
137
|
+
primary: sunColors.sunBright,
|
|
138
|
+
secondary: sunColors.sunAccent,
|
|
139
|
+
textColor: sunColors.gray,
|
|
140
|
+
primaryGradient: [sunColors.sunBright, sunColors.sunBrand],
|
|
141
|
+
secondaryGradient: [sunColors.sunHover, sunColors.sunAccent],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
96
144
|
};
|
|
97
145
|
export function getLogoColors(variant = 'datalayer', colorMode = 'light') {
|
|
98
146
|
const mode = colorMode === 'auto' ? 'light' : colorMode;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { type SVGProps } from
|
|
2
|
-
import { type ThemeVariant } from
|
|
3
|
-
export interface DatalayerTextAIProps extends Omit<SVGProps<SVGSVGElement>,
|
|
1
|
+
import { type SVGProps } from "react";
|
|
2
|
+
import { type ThemeVariant } from "../../theme";
|
|
3
|
+
export interface DatalayerTextAIProps extends Omit<SVGProps<SVGSVGElement>, "ref"> {
|
|
4
4
|
size?: number;
|
|
5
5
|
scale?: number;
|
|
6
|
-
colorMode?:
|
|
6
|
+
colorMode?: "light" | "dark" | "auto";
|
|
7
7
|
variant?: ThemeVariant;
|
|
8
8
|
/** Colour for the "LAYER" part of the wordmark. */
|
|
9
9
|
primaryColor?: string;
|
|
@@ -3,10 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
4
|
* Distributed under the terms of the Modified BSD License.
|
|
5
5
|
*/
|
|
6
|
-
import { forwardRef } from
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { getLogoColors } from './DatalayerLogo';
|
|
6
|
+
import { forwardRef } from "react";
|
|
7
|
+
import { DatalayerText } from "./DatalayerText";
|
|
8
|
+
import { getLogoColors } from "./DatalayerLogo";
|
|
10
9
|
const TEXT_LEFT_TRIM = 12;
|
|
11
10
|
const TEXT_END_X = 136.672 - TEXT_LEFT_TRIM;
|
|
12
11
|
const AI_SIZE = 24;
|
|
@@ -15,13 +14,14 @@ const AI_VISIBLE_LEFT = 0.25;
|
|
|
15
14
|
const AI_VISIBLE_HEIGHT = 16.136075;
|
|
16
15
|
const AI_SCALE_TO_TEXT_HEIGHT = 25 / AI_VISIBLE_HEIGHT;
|
|
17
16
|
const AI_RENDER_SIZE = AI_SIZE * AI_SCALE_TO_TEXT_HEIGHT;
|
|
17
|
+
const AI_RENDER_SCALE = AI_RENDER_SIZE / AI_SIZE;
|
|
18
18
|
const AI_X_TRIM = AI_VISIBLE_LEFT * AI_SCALE_TO_TEXT_HEIGHT;
|
|
19
19
|
const AI_Y_TRIM = AI_VISIBLE_TOP * AI_SCALE_TO_TEXT_HEIGHT;
|
|
20
20
|
/**
|
|
21
21
|
* Datalayer wordmark followed by AI glyph on one line.
|
|
22
22
|
* Both elements are aligned to the same 25-unit cap height.
|
|
23
23
|
*/
|
|
24
|
-
export const DatalayerTextAI = forwardRef(function DatalayerTextAI({ size = 25, scale = 1, colorMode =
|
|
24
|
+
export const DatalayerTextAI = forwardRef(function DatalayerTextAI({ size = 25, scale = 1, colorMode = "light", variant = "datalayer", primaryColor, secondaryColor, textColor,
|
|
25
25
|
// 2.380655 ~= 25 / (0.7 * 15.00187)
|
|
26
26
|
textSizeMultiplier = 2.380655, aiPrimaryColor, aiSecondaryColor, aiGap = 6, ...svgProps }, ref) {
|
|
27
27
|
const themed = getLogoColors(variant, colorMode);
|
|
@@ -35,6 +35,6 @@ textSizeMultiplier = 2.380655, aiPrimaryColor, aiSecondaryColor, aiGap = 6, ...s
|
|
|
35
35
|
const baseWidth = (viewBoxWidth / 25) * size;
|
|
36
36
|
const height = baseHeight * scale;
|
|
37
37
|
const width = baseWidth * scale;
|
|
38
|
-
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: `0 0 ${viewBoxWidth} 25`, width: width, height: height, role: "img", "aria-label": "Datalayer AI", ref: ref, ...svgProps, children: [_jsx(DatalayerText, { primaryColor: layerColor, secondaryColor: dataTextColor, sizeMultiplier: textSizeMultiplier, x: -TEXT_LEFT_TRIM }),
|
|
38
|
+
return (_jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: `0 0 ${viewBoxWidth} 25`, width: width, height: height, role: "img", "aria-label": "Datalayer AI", ref: ref, ...svgProps, children: [_jsx(DatalayerText, { primaryColor: layerColor, secondaryColor: dataTextColor, sizeMultiplier: textSizeMultiplier, x: -TEXT_LEFT_TRIM }), _jsxs("g", { transform: `translate(${TEXT_END_X + aiGap - AI_X_TRIM} ${-AI_Y_TRIM}) scale(${AI_RENDER_SCALE})`, children: [_jsx("path", { d: "m13.788825 3.932 6.43325 16.136075h3.5279L17.316725 3.932H13.788825Z", fill: resolvedAiPrimary, strokeWidth: 0.25 }), _jsx("path", { d: "m6.325375 13.682775 2.20125 -5.67065 2.201275 5.67065H6.325375ZM6.68225 3.932 0.25 20.068075h3.596525l1.3155 -3.3886h6.729425l1.315275 3.3886h3.59655L10.371 3.932H6.68225Z", fill: resolvedAiSecondary, fillRule: "evenodd", clipRule: "evenodd", strokeWidth: 0.25 })] })] }));
|
|
39
39
|
});
|
|
40
40
|
export default DatalayerTextAI;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from './DatalayerText';
|
|
7
|
-
export * from './DatalayerTextAI';
|
|
1
|
+
export * from "./DI";
|
|
2
|
+
export * from "./DatalayerLogo";
|
|
3
|
+
export * from "./DatalayerLogoText";
|
|
4
|
+
export * from "./DatalayerText";
|
|
5
|
+
export * from "./DatalayerTextAI";
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
3
|
-
export * from
|
|
4
|
-
export * from
|
|
5
|
-
export * from
|
|
6
|
-
export * from './DatalayerText';
|
|
7
|
-
export * from './DatalayerTextAI';
|
|
1
|
+
export * from "./DI";
|
|
2
|
+
export * from "./DatalayerLogo";
|
|
3
|
+
export * from "./DatalayerLogoText";
|
|
4
|
+
export * from "./DatalayerText";
|
|
5
|
+
export * from "./DatalayerTextAI";
|
|
@@ -37,7 +37,7 @@ const PrimerAddonOverlay = (props) => {
|
|
|
37
37
|
display: 'flex',
|
|
38
38
|
justifyContent: 'right',
|
|
39
39
|
width: '100%'
|
|
40
|
-
}, children: _jsx(IconButton, { variant: 'invisible', ref: closeButtonRef, onClick: closeOverlay, icon: XIcon, "aria-
|
|
40
|
+
}, children: _jsx(IconButton, { variant: 'invisible', ref: closeButtonRef, onClick: closeOverlay, icon: XIcon, "aria-label": "Close" }) }), content] }) }));
|
|
41
41
|
return (closeButtonRef && openButtonRef ?
|
|
42
42
|
_jsx(_Fragment, { children: isOpen ?
|
|
43
43
|
direction === 'left' ?
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
export type SlidingPanelPosition = 'north' | 'south' | 'west' | 'east';
|
|
3
|
+
export type SlidingPanelVariant = 'default' | 'info' | 'success' | 'warning' | 'error' | 'danger';
|
|
4
|
+
export interface SlidingPanelProps {
|
|
5
|
+
isOpen: boolean;
|
|
6
|
+
message: ReactNode;
|
|
7
|
+
details?: ReactNode;
|
|
8
|
+
onDismiss?: () => void;
|
|
9
|
+
position?: SlidingPanelPosition;
|
|
10
|
+
variant?: SlidingPanelVariant;
|
|
11
|
+
durationMs?: number;
|
|
12
|
+
transitionMs?: number;
|
|
13
|
+
zIndex?: number;
|
|
14
|
+
offset?: number;
|
|
15
|
+
panelSize?: number | string;
|
|
16
|
+
fullWidth?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare const SlidingPanel: ({ isOpen, message, details, onDismiss, position, variant, durationMs, transitionMs, zIndex, offset, panelSize, fullWidth, }: SlidingPanelProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
19
|
+
export default SlidingPanel;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { IconButton } from '@primer/react';
|
|
4
|
+
import { XIcon } from '@primer/octicons-react';
|
|
5
|
+
import { Box } from '../box/Box';
|
|
6
|
+
const variantStyles = {
|
|
7
|
+
default: {
|
|
8
|
+
bg: 'var(--bgColor-default, #ffffff)',
|
|
9
|
+
border: 'var(--borderColor-default, #d0d7de)',
|
|
10
|
+
fg: 'var(--fgColor-default, #1f2328)',
|
|
11
|
+
},
|
|
12
|
+
info: {
|
|
13
|
+
bg: 'var(--bgColor-accent-muted, #ddf4ff)',
|
|
14
|
+
border: 'var(--borderColor-accent-emphasis, #218bff)',
|
|
15
|
+
fg: 'var(--fgColor-default, #1f2328)',
|
|
16
|
+
},
|
|
17
|
+
success: {
|
|
18
|
+
bg: 'var(--bgColor-success-muted, #dafbe1)',
|
|
19
|
+
border: 'var(--borderColor-success-emphasis, #1a7f37)',
|
|
20
|
+
fg: 'var(--fgColor-success, #1a7f37)',
|
|
21
|
+
},
|
|
22
|
+
warning: {
|
|
23
|
+
bg: 'var(--bgColor-attention-muted, #fff8c5)',
|
|
24
|
+
border: 'var(--borderColor-attention-emphasis, #9a6700)',
|
|
25
|
+
fg: 'var(--fgColor-attention, #9a6700)',
|
|
26
|
+
},
|
|
27
|
+
error: {
|
|
28
|
+
bg: 'var(--bgColor-danger-muted, #ffebe9)',
|
|
29
|
+
border: 'var(--borderColor-danger-emphasis, #cf222e)',
|
|
30
|
+
fg: 'var(--fgColor-danger, #cf222e)',
|
|
31
|
+
},
|
|
32
|
+
danger: {
|
|
33
|
+
bg: 'var(--bgColor-danger-muted, #ffebe9)',
|
|
34
|
+
border: 'var(--borderColor-danger-emphasis, #cf222e)',
|
|
35
|
+
fg: 'var(--fgColor-danger, #cf222e)',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const closeButtonStyles = {
|
|
39
|
+
default: {
|
|
40
|
+
fg: 'var(--fgColor-default, #1f2328)',
|
|
41
|
+
hoverBg: 'rgba(31, 35, 40, 0.10)',
|
|
42
|
+
},
|
|
43
|
+
info: {
|
|
44
|
+
fg: 'var(--fgColor-accent, #0969da)',
|
|
45
|
+
hoverBg: 'rgba(9, 105, 218, 0.14)',
|
|
46
|
+
},
|
|
47
|
+
success: {
|
|
48
|
+
fg: 'var(--fgColor-success, #1a7f37)',
|
|
49
|
+
hoverBg: 'rgba(26, 127, 55, 0.14)',
|
|
50
|
+
},
|
|
51
|
+
warning: {
|
|
52
|
+
fg: 'var(--fgColor-attention, #9a6700)',
|
|
53
|
+
hoverBg: 'rgba(154, 103, 0, 0.16)',
|
|
54
|
+
},
|
|
55
|
+
error: {
|
|
56
|
+
fg: 'var(--fgColor-danger, #cf222e)',
|
|
57
|
+
hoverBg: 'rgba(207, 34, 46, 0.14)',
|
|
58
|
+
},
|
|
59
|
+
danger: {
|
|
60
|
+
fg: 'var(--fgColor-danger, #cf222e)',
|
|
61
|
+
hoverBg: 'rgba(207, 34, 46, 0.14)',
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
const closedTransforms = {
|
|
65
|
+
north: 'translateY(-100%)',
|
|
66
|
+
south: 'translateY(100%)',
|
|
67
|
+
west: 'translateX(-100%)',
|
|
68
|
+
east: 'translateX(100%)',
|
|
69
|
+
};
|
|
70
|
+
const edgeBorderStyles = {
|
|
71
|
+
north: {
|
|
72
|
+
borderTop: '0',
|
|
73
|
+
borderRight: '0',
|
|
74
|
+
borderBottom: '1px solid',
|
|
75
|
+
borderLeft: '0',
|
|
76
|
+
},
|
|
77
|
+
south: {
|
|
78
|
+
borderTop: '1px solid',
|
|
79
|
+
borderRight: '0',
|
|
80
|
+
borderBottom: '0',
|
|
81
|
+
borderLeft: '0',
|
|
82
|
+
},
|
|
83
|
+
west: {
|
|
84
|
+
borderTop: '0',
|
|
85
|
+
borderRight: '1px solid',
|
|
86
|
+
borderBottom: '0',
|
|
87
|
+
borderLeft: '0',
|
|
88
|
+
},
|
|
89
|
+
east: {
|
|
90
|
+
borderTop: '0',
|
|
91
|
+
borderRight: '0',
|
|
92
|
+
borderBottom: '0',
|
|
93
|
+
borderLeft: '1px solid',
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
export const SlidingPanel = ({ isOpen, message, details, onDismiss, position = 'north', variant = 'default', durationMs, transitionMs = 260, zIndex = 200, offset = 0, panelSize = 360, fullWidth = true, }) => {
|
|
97
|
+
const [isMounted, setIsMounted] = useState(isOpen);
|
|
98
|
+
const [isVisible, setIsVisible] = useState(isOpen);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (isOpen) {
|
|
101
|
+
setIsMounted(true);
|
|
102
|
+
const showTimer = window.setTimeout(() => setIsVisible(true), 10);
|
|
103
|
+
return () => window.clearTimeout(showTimer);
|
|
104
|
+
}
|
|
105
|
+
setIsVisible(false);
|
|
106
|
+
const hideTimer = window.setTimeout(() => setIsMounted(false), transitionMs);
|
|
107
|
+
return () => window.clearTimeout(hideTimer);
|
|
108
|
+
}, [isOpen, transitionMs]);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (!isOpen || !onDismiss) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (durationMs === undefined || durationMs <= 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const timer = window.setTimeout(() => {
|
|
117
|
+
onDismiss();
|
|
118
|
+
}, durationMs);
|
|
119
|
+
return () => {
|
|
120
|
+
window.clearTimeout(timer);
|
|
121
|
+
};
|
|
122
|
+
}, [isOpen, durationMs, onDismiss]);
|
|
123
|
+
const anchorStyles = useMemo(() => {
|
|
124
|
+
if (position === 'north') {
|
|
125
|
+
return {
|
|
126
|
+
top: offset,
|
|
127
|
+
left: 0,
|
|
128
|
+
right: 0,
|
|
129
|
+
justifyContent: 'center',
|
|
130
|
+
alignItems: 'flex-start',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (position === 'south') {
|
|
134
|
+
return {
|
|
135
|
+
bottom: offset,
|
|
136
|
+
left: 0,
|
|
137
|
+
right: 0,
|
|
138
|
+
justifyContent: 'center',
|
|
139
|
+
alignItems: 'flex-end',
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (position === 'west') {
|
|
143
|
+
return {
|
|
144
|
+
left: offset,
|
|
145
|
+
top: 0,
|
|
146
|
+
bottom: 0,
|
|
147
|
+
justifyContent: 'flex-start',
|
|
148
|
+
alignItems: 'stretch',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
right: offset,
|
|
153
|
+
top: 0,
|
|
154
|
+
bottom: 0,
|
|
155
|
+
justifyContent: 'flex-end',
|
|
156
|
+
alignItems: 'stretch',
|
|
157
|
+
};
|
|
158
|
+
}, [position, offset]);
|
|
159
|
+
if (!isMounted) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const style = variantStyles[variant];
|
|
163
|
+
const closeStyle = closeButtonStyles[variant];
|
|
164
|
+
const horizontal = position === 'north' || position === 'south';
|
|
165
|
+
const edgeBorder = edgeBorderStyles[position];
|
|
166
|
+
return (_jsx(Box, { sx: {
|
|
167
|
+
position: 'fixed',
|
|
168
|
+
zIndex,
|
|
169
|
+
display: 'flex',
|
|
170
|
+
pointerEvents: 'none',
|
|
171
|
+
...anchorStyles,
|
|
172
|
+
}, children: _jsxs(Box, { role: variant === 'error' || variant === 'danger' ? 'alert' : 'status', "aria-live": variant === 'error' || variant === 'danger' ? 'assertive' : 'polite', onClick: onDismiss, sx: {
|
|
173
|
+
pointerEvents: 'auto',
|
|
174
|
+
position: 'relative',
|
|
175
|
+
display: 'flex',
|
|
176
|
+
alignItems: 'center',
|
|
177
|
+
justifyContent: 'flex-start',
|
|
178
|
+
cursor: onDismiss ? 'pointer' : 'default',
|
|
179
|
+
width: horizontal
|
|
180
|
+
? fullWidth
|
|
181
|
+
? '100vw'
|
|
182
|
+
: 'min(960px, 96vw)'
|
|
183
|
+
: panelSize,
|
|
184
|
+
maxWidth: horizontal ? '100vw' : undefined,
|
|
185
|
+
height: horizontal ? undefined : '100vh',
|
|
186
|
+
maxHeight: horizontal ? undefined : '100vh',
|
|
187
|
+
borderColor: style.border,
|
|
188
|
+
borderTop: edgeBorder.borderTop,
|
|
189
|
+
borderRight: edgeBorder.borderRight,
|
|
190
|
+
borderBottom: edgeBorder.borderBottom,
|
|
191
|
+
borderLeft: edgeBorder.borderLeft,
|
|
192
|
+
backgroundColor: style.bg,
|
|
193
|
+
color: style.fg,
|
|
194
|
+
textAlign: 'left',
|
|
195
|
+
px: 4,
|
|
196
|
+
py: horizontal ? 3 : 4,
|
|
197
|
+
boxShadow: 'shadow.large',
|
|
198
|
+
transform: isVisible ? 'translate(0, 0)' : closedTransforms[position],
|
|
199
|
+
opacity: isVisible ? 1 : 0,
|
|
200
|
+
transition: `transform ${transitionMs}ms ease, opacity ${transitionMs}ms ease`,
|
|
201
|
+
overflow: 'auto',
|
|
202
|
+
}, children: [onDismiss ? (_jsx(Box, { sx: {
|
|
203
|
+
position: 'absolute',
|
|
204
|
+
top: 2,
|
|
205
|
+
right: 3,
|
|
206
|
+
}, children: _jsx(IconButton, { variant: "invisible", icon: XIcon, "aria-label": "Close panel", onClick: event => {
|
|
207
|
+
event.stopPropagation();
|
|
208
|
+
onDismiss();
|
|
209
|
+
}, sx: {
|
|
210
|
+
color: closeStyle.fg,
|
|
211
|
+
borderRadius: 2,
|
|
212
|
+
'&:hover': {
|
|
213
|
+
backgroundColor: closeStyle.hoverBg,
|
|
214
|
+
},
|
|
215
|
+
} }) })) : null, _jsxs(Box, { sx: { width: 'min(980px, 100%)', display: 'grid', gap: 1, pr: onDismiss ? 6 : 0 }, children: [_jsx(Box, { sx: { fontSize: 3, fontWeight: 500, lineHeight: 1.4 }, children: message }), details ? (_jsx(Box, { sx: { fontSize: 1, fontWeight: 400, lineHeight: 1.4, color: 'fg.muted' }, children: details })) : null] })] }) }));
|
|
216
|
+
};
|
|
217
|
+
export default SlidingPanel;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
import { type SlidingPanelProps } from './SlidingPanel';
|
|
3
|
+
declare const meta: {
|
|
4
|
+
title: string;
|
|
5
|
+
component: ({ isOpen, message, details, onDismiss, position, variant, durationMs, transitionMs, zIndex, offset, panelSize, fullWidth, }: SlidingPanelProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
tags: string[];
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export default meta;
|
|
12
|
+
type Story = StoryObj<typeof meta>;
|
|
13
|
+
export declare const NorthInfo: Story;
|
|
14
|
+
export declare const SouthWarning: Story;
|
|
15
|
+
export declare const EastSuccess: Story;
|
|
16
|
+
export declare const WestError: Story;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { Box, Button, Heading } from '@primer/react';
|
|
4
|
+
import { SlidingPanel } from './SlidingPanel';
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/SlidingPanel',
|
|
7
|
+
component: SlidingPanel,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
layout: 'fullscreen',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
export default meta;
|
|
14
|
+
const Demo = (props) => {
|
|
15
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const openButtonRef = useRef(null);
|
|
17
|
+
return (_jsxs(Box, { sx: { p: 4, minHeight: '100vh', bg: 'canvas.default' }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 3 }, children: "Sliding Panel Demo" }), _jsx(Button, { ref: openButtonRef, onClick: () => setIsOpen(true), children: "Open panel" }), _jsx(SlidingPanel, { ...props, isOpen: isOpen, onDismiss: () => setIsOpen(false) }), isOpen ? (_jsx(Box, { sx: { mt: 3 }, children: _jsx(Button, { size: "small", onClick: () => setIsOpen(false), children: "Close" }) })) : null] }));
|
|
18
|
+
};
|
|
19
|
+
export const NorthInfo = {
|
|
20
|
+
args: {
|
|
21
|
+
isOpen: false,
|
|
22
|
+
message: 'Sliding panel message',
|
|
23
|
+
details: 'Additional details appear below the message in normal font.',
|
|
24
|
+
position: 'north',
|
|
25
|
+
variant: 'info',
|
|
26
|
+
durationMs: 4500,
|
|
27
|
+
fullWidth: true,
|
|
28
|
+
},
|
|
29
|
+
render: args => _jsx(Demo, { ...args }),
|
|
30
|
+
};
|
|
31
|
+
export const SouthWarning = {
|
|
32
|
+
args: {
|
|
33
|
+
isOpen: false,
|
|
34
|
+
message: 'Sliding panel message',
|
|
35
|
+
details: 'Additional details appear below the message in normal font.',
|
|
36
|
+
position: 'south',
|
|
37
|
+
variant: 'warning',
|
|
38
|
+
durationMs: 0,
|
|
39
|
+
fullWidth: true,
|
|
40
|
+
},
|
|
41
|
+
render: args => _jsx(Demo, { ...args }),
|
|
42
|
+
};
|
|
43
|
+
export const EastSuccess = {
|
|
44
|
+
args: {
|
|
45
|
+
isOpen: false,
|
|
46
|
+
message: 'Sliding panel message',
|
|
47
|
+
details: 'Additional details appear below the message in normal font.',
|
|
48
|
+
position: 'east',
|
|
49
|
+
variant: 'success',
|
|
50
|
+
durationMs: 0,
|
|
51
|
+
panelSize: 360,
|
|
52
|
+
fullWidth: false,
|
|
53
|
+
},
|
|
54
|
+
render: args => _jsx(Demo, { ...args }),
|
|
55
|
+
};
|
|
56
|
+
export const WestError = {
|
|
57
|
+
args: {
|
|
58
|
+
isOpen: false,
|
|
59
|
+
message: 'Sliding panel message',
|
|
60
|
+
details: 'Additional details appear below the message in normal font.',
|
|
61
|
+
position: 'west',
|
|
62
|
+
variant: 'error',
|
|
63
|
+
durationMs: 0,
|
|
64
|
+
panelSize: 360,
|
|
65
|
+
fullWidth: false,
|
|
66
|
+
},
|
|
67
|
+
render: args => _jsx(Demo, { ...args }),
|
|
68
|
+
};
|
|
@@ -30,7 +30,24 @@ export function Toolbar({ items, extraItems, className, disabled, ariaLabel = 'E
|
|
|
30
30
|
const allItems = useMemo(() => {
|
|
31
31
|
if (!extraItems?.length)
|
|
32
32
|
return items;
|
|
33
|
-
|
|
33
|
+
/*
|
|
34
|
+
* An extra item takes the place of the item it shares its key with.
|
|
35
|
+
*
|
|
36
|
+
* A host that knows better what one of the items should do — how this
|
|
37
|
+
* document is saved, where it is run — says so with the key of that item
|
|
38
|
+
* rather than adding a second button next to it. What it leaves out, its
|
|
39
|
+
* order and its icon among them, stays as the toolbar had it.
|
|
40
|
+
*/
|
|
41
|
+
const overrides = new Map(extraItems.map(item => [item.key, item]));
|
|
42
|
+
const merged = items.map(item => {
|
|
43
|
+
const override = overrides.get(item.key);
|
|
44
|
+
if (!override) {
|
|
45
|
+
return item;
|
|
46
|
+
}
|
|
47
|
+
overrides.delete(item.key);
|
|
48
|
+
return { ...item, ...override };
|
|
49
|
+
});
|
|
50
|
+
return [...merged, ...extraItems.filter(item => overrides.has(item.key))];
|
|
34
51
|
}, [items, extraItems]);
|
|
35
52
|
return (_jsx(Box, { className: className, role: "toolbar", "aria-label": ariaLabel, sx: {
|
|
36
53
|
display: 'flex',
|
|
@@ -6,16 +6,19 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
/**
|
|
7
7
|
* ToolbarButton - A single button in a Toolbar.
|
|
8
8
|
*
|
|
9
|
-
* Renders a native <button> with a direct Octicon icon
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
9
|
+
* Renders a native <button> with a direct Octicon icon, wrapped in Primer's
|
|
10
|
+
* Tooltip when the item carries a title. The v37 Tooltip demands a native
|
|
11
|
+
* interactive child — the reason IconButton is avoided — and a plain
|
|
12
|
+
* <button> is exactly that. A disabled item therefore wears `aria-disabled`
|
|
13
|
+
* rather than the `disabled` attribute: a truly disabled button is not
|
|
14
|
+
* interactive, which both silences its tooltip and breaks the invariant —
|
|
15
|
+
* and the tooltip of a disabled action is precisely where the why-disabled
|
|
16
|
+
* explanation lives.
|
|
15
17
|
*
|
|
16
18
|
* @module components/toolbar/ToolbarButton
|
|
17
19
|
*/
|
|
18
20
|
import { isValidElement } from 'react';
|
|
21
|
+
import { Tooltip } from '@primer/react';
|
|
19
22
|
export function ToolbarButton({ item, size = 'medium' }) {
|
|
20
23
|
const { ariaLabel, title, icon, label, isActive, onClick, disabled } = item;
|
|
21
24
|
// Resolve icon → React element.
|
|
@@ -38,7 +41,7 @@ export function ToolbarButton({ item, size = 'medium' }) {
|
|
|
38
41
|
iconElement = _jsx("span", { style: { fontSize: 12, fontWeight: 600, lineHeight: 1 }, children: label });
|
|
39
42
|
}
|
|
40
43
|
const btnSize = size === 'small' ? 28 : 32;
|
|
41
|
-
|
|
44
|
+
const button = (_jsx("button", { type: "button", "aria-label": ariaLabel, "aria-disabled": disabled || undefined, onMouseDown: (e) => e.preventDefault(), onClick: disabled ? undefined : onClick, style: {
|
|
42
45
|
display: 'inline-flex',
|
|
43
46
|
alignItems: 'center',
|
|
44
47
|
justifyContent: 'center',
|
|
@@ -66,5 +69,7 @@ export function ToolbarButton({ item, size = 'medium' }) {
|
|
|
66
69
|
? 'var(--fgColor-accent, #0969da)'
|
|
67
70
|
: 'var(--fgColor-muted, #656d76)';
|
|
68
71
|
}, children: iconElement }));
|
|
72
|
+
// No title: nothing to say, no wrapper to say it with.
|
|
73
|
+
return title ? (_jsx(Tooltip, { text: title, direction: "s", children: button })) : (button);
|
|
69
74
|
}
|
|
70
75
|
export default ToolbarButton;
|