@timmo001/groundwork-tokens 0.9.37 → 0.9.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-tokens",
3
- "version": "0.9.37",
3
+ "version": "0.9.39",
4
4
  "description": "Groundwork's design tokens: the theme schema, the OKLCH solver and every theme as CSS and JSON",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -10,6 +10,8 @@
10
10
  "./page": "./src/page.ts",
11
11
  "./webmcp": "./src/webmcp.ts",
12
12
  "./omarchy-current": "./src/omarchy-current.ts",
13
+ "./desktop-themes": "./src/desktop-themes.ts",
14
+ "./translate": "./src/translate.ts",
13
15
  "./opentui": "./generated/opentui.ts",
14
16
  "./themes.css": "./generated/themes.css",
15
17
  "./themes.json": "./generated/themes.json",
@@ -0,0 +1,253 @@
1
+ /**
2
+ * The color themes a machine has, translated into mapped themes so an app can
3
+ * offer them beside its own: every installed Omarchy theme and the theme
4
+ * each terminal (Ghostty, kitty, Alacritty and foot) uses. It needs a file system, so it's an entry
5
+ * of its own; drop the ones an app already offers with `distinctThemes`.
6
+ *
7
+ * @module
8
+ */
9
+ import type { Dirent } from "node:fs";
10
+ import { readdir, readFile } from "node:fs/promises";
11
+ import { homedir } from "node:os";
12
+ import { dirname, isAbsolute, join, resolve } from "node:path";
13
+
14
+ import { omarchyTheme } from "./omarchy.ts";
15
+ import type { MappedTheme } from "./schema.ts";
16
+ import {
17
+ alacrittyPalette,
18
+ colorsTomlPalette,
19
+ distinctThemes,
20
+ footPalette,
21
+ ghosttyPalette,
22
+ kittyPalette,
23
+ translatePalette,
24
+ type TerminalPalette,
25
+ } from "./translate.ts";
26
+
27
+ export { distinctThemes, sameTheme, translatePalette } from "./translate.ts";
28
+
29
+ /** Where a desktop theme came from, such as `Omarchy` or `Ghostty`. */
30
+ export type DesktopThemeSource =
31
+ "Omarchy" | "Ghostty" | "kitty" | "Alacritty" | "foot";
32
+
33
+ const home = homedir();
34
+
35
+ const config = process.env.XDG_CONFIG_HOME || join(home, ".config");
36
+ const data = process.env.XDG_DATA_HOME || join(home, ".local", "share");
37
+
38
+ const expand = (path: string, from: string) => {
39
+ const unquoted = path
40
+ .trim()
41
+ .replace(/^\?/, "")
42
+ .replace(/^["']|["']$/g, "");
43
+
44
+ const tilde = unquoted.replace(/^~(?=\/|$)/, home);
45
+
46
+ return isAbsolute(tilde) ? tilde : resolve(from, tilde);
47
+ };
48
+
49
+ const text = (path: string) => readFile(path, "utf8").catch(() => undefined);
50
+
51
+ const folders = (path: string) =>
52
+ readdir(path, { withFileTypes: true }).catch((): Dirent[] => []);
53
+
54
+ const slug = (value: string) =>
55
+ value
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9]+/g, "-")
58
+ .replace(/^-|-$/g, "");
59
+
60
+ // `tokyo-night` reads as `Tokyo Night`
61
+ const titled = (value: string) =>
62
+ value
63
+ .split(/[-_\s]+/)
64
+ .filter((word) => word !== "")
65
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
66
+ .join(" ");
67
+
68
+ const named = (source: DesktopThemeSource, name: string) => ({
69
+ id: `desktop-${slug(source)}-${slug(name)}`,
70
+ name: `${name} (${source})`,
71
+ family: source,
72
+ });
73
+
74
+ /** Every Omarchy theme installed, the shipped ones and the user's own. */
75
+ const omarchyThemes = async () => {
76
+ const found: MappedTheme[] = [];
77
+
78
+ for (const root of [
79
+ join(data, "omarchy", "themes"),
80
+ join(config, "omarchy", "themes"),
81
+ ]) {
82
+ for (const entry of await folders(root)) {
83
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
84
+
85
+ const folder = join(root, entry.name);
86
+ const theme = named("Omarchy", titled(entry.name));
87
+ const colors = await text(join(folder, "colors.toml"));
88
+
89
+ const parsed =
90
+ colors === undefined
91
+ ? undefined
92
+ : omarchyTheme(colors, theme.name, theme.id);
93
+
94
+ if (parsed?._tag === "Success") {
95
+ found.push(parsed.success);
96
+ continue;
97
+ }
98
+
99
+ // Older themes number their colors, or only have each app's file
100
+ for (const [file, palette] of [
101
+ ["colors.toml", colorsTomlPalette],
102
+ ["alacritty.toml", alacrittyPalette],
103
+ ["ghostty.conf", ghosttyPalette],
104
+ ["kitty.conf", kittyPalette],
105
+ ] as const) {
106
+ const read = await text(join(folder, file));
107
+
108
+ const translated =
109
+ read === undefined
110
+ ? undefined
111
+ : translatePalette(palette(read), theme);
112
+
113
+ if (translated !== undefined) {
114
+ found.push(translated);
115
+ break;
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ return found;
122
+ };
123
+
124
+ /**
125
+ * A terminal config with the files it includes, one level deep, joined in
126
+ * order so later colors win, as the terminal reads them.
127
+ */
128
+ const withIncludes = async (
129
+ path: string,
130
+ include: RegExp,
131
+ extra: (text: string, from: string) => readonly string[] = () => [],
132
+ ) => {
133
+ const main = await text(path);
134
+
135
+ if (main === undefined) return undefined;
136
+
137
+ const from = dirname(path);
138
+ const parts: string[] = [];
139
+
140
+ for (const line of main.split(/\r?\n/)) {
141
+ const match = include.exec(line);
142
+
143
+ if (match?.[1] !== undefined) {
144
+ for (const file of match[1].split(/,\s*/)) {
145
+ const included = await text(expand(file.replace(/[[\]]/g, ""), from));
146
+
147
+ if (included !== undefined) parts.push(included);
148
+ }
149
+ }
150
+ }
151
+
152
+ for (const file of extra(main, from)) {
153
+ const included = await text(file);
154
+
155
+ if (included !== undefined) parts.push(included);
156
+ }
157
+
158
+ return [...parts, main].join("\n");
159
+ };
160
+
161
+ /** Ghostty's theme: its config, the files it pulls in, and a named `theme`. */
162
+ const ghosttyConfig = () =>
163
+ withIncludes(
164
+ join(config, "ghostty", "config"),
165
+ /^\s*config-file\s*=\s*(.+)$/,
166
+ (main) => {
167
+ const theme = /^\s*theme\s*=\s*(.+)$/m.exec(main)?.[1]?.trim();
168
+
169
+ if (theme === undefined) return [];
170
+
171
+ // `light:A,dark:B` takes the dark one
172
+ const name =
173
+ /dark:([^,]+)/.exec(theme)?.[1]?.trim() ??
174
+ theme
175
+ .replace(/^light:/, "")
176
+ .split(",")[0]
177
+ ?.trim() ??
178
+ theme;
179
+
180
+ return [
181
+ join(config, "ghostty", "themes", name),
182
+ join("/usr/share/ghostty/themes", name),
183
+ ];
184
+ },
185
+ );
186
+
187
+ const terminals: readonly {
188
+ readonly source: DesktopThemeSource;
189
+ readonly read: () => Promise<string | undefined>;
190
+ readonly palette: (text: string) => TerminalPalette;
191
+ }[] = [
192
+ { source: "Ghostty", read: ghosttyConfig, palette: ghosttyPalette },
193
+ {
194
+ source: "kitty",
195
+ read: () =>
196
+ withIncludes(
197
+ join(config, "kitty", "kitty.conf"),
198
+ /^\s*(?:include|globinclude)\s+(.+)$/,
199
+ ),
200
+ palette: kittyPalette,
201
+ },
202
+ {
203
+ source: "Alacritty",
204
+ read: () =>
205
+ withIncludes(
206
+ join(config, "alacritty", "alacritty.toml"),
207
+ /^\s*(?:general\.)?import\s*=\s*\[(.+)\]/,
208
+ ),
209
+ palette: alacrittyPalette,
210
+ },
211
+ {
212
+ source: "foot",
213
+ read: () =>
214
+ withIncludes(join(config, "foot", "foot.ini"), /^\s*include\s*=\s*(.+)$/),
215
+ palette: footPalette,
216
+ },
217
+ ];
218
+
219
+ /** The theme each terminal uses. */
220
+ const terminalThemes = async () => {
221
+ const found: MappedTheme[] = [];
222
+
223
+ for (const terminal of terminals) {
224
+ const read = await terminal.read();
225
+
226
+ if (read === undefined) continue;
227
+
228
+ const translated = translatePalette(
229
+ terminal.palette(read),
230
+ named(terminal.source, terminal.source),
231
+ );
232
+
233
+ if (translated !== undefined) found.push(translated);
234
+ }
235
+
236
+ return found;
237
+ };
238
+
239
+ /**
240
+ * Every theme on the machine, translated: the terminals' first, as the ones
241
+ * in use, then Omarchy's. Themes that look the same are kept once; pass the
242
+ * app's own as `existing` to drop those too.
243
+ */
244
+ export const readDesktopThemes = async (
245
+ existing: readonly Pick<MappedTheme, "name" | "palette">[] = [],
246
+ ): Promise<readonly MappedTheme[]> => {
247
+ const [terminal, omarchy] = await Promise.all([
248
+ terminalThemes(),
249
+ omarchyThemes(),
250
+ ]);
251
+
252
+ return distinctThemes([...terminal, ...omarchy], existing);
253
+ };
package/src/generate.ts CHANGED
@@ -756,12 +756,12 @@ export const optionalPage = (themes: readonly ResolvedTheme[]) => {
756
756
  return [
757
757
  "---",
758
758
  "title: Optional themes",
759
- "description: Catppuccin and Tokyo Night, mapped onto the roles so they stay faithful to their palettes. Apps opt in.",
759
+ "description: Catppuccin, Tokyo Night and GitHub, mapped onto the roles so they stay faithful to their palettes. Apps opt in.",
760
760
  "---",
761
761
  "",
762
762
  `{/* ${header} */}`,
763
763
  "",
764
- "Each flavor is one mode of its palette, mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
764
+ "Most flavors are one mode of their palette, and GitHub's cover both light and dark. Each is mapped straight onto the roles rather than solved, so it looks the way the palette's authors intended. They take Base's style. The [contrast report](/themes/contrast) shows where a palette sits below the core targets.",
765
765
  "",
766
766
  ...families.flatMap((family) => [
767
767
  `## ${family}`,
package/src/page.ts CHANGED
@@ -11,10 +11,21 @@
11
11
  */
12
12
  import base from "../themes/base.json" with { type: "json" };
13
13
  import bridge from "../themes/bridge.json" with { type: "json" };
14
+ import catppuccinFrappe from "../themes/catppuccin-frappe.json" with { type: "json" };
15
+ import catppuccinLatte from "../themes/catppuccin-latte.json" with { type: "json" };
16
+ import catppuccinMacchiato from "../themes/catppuccin-macchiato.json" with { type: "json" };
17
+ import catppuccinMocha from "../themes/catppuccin-mocha.json" with { type: "json" };
18
+ import githubDimmed from "../themes/github-dimmed.json" with { type: "json" };
19
+ import githubHighContrast from "../themes/github-high-contrast.json" with { type: "json" };
20
+ import github from "../themes/github.json" with { type: "json" };
14
21
  import glazing from "../themes/glazing.json" with { type: "json" };
15
22
  import slate from "../themes/slate.json" with { type: "json" };
16
23
  import snag from "../themes/snag.json" with { type: "json" };
17
24
  import survey from "../themes/survey.json" with { type: "json" };
25
+ import tokyoNightDay from "../themes/tokyo-night-day.json" with { type: "json" };
26
+ import tokyoNightMoon from "../themes/tokyo-night-moon.json" with { type: "json" };
27
+ import tokyoNightStorm from "../themes/tokyo-night-storm.json" with { type: "json" };
28
+ import tokyoNight from "../themes/tokyo-night.json" with { type: "json" };
18
29
  import type { OmarchyCurrentTheme } from "./omarchy.ts";
19
30
  import type { ThemeStore } from "./runtime.ts";
20
31
  import type { Theme } from "./schema.ts";
@@ -71,6 +82,8 @@ export interface ThemeChoice {
71
82
  readonly id: string;
72
83
  readonly name: string;
73
84
  readonly mode?: ThemeScheme | undefined;
85
+ /** The palette an optional theme comes from, such as `Catppuccin`. */
86
+ readonly family?: string | undefined;
74
87
  }
75
88
 
76
89
  /** Whether `choice` can show in `scheme`. */
@@ -87,6 +100,43 @@ export const coreThemeChoices: readonly ThemeChoice[] = [
87
100
  snag,
88
101
  ].map(({ id, name }) => ({ id, name }));
89
102
 
103
+ /**
104
+ * The optional themes' documents, by family and then from light to dark.
105
+ * They're bundled with the module, so an app compiled to one binary has them
106
+ * without reading `node_modules`. Decode one with the `MappedTheme` Schema.
107
+ */
108
+ export const optionalThemeDocuments = [
109
+ catppuccinLatte,
110
+ catppuccinFrappe,
111
+ catppuccinMacchiato,
112
+ catppuccinMocha,
113
+ tokyoNightDay,
114
+ tokyoNight,
115
+ tokyoNightStorm,
116
+ tokyoNightMoon,
117
+ github,
118
+ githubDimmed,
119
+ githubHighContrast,
120
+ ] as const;
121
+
122
+ /**
123
+ * The optional themes, mapped from other palettes, by family and then from
124
+ * light to dark. One with a single mode is offered for that scheme alone;
125
+ * one with both, such as GitHub's, for either.
126
+ */
127
+ export const optionalThemeChoices: readonly ThemeChoice[] =
128
+ optionalThemeDocuments.map((theme) => ({
129
+ id: theme.id,
130
+ name: theme.name,
131
+ family: theme.family,
132
+ mode:
133
+ "otherPalette" in theme
134
+ ? undefined
135
+ : theme.mode === "light"
136
+ ? "light"
137
+ : "dark",
138
+ }));
139
+
90
140
  export interface PageThemeOptions {
91
141
  /** The local storage key prefix, such as `my-site:theme`. */
92
142
  readonly key: string;
@@ -101,6 +151,12 @@ export interface PageThemeOptions {
101
151
  readonly defaultTheme: string;
102
152
  /** The mode to start with. Defaults to `auto`. */
103
153
  readonly defaultMode?: ThemeMode;
154
+ /**
155
+ * Whether the Omarchy theme takes over while the visitor hasn't picked
156
+ * one. With `false` it's only offered, and `defaultTheme` stays in use.
157
+ * Defaults to `true`.
158
+ */
159
+ readonly followOmarchy?: boolean;
104
160
  /** The element the attributes go on. Defaults to `<html>`. */
105
161
  readonly root?: HTMLElement;
106
162
  /**
@@ -178,21 +234,26 @@ const write = (key: string, value: string | null) => {
178
234
  * defaults as `pageTheme`.
179
235
  */
180
236
  export const themeBootScript = (
181
- options: Pick<PageThemeOptions, "key" | "defaultTheme" | "defaultMode">,
237
+ options: Pick<
238
+ PageThemeOptions,
239
+ "key" | "defaultTheme" | "defaultMode" | "followOmarchy"
240
+ >,
182
241
  ) => {
183
242
  const config = JSON.stringify({
184
243
  keys: keys(options.key),
185
244
  omarchy: omarchyKeys(options.key),
186
245
  theme: options.defaultTheme,
187
246
  mode: options.defaultMode ?? "auto",
247
+ follow: options.followOmarchy !== false,
188
248
  custom: customThemeId,
189
249
  style: customThemeStyleId,
190
250
  omarchyId: omarchyThemeId,
191
251
  omarchyStyle: omarchyThemeStyleId,
192
252
  });
193
253
 
194
- // With nothing picked, a cached Omarchy theme for the scheme is the default
195
- return `(()=>{const c=${config};let s;try{s=localStorage}catch{}const g=(k)=>{try{return s?.getItem(k)??null}catch{return null}};const r=document.documentElement;const a=(id,css)=>{const e=document.createElement("style");e.id=id;e.textContent=css;document.head.append(e)};const m=g(c.keys.mode)??c.mode;const d=m==="dark"||m==="darker"||(m==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches);const o=g(c.omarchy.css);let t=(d?g(c.keys.dark):null)??g(c.keys.theme)??(o&&g(c.omarchy.mode)===(d?"dark":"light")?c.omarchyId:c.theme);if(t===c.custom){const css=g(c.keys.css);if(css)a(c.style,css);else t=c.theme}else if(t===c.omarchyId){if(o)a(c.omarchyStyle,o);else t=c.theme}r.setAttribute("data-gw-theme",t);if(m==="auto")r.removeAttribute("data-gw-mode");else r.setAttribute("data-gw-mode",m)})();`;
254
+ // With nothing picked, a cached Omarchy theme for the scheme is the
255
+ // default, unless it's only offered
256
+ return `(()=>{const c=${config};let s;try{s=localStorage}catch{}const g=(k)=>{try{return s?.getItem(k)??null}catch{return null}};const r=document.documentElement;const a=(id,css)=>{const e=document.createElement("style");e.id=id;e.textContent=css;document.head.append(e)};const m=g(c.keys.mode)??c.mode;const d=m==="dark"||m==="darker"||(m==="auto"&&matchMedia("(prefers-color-scheme: dark)").matches);const o=g(c.omarchy.css);let t=(d?g(c.keys.dark):null)??g(c.keys.theme)??(c.follow&&o&&g(c.omarchy.mode)===(d?"dark":"light")?c.omarchyId:c.theme);if(t===c.custom){const css=g(c.keys.css);if(css)a(c.style,css);else t=c.theme}else if(t===c.omarchyId){if(o)a(c.omarchyStyle,o);else t=c.theme}r.setAttribute("data-gw-theme",t);if(m==="auto")r.removeAttribute("data-gw-mode");else r.setAttribute("data-gw-mode",m)})();`;
196
257
  };
197
258
 
198
259
  /** Reads a theme from JSON text or an Omarchy `colors.toml`. */
@@ -284,9 +345,12 @@ export const pageTheme = (options: PageThemeOptions): PageTheme => {
284
345
  choices().some((theme) => theme.id === id && themeCovers(theme, scheme)));
285
346
 
286
347
  // The Omarchy theme comes first where it covers the scheme, so the app
287
- // matches the desktop until the visitor picks another
348
+ // matches the desktop until the visitor picks another, unless it's only
349
+ // offered
288
350
  const fallback = (scheme: ThemeScheme) =>
289
- omarchy !== null && themeCovers(omarchy, scheme)
351
+ options.followOmarchy !== false &&
352
+ omarchy !== null &&
353
+ themeCovers(omarchy, scheme)
290
354
  ? omarchy.id
291
355
  : options.themes.some(
292
356
  (theme) =>
@@ -437,7 +501,9 @@ export const pageTheme = (options: PageThemeOptions): PageTheme => {
437
501
  read(stored.dark),
438
502
  ].includes(omarchyThemeId);
439
503
 
440
- const unpicked = read(stored.theme) === null;
504
+ const unpicked =
505
+ options.followOmarchy !== false && read(stored.theme) === null;
506
+
441
507
  let mode: ThemeScheme | undefined;
442
508
 
443
509
  if (theme !== null) {
package/src/schema.ts CHANGED
@@ -108,15 +108,19 @@ export interface Palette extends Schema.Schema.Type<typeof Palette> {}
108
108
 
109
109
  /**
110
110
  * An optional theme mapped from an existing palette, such as Catppuccin, so it
111
- * stays faithful. Each flavor is one mode, and it takes Base's style.
111
+ * stays faithful. It takes Base's style. Most flavors are one mode; one that
112
+ * comes in both, such as GitHub's, also gives the other mode's palette.
112
113
  */
113
114
  export const MappedTheme = Schema.Struct({
114
115
  kind: Schema.tag("mapped"),
115
116
  id: Schema.String,
116
117
  name: Schema.String,
117
118
  family: Schema.String,
119
+ /** The mode `palette` is for. */
118
120
  mode: Schema.Literals(["light", "dark"]),
119
121
  palette: Palette,
122
+ /** The other mode's palette, for a theme that covers both. */
123
+ otherPalette: Schema.optionalKey(Palette),
120
124
  });
121
125
 
122
126
  export interface MappedTheme extends Schema.Schema.Type<typeof MappedTheme> {}
package/src/solve.ts CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  withLightness,
15
15
  type Oklch,
16
16
  } from "./color.ts";
17
- import type { MappedTheme, SolvedTheme, Style, Theme } from "./schema.ts";
17
+ import type { Palette, SolvedTheme, Style, Theme } from "./schema.ts";
18
18
 
19
19
  export const statuses = [
20
20
  "info",
@@ -261,18 +261,17 @@ const darker = (dark: Roles, by = 0.045) => {
261
261
  };
262
262
  };
263
263
 
264
- const mappedRoles = (theme: MappedTheme) => {
264
+ const mappedRoles = (source: Palette, dark: boolean) => {
265
265
  const palette = {
266
- lower: fromHex(theme.palette.lower),
267
- background: fromHex(theme.palette.background),
268
- raised: fromHex(theme.palette.raised),
269
- border: fromHex(theme.palette.border),
270
- text: fromHex(theme.palette.text),
271
- muted: fromHex(theme.palette.muted),
272
- accent: fromHex(theme.palette.accent),
266
+ lower: fromHex(source.lower),
267
+ background: fromHex(source.background),
268
+ raised: fromHex(source.raised),
269
+ border: fromHex(source.border),
270
+ text: fromHex(source.text),
271
+ muted: fromHex(source.muted),
272
+ accent: fromHex(source.accent),
273
273
  };
274
274
 
275
- const dark = theme.mode === "dark";
276
275
  const away = dark ? 1 : -1;
277
276
 
278
277
  const surfaces = {
@@ -281,11 +280,11 @@ const mappedRoles = (theme: MappedTheme) => {
281
280
  };
282
281
 
283
282
  const seeds: Record<Status, Oklch> = {
284
- info: fromHex(theme.palette.info),
285
- success: fromHex(theme.palette.success),
286
- important: fromHex(theme.palette.important),
287
- warning: fromHex(theme.palette.warning),
288
- danger: fromHex(theme.palette.danger),
283
+ info: fromHex(source.info),
284
+ success: fromHex(source.success),
285
+ important: fromHex(source.important),
286
+ warning: fromHex(source.warning),
287
+ danger: fromHex(source.danger),
289
288
  };
290
289
 
291
290
  return {
@@ -338,7 +337,20 @@ const mappedStyle = (base: Style): Style => ({
338
337
  /** Resolve a theme into roles. Mapped themes take `baseStyle`, Base's style. */
339
338
  export const resolve = (theme: Theme, baseStyle: Style): ResolvedTheme => {
340
339
  if (theme.kind === "mapped") {
341
- const roles = mappedRoles(theme);
340
+ // `palette` is for its own mode, `otherPalette` for the opposite one
341
+ const light = theme.mode === "light" ? theme.palette : theme.otherPalette;
342
+
343
+ const dark = theme.mode === "dark" ? theme.palette : theme.otherPalette;
344
+ const modes: Partial<Record<Mode, Roles>> = {};
345
+
346
+ if (light !== undefined) modes.light = mappedRoles(light, false);
347
+
348
+ if (dark !== undefined) {
349
+ const roles = mappedRoles(dark, true);
350
+
351
+ modes.dark = roles;
352
+ modes.darker = darker(roles);
353
+ }
342
354
 
343
355
  return {
344
356
  id: theme.id,
@@ -348,10 +360,7 @@ export const resolve = (theme: Theme, baseStyle: Style): ResolvedTheme => {
348
360
  family: theme.family,
349
361
  examples: [],
350
362
  style: mappedStyle(baseStyle),
351
- modes:
352
- theme.mode === "dark"
353
- ? { dark: roles, darker: darker(roles) }
354
- : { light: roles },
363
+ modes,
355
364
  };
356
365
  }
357
366