@datalayer/primer-addons 1.0.9 → 1.0.11
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/story-helpers.d.ts +31 -0
- package/lib/story-helpers.js +105 -0
- package/lib/theme/DatalayerThemeProvider.js +89 -1
- package/lib/theme/ThemeSwitcher.d.ts +17 -0
- package/lib/theme/ThemeSwitcher.js +19 -0
- package/lib/theme/ThemedProvider.d.ts +25 -0
- package/lib/theme/ThemedProvider.js +14 -0
- package/lib/theme/colors/datalayerColors.d.ts +14 -0
- package/lib/theme/colors/datalayerColors.js +17 -0
- package/lib/theme/colors/lovelyColors.d.ts +14 -0
- package/lib/theme/colors/lovelyColors.js +17 -0
- package/lib/theme/colors/matrixColors.d.ts +14 -0
- package/lib/theme/colors/matrixColors.js +17 -0
- package/lib/theme/colors/spatialColors.d.ts +14 -0
- package/lib/theme/colors/spatialColors.js +17 -0
- package/lib/theme/index.d.ts +4 -0
- package/lib/theme/index.js +4 -0
- package/lib/theme/themeRegistry.d.ts +105 -0
- package/lib/theme/themeRegistry.js +288 -0
- package/lib/theme/themes/createThemeCSSVars.d.ts +18 -1
- package/lib/theme/themes/createThemeCSSVars.js +72 -11
- package/lib/theme/themes/datalayerTheme.js +4 -0
- package/lib/theme/themes/matrixTheme.js +9 -1
- package/lib/theme/useThemeStore.d.ts +50 -0
- package/lib/theme/useThemeStore.js +65 -0
- package/lib/utils/Portals.d.ts +15 -0
- package/lib/utils/Portals.js +50 -0
- package/package.json +3 -2
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
import { datalayerTheme, datalayerThemeStyles } from './themes/datalayerTheme';
|
|
6
|
+
import { spatialTheme, spatialThemeStyles } from './themes/spatialTheme';
|
|
7
|
+
import { lovelyTheme, lovelyThemeStyles } from './themes/lovelyTheme';
|
|
8
|
+
import { matrixTheme, matrixThemeStyles } from './themes/matrixTheme';
|
|
9
|
+
import { datalayerColors } from './colors/datalayerColors';
|
|
10
|
+
import { spatialColors } from './colors/spatialColors';
|
|
11
|
+
import { lovelyColors } from './colors/lovelyColors';
|
|
12
|
+
import { matrixColors } from './colors/matrixColors';
|
|
13
|
+
/* ─── Registry ────────────────────────────────────────────────────────── */
|
|
14
|
+
export const themeConfigs = {
|
|
15
|
+
datalayer: {
|
|
16
|
+
label: 'Datalayer',
|
|
17
|
+
description: 'Default green palette — the classic Datalayer look.',
|
|
18
|
+
brandColor: datalayerColors.greenBrand,
|
|
19
|
+
defaultColorMode: 'auto',
|
|
20
|
+
primerTheme: datalayerTheme,
|
|
21
|
+
themeStyles: datalayerThemeStyles,
|
|
22
|
+
cardGradient: {
|
|
23
|
+
light: { from: datalayerColors.greenBrand, to: datalayerColors.greenText }, // #16A085 → #117A65
|
|
24
|
+
dark: { from: datalayerColors.greenAccent, to: datalayerColors.greenBrand }, // #1ABC9C → #16A085
|
|
25
|
+
},
|
|
26
|
+
brightPalette: {
|
|
27
|
+
glow: datalayerColors.brightGlow, // #00D68F — vivid emerald
|
|
28
|
+
onGlow: '#1b1f24', // dark text on bright green
|
|
29
|
+
pop: datalayerColors.brightPop, // #00E5FF — electric cyan
|
|
30
|
+
spark: datalayerColors.brightSpark, // #76FF03 — lime neon
|
|
31
|
+
blaze: datalayerColors.brightBlaze, // #FF1744 — vivid red
|
|
32
|
+
surge: datalayerColors.brightSurge, // #2979FF — electric blue
|
|
33
|
+
flame: datalayerColors.brightFlame, // #FF6D00 — vivid orange
|
|
34
|
+
gold: datalayerColors.brightGold, // #FFD600 — vivid yellow
|
|
35
|
+
},
|
|
36
|
+
brightPaletteLight: {
|
|
37
|
+
glow: datalayerColors.brightLightGlow, // #00BFA5 — vivid aqua-green
|
|
38
|
+
onGlow: '#1b1f24', // dark text on aqua-green
|
|
39
|
+
pop: datalayerColors.brightLightPop, // #00B0FF — vivid sky blue
|
|
40
|
+
spark: datalayerColors.brightLightSpark, // #64DD17 — vivid chartreuse
|
|
41
|
+
blaze: datalayerColors.brightLightBlaze, // #FF1744 — vivid red
|
|
42
|
+
surge: datalayerColors.brightLightSurge, // #2979FF — electric blue
|
|
43
|
+
flame: datalayerColors.brightLightFlame, // #FF6D00 — vivid orange
|
|
44
|
+
gold: datalayerColors.brightLightGold, // #FFAB00 — vivid amber
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
spatial: {
|
|
48
|
+
label: 'Spatial',
|
|
49
|
+
description: 'Cosmic indigo & deep blues — inspired by space.',
|
|
50
|
+
brandColor: spatialColors.indigoBrand,
|
|
51
|
+
defaultColorMode: 'auto',
|
|
52
|
+
primerTheme: spatialTheme,
|
|
53
|
+
themeStyles: spatialThemeStyles,
|
|
54
|
+
cardGradient: {
|
|
55
|
+
light: { from: spatialColors.indigoBrand, to: spatialColors.indigoText }, // #4F46E5 → #3730A3
|
|
56
|
+
dark: { from: spatialColors.indigoAccent, to: spatialColors.indigoBrand }, // #6366F1 → #4F46E5
|
|
57
|
+
},
|
|
58
|
+
brightPalette: {
|
|
59
|
+
glow: spatialColors.brightGlow, // #7C4DFF — electric violet
|
|
60
|
+
onGlow: '#ffffff', // white text on violet
|
|
61
|
+
pop: spatialColors.brightPop, // #448AFF — neon blue
|
|
62
|
+
spark: spatialColors.brightSpark, // #E040FB — vivid magenta
|
|
63
|
+
blaze: spatialColors.brightBlaze, // #FF5252 — neon red
|
|
64
|
+
surge: spatialColors.brightSurge, // #00B0FF — deep sky blue
|
|
65
|
+
flame: spatialColors.brightFlame, // #FF9100 — amber orange
|
|
66
|
+
gold: spatialColors.brightGold, // #FFEA00 — electric yellow
|
|
67
|
+
},
|
|
68
|
+
brightPaletteLight: {
|
|
69
|
+
glow: spatialColors.brightLightGlow, // #7C4DFF — electric violet
|
|
70
|
+
onGlow: '#ffffff', // white text on violet
|
|
71
|
+
pop: spatialColors.brightLightPop, // #448AFF — neon blue
|
|
72
|
+
spark: spatialColors.brightLightSpark, // #D500F9 — vivid purple
|
|
73
|
+
blaze: spatialColors.brightLightBlaze, // #FF5252 — neon red
|
|
74
|
+
surge: spatialColors.brightLightSurge, // #00B0FF — vivid sky blue
|
|
75
|
+
flame: spatialColors.brightLightFlame, // #FF9100 — vivid amber orange
|
|
76
|
+
gold: spatialColors.brightLightGold, // #FFC400 — vivid gold
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
lovely: {
|
|
80
|
+
label: 'Lovely',
|
|
81
|
+
description: 'Warm rose & magenta — soft and inviting.',
|
|
82
|
+
brandColor: lovelyColors.roseBrand,
|
|
83
|
+
defaultColorMode: 'auto',
|
|
84
|
+
primerTheme: lovelyTheme,
|
|
85
|
+
themeStyles: lovelyThemeStyles,
|
|
86
|
+
cardGradient: {
|
|
87
|
+
light: { from: lovelyColors.roseBrand, to: lovelyColors.roseText }, // #DB2777 → #9D174D
|
|
88
|
+
dark: { from: lovelyColors.roseAccent, to: lovelyColors.roseBrand }, // #EC4899 → #DB2777
|
|
89
|
+
},
|
|
90
|
+
brightPalette: {
|
|
91
|
+
glow: lovelyColors.brightGlow, // #FF4081 — hot pink
|
|
92
|
+
onGlow: '#ffffff', // white text on hot pink
|
|
93
|
+
pop: lovelyColors.brightPop, // #FF6E40 — vivid coral
|
|
94
|
+
spark: lovelyColors.brightSpark, // #EA80FC — electric fuchsia
|
|
95
|
+
blaze: lovelyColors.brightBlaze, // #FF1744 — vivid crimson
|
|
96
|
+
surge: lovelyColors.brightSurge, // #536DFE — indigo blue
|
|
97
|
+
flame: lovelyColors.brightFlame, // #FF6E40 — coral orange
|
|
98
|
+
gold: lovelyColors.brightGold, // #FFD740 — amber gold
|
|
99
|
+
},
|
|
100
|
+
brightPaletteLight: {
|
|
101
|
+
glow: lovelyColors.brightLightGlow, // #FF4081 — hot pink
|
|
102
|
+
onGlow: '#ffffff', // white text on hot pink
|
|
103
|
+
pop: lovelyColors.brightLightPop, // #FF6E40 — vivid coral
|
|
104
|
+
spark: lovelyColors.brightLightSpark, // #EA80FC — electric fuchsia
|
|
105
|
+
blaze: lovelyColors.brightLightBlaze, // #FF1744 — vivid crimson
|
|
106
|
+
surge: lovelyColors.brightLightSurge, // #536DFE — vivid indigo
|
|
107
|
+
flame: lovelyColors.brightLightFlame, // #FF6E40 — vivid coral orange
|
|
108
|
+
gold: lovelyColors.brightLightGold, // #FFD740 — vivid amber gold
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
matrix: {
|
|
112
|
+
label: 'Matrix',
|
|
113
|
+
description: 'Phosphor green on black — CRT terminal aesthetic.',
|
|
114
|
+
brandColor: matrixColors.greenPhosphor,
|
|
115
|
+
defaultColorMode: 'dark',
|
|
116
|
+
primerTheme: matrixTheme,
|
|
117
|
+
themeStyles: matrixThemeStyles,
|
|
118
|
+
cardGradient: {
|
|
119
|
+
light: { from: matrixColors.greenBrand, to: matrixColors.greenText }, // #16A085 → #117A65
|
|
120
|
+
dark: { from: matrixColors.greenGlow, to: matrixColors.greenHover }, // #39FF14 → #0E6655
|
|
121
|
+
},
|
|
122
|
+
brightPalette: {
|
|
123
|
+
glow: matrixColors.brightGlow, // #39FF14 — neon green
|
|
124
|
+
onGlow: '#1b1f24', // dark text on neon green
|
|
125
|
+
pop: matrixColors.brightPop, // #00FF88 — phosphor cyan
|
|
126
|
+
spark: matrixColors.brightSpark, // #CCFF00 — acid lime
|
|
127
|
+
blaze: matrixColors.brightBlaze, // #FF0040 — matrix red pill
|
|
128
|
+
surge: matrixColors.brightSurge, // #00E5FF — electric blue
|
|
129
|
+
flame: matrixColors.brightFlame, // #FF6D00 — electric orange
|
|
130
|
+
gold: matrixColors.brightGold, // #FFEA00 — glitch yellow
|
|
131
|
+
},
|
|
132
|
+
brightPaletteLight: {
|
|
133
|
+
glow: matrixColors.brightLightGlow, // #00E676 — vivid green
|
|
134
|
+
onGlow: '#1b1f24', // dark text on vivid green
|
|
135
|
+
pop: matrixColors.brightLightPop, // #1DE9B6 — vivid teal-mint
|
|
136
|
+
spark: matrixColors.brightLightSpark, // #AEEA00 — vivid lime
|
|
137
|
+
blaze: matrixColors.brightLightBlaze, // #FF1744 — vivid red
|
|
138
|
+
surge: matrixColors.brightLightSurge, // #00B0FF — vivid sky blue
|
|
139
|
+
flame: matrixColors.brightLightFlame, // #FF6D00 — vivid orange
|
|
140
|
+
gold: matrixColors.brightLightGold, // #FFAB00 — vivid amber
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
/** All available theme variants in display order. */
|
|
145
|
+
export const themeVariants = ['datalayer', 'spatial', 'lovely', 'matrix'];
|
|
146
|
+
/** Look up a theme config by variant name. */
|
|
147
|
+
export function getThemeConfig(variant) {
|
|
148
|
+
return themeConfigs[variant];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resolve the card gradient for a given theme variant and colour mode.
|
|
152
|
+
* Falls back to `datalayer` / `light` when values are missing.
|
|
153
|
+
*/
|
|
154
|
+
export function getCardGradient(variant = 'datalayer', colorMode = 'light') {
|
|
155
|
+
const mode = colorMode === 'auto' ? 'light' : colorMode;
|
|
156
|
+
return themeConfigs[variant]?.cardGradient?.[mode] ?? themeConfigs.datalayer.cardGradient.light;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Get the bright (vivid / OpenAI-blog-style) palette for a given theme variant.
|
|
160
|
+
*
|
|
161
|
+
* When `colorMode` is `'light'` the vivid / saturated light-background palette
|
|
162
|
+
* is returned — these are punchy, high-saturation colours designed to keep SVG
|
|
163
|
+
* illustrations vibrant and energetic on near-white surfaces.
|
|
164
|
+
* When `colorMode` is `'auto'`, the OS preference is queried via
|
|
165
|
+
* `prefers-color-scheme` so palettes always match the effective background.
|
|
166
|
+
* Defaults to the dark-optimised neon palette for backward compatibility.
|
|
167
|
+
*/
|
|
168
|
+
export function getBrightPalette(variant = 'datalayer', colorMode) {
|
|
169
|
+
const cfg = themeConfigs[variant] ?? themeConfigs.datalayer;
|
|
170
|
+
let resolvedMode;
|
|
171
|
+
if (colorMode === 'auto') {
|
|
172
|
+
resolvedMode =
|
|
173
|
+
typeof window !== 'undefined' &&
|
|
174
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
175
|
+
? 'dark'
|
|
176
|
+
: 'light';
|
|
177
|
+
}
|
|
178
|
+
else if (colorMode === 'light') {
|
|
179
|
+
resolvedMode = 'light';
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
resolvedMode = 'dark';
|
|
183
|
+
}
|
|
184
|
+
if (resolvedMode === 'light') {
|
|
185
|
+
return cfg.brightPaletteLight ?? cfg.brightPalette;
|
|
186
|
+
}
|
|
187
|
+
return cfg.brightPalette;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Avatar colour palettes per theme × colour mode.
|
|
191
|
+
*
|
|
192
|
+
* Each palette is a 5-colour array tuned for the `boring-avatars` library.
|
|
193
|
+
* The colours are picked from each theme's brand + bright palette so the
|
|
194
|
+
* avatar always feels native to the current theme.
|
|
195
|
+
*
|
|
196
|
+
* - **Light mode** uses the deeper / accessible colour variants so the
|
|
197
|
+
* avatar is legible on light backgrounds.
|
|
198
|
+
* - **Dark mode** uses the brighter / glowing variants so the avatar
|
|
199
|
+
* pops against dark surfaces.
|
|
200
|
+
*/
|
|
201
|
+
const avatarColorPalettes = {
|
|
202
|
+
datalayer: {
|
|
203
|
+
light: [
|
|
204
|
+
datalayerColors.greenText, // #117A65 — deep green
|
|
205
|
+
datalayerColors.greenBrand, // #16A085 — brand green
|
|
206
|
+
datalayerColors.brightLightPop, // #00B0FF — sky blue
|
|
207
|
+
datalayerColors.brightLightGold, // #FFAB00 — amber
|
|
208
|
+
datalayerColors.gray, // #59595C — neutral
|
|
209
|
+
],
|
|
210
|
+
dark: [
|
|
211
|
+
datalayerColors.greenAccent, // #1ABC9C — bright teal
|
|
212
|
+
datalayerColors.greenBright, // #2ECC71 — bright green
|
|
213
|
+
datalayerColors.brightPop, // #00E5FF — electric cyan
|
|
214
|
+
datalayerColors.brightGold, // #FFD600 — vivid yellow
|
|
215
|
+
datalayerColors.brightFlame, // #FF6D00 — vivid orange
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
spatial: {
|
|
219
|
+
light: [
|
|
220
|
+
spatialColors.indigoText, // #3730A3 — deep indigo
|
|
221
|
+
spatialColors.indigoBrand, // #4F46E5 — brand indigo
|
|
222
|
+
spatialColors.brightLightSpark, // #D500F9 — vivid purple
|
|
223
|
+
spatialColors.brightLightGold, // #FFC400 — vivid gold
|
|
224
|
+
spatialColors.gray, // #8892B0 — nebula gray
|
|
225
|
+
],
|
|
226
|
+
dark: [
|
|
227
|
+
spatialColors.indigoAccent, // #6366F1 — bright indigo
|
|
228
|
+
spatialColors.indigoBright, // #818CF8 — light indigo
|
|
229
|
+
spatialColors.brightPop, // #448AFF — neon blue
|
|
230
|
+
spatialColors.brightSpark, // #E040FB — vivid magenta
|
|
231
|
+
spatialColors.brightGold, // #FFEA00 — electric yellow
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
lovely: {
|
|
235
|
+
light: [
|
|
236
|
+
lovelyColors.roseText, // #9D174D — deep rose
|
|
237
|
+
lovelyColors.roseBrand, // #DB2777 — brand pink
|
|
238
|
+
lovelyColors.brightLightSurge, // #536DFE — vivid indigo
|
|
239
|
+
lovelyColors.brightLightGold, // #FFD740 — amber gold
|
|
240
|
+
lovelyColors.gray, // #9D7A8F — mauve gray
|
|
241
|
+
],
|
|
242
|
+
dark: [
|
|
243
|
+
lovelyColors.roseAccent, // #EC4899 — bright pink
|
|
244
|
+
lovelyColors.roseBright, // #F472B6 — light pink
|
|
245
|
+
lovelyColors.brightPop, // #FF6E40 — vivid coral
|
|
246
|
+
lovelyColors.brightSpark, // #EA80FC — electric fuchsia
|
|
247
|
+
lovelyColors.brightGold, // #FFD740 — amber gold
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
matrix: {
|
|
251
|
+
light: [
|
|
252
|
+
matrixColors.greenText, // #117A65 — deep green
|
|
253
|
+
matrixColors.greenBrand, // #16A085 — brand green
|
|
254
|
+
matrixColors.brightLightPop, // #1DE9B6 — vivid teal
|
|
255
|
+
matrixColors.brightLightGold, // #FFAB00 — vivid amber
|
|
256
|
+
matrixColors.gray, // #4A7856 — dim green-gray
|
|
257
|
+
],
|
|
258
|
+
dark: [
|
|
259
|
+
matrixColors.greenPhosphor, // #00FF41 — Matrix green
|
|
260
|
+
matrixColors.greenGlow, // #39FF14 — neon phosphor
|
|
261
|
+
matrixColors.brightPop, // #00FF88 — phosphor cyan
|
|
262
|
+
matrixColors.brightSpark, // #CCFF00 — acid lime
|
|
263
|
+
matrixColors.brightSurge, // #00E5FF — electric blue
|
|
264
|
+
],
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Get a 5-colour avatar palette for the given theme variant and colour mode.
|
|
269
|
+
*
|
|
270
|
+
* Designed for use with the `boring-avatars` library's `colors` prop.
|
|
271
|
+
* When `colorMode` is `'auto'`, the OS preference is queried via
|
|
272
|
+
* `prefers-color-scheme` so avatars always match the effective background.
|
|
273
|
+
* Falls back to `datalayer` / `light` when values are missing.
|
|
274
|
+
*/
|
|
275
|
+
export function getAvatarColors(variant = 'datalayer', colorMode = 'light') {
|
|
276
|
+
let mode;
|
|
277
|
+
if (colorMode === 'auto') {
|
|
278
|
+
mode =
|
|
279
|
+
typeof window !== 'undefined' &&
|
|
280
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
281
|
+
? 'dark'
|
|
282
|
+
: 'light';
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
mode = colorMode;
|
|
286
|
+
}
|
|
287
|
+
return avatarColorPalettes[variant]?.[mode] ?? avatarColorPalettes.datalayer.light;
|
|
288
|
+
}
|
|
@@ -31,6 +31,10 @@ export interface ThemeColorDefs {
|
|
|
31
31
|
muted: string;
|
|
32
32
|
subtle?: string;
|
|
33
33
|
};
|
|
34
|
+
border?: {
|
|
35
|
+
default: string;
|
|
36
|
+
muted: string;
|
|
37
|
+
};
|
|
34
38
|
btn: {
|
|
35
39
|
text: string;
|
|
36
40
|
bg: string;
|
|
@@ -114,6 +118,10 @@ export interface ThemeColorDefs {
|
|
|
114
118
|
* | Outline button | `--button-outline-fgColor-rest` |
|
|
115
119
|
* | Danger button | `--button-danger-bgColor-hover` |
|
|
116
120
|
* | Button counters | `--buttonCounter-primary-bgColor-rest` |
|
|
121
|
+
* | Control track | `--controlTrack-bgColor-rest` |
|
|
122
|
+
* | Control checked | `--control-checked-bgColor-rest` |
|
|
123
|
+
* | Control knob | `--controlKnob-bgColor-rest` |
|
|
124
|
+
* | List hover | `--control-transparent-bgColor-hover` |
|
|
117
125
|
* | Legacy aliases | `--color-btn-primary-bg` |
|
|
118
126
|
* | Brand (custom) | `--brand-color-canvas-default` |
|
|
119
127
|
*/
|
|
@@ -127,5 +135,14 @@ export interface ThemeStyles {
|
|
|
127
135
|
* Build a complete `ThemeStyles` object from light / dark
|
|
128
136
|
* `ThemeColorDefs`. The result is ready to pass straight to
|
|
129
137
|
* `<DatalayerThemeProvider themeStyles={…}>`.
|
|
138
|
+
*
|
|
139
|
+
* @param options.fontFamily When supplied, the Primer font-stack
|
|
140
|
+
* CSS custom properties (`--fontStack-sansSerif`,
|
|
141
|
+
* `--fontStack-sansSerifDisplay`) are overridden so that every
|
|
142
|
+
* Primer component that uses the CSS `font` shorthand (e.g.
|
|
143
|
+
* `Blankslate`) picks up the themed font instead of the
|
|
144
|
+
* hard-coded system-font fallback.
|
|
130
145
|
*/
|
|
131
|
-
export declare function buildThemeStyles(light: ThemeColorDefs, dark: ThemeColorDefs
|
|
146
|
+
export declare function buildThemeStyles(light: ThemeColorDefs, dark: ThemeColorDefs, options?: {
|
|
147
|
+
fontFamily?: string;
|
|
148
|
+
}): ThemeStyles;
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
* | Outline button | `--button-outline-fgColor-rest` |
|
|
27
27
|
* | Danger button | `--button-danger-bgColor-hover` |
|
|
28
28
|
* | Button counters | `--buttonCounter-primary-bgColor-rest` |
|
|
29
|
+
* | Control track | `--controlTrack-bgColor-rest` |
|
|
30
|
+
* | Control checked | `--control-checked-bgColor-rest` |
|
|
31
|
+
* | Control knob | `--controlKnob-bgColor-rest` |
|
|
32
|
+
* | List hover | `--control-transparent-bgColor-hover` |
|
|
29
33
|
* | Legacy aliases | `--color-btn-primary-bg` |
|
|
30
34
|
* | Brand (custom) | `--brand-color-canvas-default` |
|
|
31
35
|
*/
|
|
@@ -34,12 +38,6 @@ export function colorDefsToCSS(defs) {
|
|
|
34
38
|
if (defs.canvas.subtle) {
|
|
35
39
|
optional['--bgColor-muted'] = defs.canvas.subtle;
|
|
36
40
|
}
|
|
37
|
-
if (defs.accent.subtle) {
|
|
38
|
-
optional['--bgColor-accent-subtle'] = defs.accent.subtle;
|
|
39
|
-
}
|
|
40
|
-
if (defs.success.subtle) {
|
|
41
|
-
optional['--bgColor-success-subtle'] = defs.success.subtle;
|
|
42
|
-
}
|
|
43
41
|
return {
|
|
44
42
|
/* ── Canvas / Background ─────────────────────────────────────── */
|
|
45
43
|
'--bgColor-default': defs.canvas.default,
|
|
@@ -54,18 +52,18 @@ export function colorDefsToCSS(defs) {
|
|
|
54
52
|
/* ── Accent ──────────────────────────────────────────────────── */
|
|
55
53
|
'--fgColor-accent': defs.accent.fg,
|
|
56
54
|
'--bgColor-accent-emphasis': defs.accent.emphasis,
|
|
57
|
-
'--bgColor-accent-muted': defs.accent.muted,
|
|
55
|
+
'--bgColor-accent-muted': defs.accent.subtle ?? defs.accent.muted,
|
|
58
56
|
'--borderColor-accent-emphasis': defs.accent.emphasis,
|
|
59
57
|
'--borderColor-accent-muted': defs.accent.muted,
|
|
60
58
|
/* ── Success ─────────────────────────────────────────────────── */
|
|
61
59
|
'--fgColor-success': defs.success.fg,
|
|
62
60
|
'--bgColor-success-emphasis': defs.success.emphasis,
|
|
63
|
-
'--bgColor-success-muted': defs.success.muted,
|
|
61
|
+
'--bgColor-success-muted': defs.success.subtle ?? defs.success.muted,
|
|
64
62
|
'--borderColor-success-emphasis': defs.success.emphasis,
|
|
65
63
|
'--borderColor-success-muted': defs.success.muted,
|
|
66
64
|
/* ── Border (generic) ────────────────────────────────────────── */
|
|
67
|
-
'--borderColor-default': defs.btn.border,
|
|
68
|
-
'--borderColor-muted': defs.btn.border,
|
|
65
|
+
'--borderColor-default': defs.border?.default ?? defs.btn.border,
|
|
66
|
+
'--borderColor-muted': defs.border?.muted ?? defs.btn.border,
|
|
69
67
|
/* ── Default button ──────────────────────────────────────────── */
|
|
70
68
|
'--button-default-fgColor-rest': defs.btn.text,
|
|
71
69
|
'--button-default-bgColor-rest': defs.btn.bg,
|
|
@@ -141,6 +139,35 @@ export function colorDefsToCSS(defs) {
|
|
|
141
139
|
'--button-invisible-iconColor-rest': defs.fg.muted,
|
|
142
140
|
'--button-invisible-iconColor-hover': defs.fg.default,
|
|
143
141
|
'--button-invisible-iconColor-disabled': defs.fg.muted,
|
|
142
|
+
/* ── Control track (unchecked toggle / switch) ────────────── */
|
|
143
|
+
'--controlTrack-bgColor-rest': defs.border?.muted ?? defs.btn.border,
|
|
144
|
+
'--controlTrack-bgColor-hover': defs.border?.default ?? defs.btn.border,
|
|
145
|
+
'--controlTrack-bgColor-active': defs.border?.default ?? defs.btn.hoverBorder,
|
|
146
|
+
'--controlTrack-bgColor-disabled': defs.fg.muted,
|
|
147
|
+
'--controlTrack-fgColor-rest': defs.fg.muted,
|
|
148
|
+
'--controlTrack-fgColor-disabled': defs.fg.onEmphasis,
|
|
149
|
+
'--controlTrack-borderColor-rest': 'transparent',
|
|
150
|
+
/* ── Control checked (checked toggle / switch track) ──────── */
|
|
151
|
+
'--control-checked-bgColor-rest': defs.accent.emphasis,
|
|
152
|
+
'--control-checked-bgColor-hover': defs.btn.primary.hoverBg,
|
|
153
|
+
'--control-checked-bgColor-active': defs.btn.primary.selectedBg,
|
|
154
|
+
'--control-checked-fgColor-rest': defs.btn.primary.text,
|
|
155
|
+
'--control-checked-fgColor-disabled': defs.btn.primary.text,
|
|
156
|
+
'--control-checked-borderColor-rest': 'transparent',
|
|
157
|
+
/* ── Control knob (toggle switch handle) ──────────────────── */
|
|
158
|
+
'--controlKnob-bgColor-rest': defs.canvas.default,
|
|
159
|
+
'--controlKnob-bgColor-checked': defs.canvas.default,
|
|
160
|
+
'--controlKnob-bgColor-disabled': defs.canvas.subtle ?? defs.canvas.default,
|
|
161
|
+
'--controlKnob-borderColor-rest': defs.border?.default ?? defs.btn.border,
|
|
162
|
+
'--controlKnob-borderColor-checked': defs.accent.emphasis,
|
|
163
|
+
/* ── Control transparent (action list / nav list hover) ───── */
|
|
164
|
+
'--control-transparent-bgColor-hover': defs.btn.hoverBg,
|
|
165
|
+
'--control-transparent-bgColor-active': defs.btn.activeBg,
|
|
166
|
+
'--control-transparent-bgColor-selected': defs.btn.selectedBg,
|
|
167
|
+
'--control-transparent-borderColor-hover': 'transparent',
|
|
168
|
+
'--control-transparent-borderColor-active': 'transparent',
|
|
169
|
+
/* ── Menu ────────────────────────────────────────────────────── */
|
|
170
|
+
'--menu-bgColor-active': defs.btn.hoverBg,
|
|
144
171
|
/* ── Legacy / compat aliases ─────────────────────────────────── */
|
|
145
172
|
'--color-btn-primary-bg': defs.btn.primary.bg,
|
|
146
173
|
'--color-btn-primary-hover-bg': defs.btn.primary.hoverBg,
|
|
@@ -153,20 +180,54 @@ export function colorDefsToCSS(defs) {
|
|
|
153
180
|
* Build a complete `ThemeStyles` object from light / dark
|
|
154
181
|
* `ThemeColorDefs`. The result is ready to pass straight to
|
|
155
182
|
* `<DatalayerThemeProvider themeStyles={…}>`.
|
|
183
|
+
*
|
|
184
|
+
* @param options.fontFamily When supplied, the Primer font-stack
|
|
185
|
+
* CSS custom properties (`--fontStack-sansSerif`,
|
|
186
|
+
* `--fontStack-sansSerifDisplay`) are overridden so that every
|
|
187
|
+
* Primer component that uses the CSS `font` shorthand (e.g.
|
|
188
|
+
* `Blankslate`) picks up the themed font instead of the
|
|
189
|
+
* hard-coded system-font fallback.
|
|
156
190
|
*/
|
|
157
|
-
export function buildThemeStyles(light, dark) {
|
|
191
|
+
export function buildThemeStyles(light, dark, options) {
|
|
192
|
+
const fontVars = {};
|
|
193
|
+
if (options?.fontFamily) {
|
|
194
|
+
const f = options.fontFamily;
|
|
195
|
+
// Override the font-stack custom properties so that any consumer
|
|
196
|
+
// referencing `var(--fontStack-sansSerif)` picks up the theme font.
|
|
197
|
+
fontVars['--fontStack-sansSerif'] = f;
|
|
198
|
+
fontVars['--fontStack-sansSerifDisplay'] = f;
|
|
199
|
+
// Also override every font-shorthand token directly. Primer
|
|
200
|
+
// components like Blankslate use
|
|
201
|
+
// `font: var(--text-title-shorthand-medium, <hardcoded>)`
|
|
202
|
+
// and relying on nested `var()` resolution inside the CSS `font`
|
|
203
|
+
// shorthand is unreliable across browsers. Inlining the font
|
|
204
|
+
// family here guarantees the themed typeface is applied.
|
|
205
|
+
fontVars['--text-body-shorthand-large'] = `400 1rem/1.5 ${f}`;
|
|
206
|
+
fontVars['--text-body-shorthand-medium'] = `400 0.875rem/1.4285 ${f}`;
|
|
207
|
+
fontVars['--text-body-shorthand-small'] = `400 0.75rem/1.6666 ${f}`;
|
|
208
|
+
fontVars['--text-title-shorthand-large'] = `600 2rem/1.5 ${f}`;
|
|
209
|
+
fontVars['--text-title-shorthand-medium'] = `600 1.25rem/1.6 ${f}`;
|
|
210
|
+
fontVars['--text-title-shorthand-small'] = `600 1rem/1.5 ${f}`;
|
|
211
|
+
fontVars['--text-caption-shorthand'] = `400 0.75rem/1.3333 ${f}`;
|
|
212
|
+
fontVars['--text-subtitle-shorthand'] = `400 1.25rem/1.6 ${f}`;
|
|
213
|
+
fontVars['--text-display-shorthand'] = `500 2.5rem/1.4 ${f}`;
|
|
214
|
+
}
|
|
158
215
|
return {
|
|
159
216
|
light: {
|
|
160
217
|
backgroundColor: light.canvas.default,
|
|
161
218
|
color: light.fg.default,
|
|
162
219
|
fontSize: 'var(--text-body-size-medium)',
|
|
220
|
+
...(options?.fontFamily ? { fontFamily: options.fontFamily } : {}),
|
|
163
221
|
...colorDefsToCSS(light),
|
|
222
|
+
...fontVars,
|
|
164
223
|
},
|
|
165
224
|
dark: {
|
|
166
225
|
backgroundColor: dark.canvas.default,
|
|
167
226
|
color: dark.fg.default,
|
|
168
227
|
fontSize: 'var(--text-body-size-medium)',
|
|
228
|
+
...(options?.fontFamily ? { fontFamily: options.fontFamily } : {}),
|
|
169
229
|
...colorDefsToCSS(dark),
|
|
230
|
+
...fontVars,
|
|
170
231
|
},
|
|
171
232
|
};
|
|
172
233
|
}
|
|
@@ -27,11 +27,13 @@ const matrixLight = {
|
|
|
27
27
|
fg: matrixColors.greenText,
|
|
28
28
|
emphasis: matrixColors.greenBrand,
|
|
29
29
|
muted: matrixColors.greenAccent,
|
|
30
|
+
subtle: matrixColors.greenTint,
|
|
30
31
|
},
|
|
31
32
|
success: {
|
|
32
33
|
fg: matrixColors.greenText,
|
|
33
34
|
emphasis: matrixColors.greenBrand,
|
|
34
35
|
muted: matrixColors.greenAccent,
|
|
36
|
+
subtle: matrixColors.greenTint,
|
|
35
37
|
},
|
|
36
38
|
btn: {
|
|
37
39
|
text: '#0A2E1A',
|
|
@@ -185,6 +187,12 @@ const matrixDark = {
|
|
|
185
187
|
* default Primer theme kept for backward compatibility.
|
|
186
188
|
*/
|
|
187
189
|
export const matrixTheme = primerTheme;
|
|
190
|
+
/**
|
|
191
|
+
* Monospace font stack for the Matrix terminal aesthetic.
|
|
192
|
+
* Uses fonts pre-installed across macOS / Windows / Linux so no
|
|
193
|
+
* web-font loading is required.
|
|
194
|
+
*/
|
|
195
|
+
const matrixFontFamily = '"SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
188
196
|
/** Comprehensive Primer CSS-variable overrides for light & dark mode. */
|
|
189
|
-
export const matrixThemeStyles = buildThemeStyles(matrixLight, matrixDark);
|
|
197
|
+
export const matrixThemeStyles = buildThemeStyles(matrixLight, matrixDark, { fontFamily: matrixFontFamily });
|
|
190
198
|
export default matrixTheme;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zustand store for theme preferences – reusable across Datalayer apps.
|
|
3
|
+
*
|
|
4
|
+
* Persists `colorMode` and `theme` to localStorage. The store key is
|
|
5
|
+
* configurable so that different apps can maintain independent prefs.
|
|
6
|
+
*
|
|
7
|
+
* @module theme/useThemeStore
|
|
8
|
+
*/
|
|
9
|
+
import { type StoreApi, type UseBoundStore } from 'zustand';
|
|
10
|
+
import type { ThemeVariant } from './themeRegistry';
|
|
11
|
+
import type { ColorMode } from './DatalayerBrandThemeProvider';
|
|
12
|
+
export type { ThemeVariant, ColorMode };
|
|
13
|
+
export interface ThemeState {
|
|
14
|
+
/** Current color mode (light, dark, or auto = follow OS). */
|
|
15
|
+
colorMode: ColorMode;
|
|
16
|
+
/** Current theme variant. */
|
|
17
|
+
theme: ThemeVariant;
|
|
18
|
+
/** Cycle through light → dark → auto. */
|
|
19
|
+
toggleColorMode: () => void;
|
|
20
|
+
/** Set a specific color mode. */
|
|
21
|
+
setColorMode: (mode: ColorMode) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Set the active theme variant.
|
|
24
|
+
* @param applyDefaultColorMode When true (default), also switches the
|
|
25
|
+
* color mode to the theme's configured default.
|
|
26
|
+
*/
|
|
27
|
+
setTheme: (theme: ThemeVariant, applyDefaultColorMode?: boolean) => void;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a theme store bound to a specific localStorage key.
|
|
31
|
+
*
|
|
32
|
+
* Usage:
|
|
33
|
+
* ```ts
|
|
34
|
+
* export const useMyAppThemeStore = createThemeStore('my-app-theme');
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @param storageKey localStorage key for persistence (e.g. `'otel-example-theme'`)
|
|
38
|
+
* @param defaults Optional overrides for the initial colorMode / theme
|
|
39
|
+
*/
|
|
40
|
+
export declare function createThemeStore(storageKey: string, defaults?: {
|
|
41
|
+
colorMode?: ColorMode;
|
|
42
|
+
theme?: ThemeVariant;
|
|
43
|
+
}): UseBoundStore<StoreApi<ThemeState>>;
|
|
44
|
+
/**
|
|
45
|
+
* Default theme store for Datalayer applications.
|
|
46
|
+
*
|
|
47
|
+
* Persists to `localStorage` under the key `'datalayer-theme'`.
|
|
48
|
+
* Use `createThemeStore` if you need an app-specific key or defaults.
|
|
49
|
+
*/
|
|
50
|
+
export declare const useThemeStore: UseBoundStore<StoreApi<ThemeState>>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Zustand store for theme preferences – reusable across Datalayer apps.
|
|
7
|
+
*
|
|
8
|
+
* Persists `colorMode` and `theme` to localStorage. The store key is
|
|
9
|
+
* configurable so that different apps can maintain independent prefs.
|
|
10
|
+
*
|
|
11
|
+
* @module theme/useThemeStore
|
|
12
|
+
*/
|
|
13
|
+
import { create } from 'zustand';
|
|
14
|
+
import { persist, createJSONStorage } from 'zustand/middleware';
|
|
15
|
+
import { themeConfigs } from './themeRegistry';
|
|
16
|
+
/**
|
|
17
|
+
* Create a theme store bound to a specific localStorage key.
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```ts
|
|
21
|
+
* export const useMyAppThemeStore = createThemeStore('my-app-theme');
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* @param storageKey localStorage key for persistence (e.g. `'otel-example-theme'`)
|
|
25
|
+
* @param defaults Optional overrides for the initial colorMode / theme
|
|
26
|
+
*/
|
|
27
|
+
export function createThemeStore(storageKey, defaults) {
|
|
28
|
+
return create()(persist(set => ({
|
|
29
|
+
colorMode: defaults?.colorMode ?? 'dark',
|
|
30
|
+
theme: defaults?.theme ?? 'matrix',
|
|
31
|
+
toggleColorMode: () => set(state => {
|
|
32
|
+
const cycle = {
|
|
33
|
+
light: 'dark',
|
|
34
|
+
dark: 'auto',
|
|
35
|
+
auto: 'light',
|
|
36
|
+
};
|
|
37
|
+
return { colorMode: cycle[state.colorMode] };
|
|
38
|
+
}),
|
|
39
|
+
setColorMode: (mode) => set({ colorMode: mode }),
|
|
40
|
+
setTheme: (theme, applyDefaultColorMode = true) => set(() => {
|
|
41
|
+
const next = { theme };
|
|
42
|
+
if (applyDefaultColorMode) {
|
|
43
|
+
next.colorMode = themeConfigs[theme].defaultColorMode;
|
|
44
|
+
}
|
|
45
|
+
return next;
|
|
46
|
+
}),
|
|
47
|
+
}), {
|
|
48
|
+
name: storageKey,
|
|
49
|
+
storage: createJSONStorage(() => localStorage),
|
|
50
|
+
partialize: state => ({
|
|
51
|
+
colorMode: state.colorMode,
|
|
52
|
+
theme: state.theme,
|
|
53
|
+
}),
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Default theme store for Datalayer applications.
|
|
58
|
+
*
|
|
59
|
+
* Persists to `localStorage` under the key `'datalayer-theme'`.
|
|
60
|
+
* Use `createThemeStore` if you need an app-specific key or defaults.
|
|
61
|
+
*/
|
|
62
|
+
export const useThemeStore = createThemeStore('datalayer-theme', {
|
|
63
|
+
colorMode: 'dark',
|
|
64
|
+
theme: 'matrix',
|
|
65
|
+
});
|
package/lib/utils/Portals.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
1
2
|
import '@primer/react-brand/lib/css/main.css';
|
|
2
3
|
type Colormode = 'light' | 'dark' | 'auto';
|
|
3
4
|
/**
|
|
@@ -7,4 +8,18 @@ type Colormode = 'light' | 'dark' | 'auto';
|
|
|
7
8
|
* @see https://github.com/primer/react/blob/030fe020b48b7f12c2994c6614e5d4191fe764ee/src/Portal/Portal.tsx#L33
|
|
8
9
|
*/
|
|
9
10
|
export declare const setupPrimerPortals: (colormode?: Colormode) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Sync a set of CSS properties (including CSS custom properties) to
|
|
13
|
+
* `document.body` so that Primer portal content — which is a DOM child
|
|
14
|
+
* of `<body>`, not of the React-tree `<BaseStyles>` element — inherits
|
|
15
|
+
* the same theme tokens and cascade properties (font, color, background)
|
|
16
|
+
* as the rest of the application.
|
|
17
|
+
*
|
|
18
|
+
* This function is **idempotent**: calling it again replaces the
|
|
19
|
+
* properties from the previous call and removes any stale ones.
|
|
20
|
+
*
|
|
21
|
+
* Typical usage: call from `DatalayerThemeProvider`'s `useEffect` so
|
|
22
|
+
* portals stay in sync whenever theme or color-mode changes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function syncPortalThemeStyles(styles: CSSProperties): void;
|
|
10
25
|
export default setupPrimerPortals;
|