@tooee/themes 0.7.3 → 0.9.0

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/src/loader.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { SyntaxStyle } from "@opentui/core";
2
2
  import { readFileSync, readdirSync, existsSync } from "node:fs";
3
3
  import path from "node:path";
4
+ import type { ColorMode } from "@tooee/config";
4
5
  import { resolveTheme } from "./types.js";
5
6
  import type { ThemeJSON, ResolvedTheme } from "./types.js";
6
7
  import { buildSyntaxStyle } from "./syntax-rules.js";
@@ -11,7 +12,7 @@ import { buildSyntaxStyle } from "./syntax-rules.js";
11
12
 
12
13
  export interface Theme {
13
14
  name: string;
14
- mode: "dark" | "light";
15
+ mode: ColorMode;
15
16
  colors: ResolvedTheme;
16
17
  syntax: SyntaxStyle;
17
18
  }
@@ -34,8 +35,13 @@ const loadJsonThemesFromDir = function loadJsonThemesFromDir(
34
35
  const name = path.basename(file, ".json");
35
36
  try {
36
37
  const content = readFileSync(path.join(dir, file), "utf-8");
37
- // Deferred(lint-sweep): replace with schema-based validation of untrusted JSON
38
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- untrusted external JSON, validated in a later sweep
38
+ // SAFETY: the file is not validated here on purpose. Its only consumer is
39
+ // `resolveTheme`, which reads each key with a per-key fallback, and
40
+ // `buildTheme` catches every malformed value and returns the hardcoded
41
+ // theme, so a document that is not a ThemeJSON cannot escape this module.
42
+ // A strict decoder would also drop malformed files from `getThemeNames`,
43
+ // which is a behaviour change reserved for a separate decision.
44
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- tolerant consumer boundary; see the SAFETY note
39
45
  target.set(name, JSON.parse(content) as ThemeJSON);
40
46
  } catch {
41
47
  // skip invalid files
@@ -89,85 +95,38 @@ export const getThemeNames = function getThemeNames(): string[] {
89
95
  // ---------------------------------------------------------------------------
90
96
 
91
97
  export const DEFAULT_THEME_NAME = "tokyonight";
92
- export const DEFAULT_MODE: "dark" | "light" = "dark";
93
-
94
- const hardcodedDefaultTheme: Theme = (() => {
95
- const colors: ResolvedTheme = {
96
- accent: "#7dcfff",
97
- background: "#1a1b26",
98
- backgroundElement: "#222436",
99
- backgroundPanel: "#1e2030",
100
- border: "#565f89",
101
- borderActive: "#737aa2",
102
- borderSubtle: "#414868",
103
- cursorLine: "#222436",
104
- diffAdded: "#4fd6be",
105
- diffAddedBg: "#20303b",
106
- diffAddedLineNumberBg: "#1b2b34",
107
- diffContext: "#828bb8",
108
- diffContextBg: "#1e2030",
109
- diffHighlightAdded: "#b8db87",
110
- diffHighlightRemoved: "#e26a75",
111
- diffHunkHeader: "#828bb8",
112
- diffLineNumber: "#222436",
113
- diffRemoved: "#c53b53",
114
- diffRemovedBg: "#37222c",
115
- diffRemovedLineNumberBg: "#2d1f26",
116
- error: "#f7768e",
117
- info: "#7aa2f7",
118
- markdownBlockQuote: "#e0af68",
119
- markdownCode: "#9ece6a",
120
- markdownCodeBlock: "#c0caf5",
121
- markdownEmph: "#e0af68",
122
- markdownHeading: "#bb9af7",
123
- markdownHorizontalRule: "#565f89",
124
- markdownImage: "#7aa2f7",
125
- markdownImageText: "#7dcfff",
126
- markdownLink: "#7aa2f7",
127
- markdownLinkText: "#7dcfff",
128
- markdownListEnumeration: "#7dcfff",
129
- markdownListItem: "#7aa2f7",
130
- markdownStrong: "#ff966c",
131
- markdownText: "#c0caf5",
132
- primary: "#7aa2f7",
133
- secondary: "#bb9af7",
134
- selection: "#1e2030",
135
- success: "#9ece6a",
136
- syntaxComment: "#565f89",
137
- syntaxFunction: "#7aa2f7",
138
- syntaxKeyword: "#bb9af7",
139
- syntaxNumber: "#ff9e64",
140
- syntaxOperator: "#89ddff",
141
- syntaxPunctuation: "#a9b1d6",
142
- syntaxString: "#9ece6a",
143
- syntaxType: "#2ac3de",
144
- syntaxVariable: "#c0caf5",
145
- text: "#c0caf5",
146
- textMuted: "#565f89",
147
- warning: "#e0af68",
148
- };
149
- return { colors, mode: DEFAULT_MODE, name: DEFAULT_THEME_NAME, syntax: buildSyntaxStyle(colors) };
150
- })();
151
-
152
- export const defaultTheme: Theme = hardcodedDefaultTheme;
153
-
154
- export const buildTheme = function buildTheme(name: string, mode: "dark" | "light"): Theme {
98
+ export const DEFAULT_MODE: ColorMode = "dark";
99
+
100
+ // SAFETY: this package owns the bundled theme document. resolveTheme validates
101
+ // every consumed color and fills every omitted key from FALLBACKS.
102
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- trusted package-owned JSON boundary
103
+ const bundledDefaultThemeJson = JSON.parse(
104
+ readFileSync(new URL("../themes/tokyonight.json", import.meta.url), "utf-8"),
105
+ ) as ThemeJSON;
106
+
107
+ const buildBundledDefaultTheme = function buildBundledDefaultTheme(mode: ColorMode): Theme {
108
+ const colors = resolveTheme(bundledDefaultThemeJson, mode);
109
+ return { colors, mode, name: DEFAULT_THEME_NAME, syntax: buildSyntaxStyle(colors) };
110
+ };
111
+
112
+ export const defaultTheme: Theme = buildBundledDefaultTheme(DEFAULT_MODE);
113
+
114
+ export const buildTheme = function buildTheme(name: string, mode: ColorMode): Theme {
155
115
  const themes = loadThemes();
156
116
  const json = themes.get(name);
157
117
  if (!json) {
158
- // Fall back to tokyonight, then first available, then hardcoded
118
+ // Fall back to tokyonight, then first available, then the bundled default.
159
119
  const fallbackJson = themes.get(DEFAULT_THEME_NAME) ?? themes.values().next().value;
160
120
  if (fallbackJson) {
161
121
  const resolved = resolveTheme(fallbackJson, mode);
162
122
  return { colors: resolved, mode, name, syntax: buildSyntaxStyle(resolved) };
163
123
  }
164
- // Absolute fallback — hardcoded Tokyo Night colors
165
- return hardcodedDefaultTheme;
124
+ return buildBundledDefaultTheme(mode);
166
125
  }
167
126
  try {
168
127
  const resolved = resolveTheme(json, mode);
169
128
  return { colors: resolved, mode, name, syntax: buildSyntaxStyle(resolved) };
170
129
  } catch {
171
- return hardcodedDefaultTheme;
130
+ return buildBundledDefaultTheme(mode);
172
131
  }
173
132
  };
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { ColorMode } from "@tooee/config";
2
+
1
3
  // ---------------------------------------------------------------------------
2
4
  // Theme JSON format (OpenCode-compatible)
3
5
  // ---------------------------------------------------------------------------
@@ -78,64 +80,8 @@ export interface ResolvedTheme {
78
80
  syntaxPunctuation: string;
79
81
  }
80
82
 
81
- // All keys of ResolvedTheme for iteration
82
- export const RESOLVED_KEYS: (keyof ResolvedTheme)[] = [
83
- "primary",
84
- "secondary",
85
- "accent",
86
- "error",
87
- "warning",
88
- "success",
89
- "info",
90
- "text",
91
- "textMuted",
92
- "background",
93
- "backgroundPanel",
94
- "backgroundElement",
95
- "border",
96
- "borderActive",
97
- "borderSubtle",
98
- "cursorLine",
99
- "selection",
100
- "diffAdded",
101
- "diffRemoved",
102
- "diffContext",
103
- "diffHunkHeader",
104
- "diffHighlightAdded",
105
- "diffHighlightRemoved",
106
- "diffAddedBg",
107
- "diffRemovedBg",
108
- "diffContextBg",
109
- "diffLineNumber",
110
- "diffAddedLineNumberBg",
111
- "diffRemovedLineNumberBg",
112
- "markdownText",
113
- "markdownHeading",
114
- "markdownLink",
115
- "markdownLinkText",
116
- "markdownCode",
117
- "markdownBlockQuote",
118
- "markdownEmph",
119
- "markdownStrong",
120
- "markdownHorizontalRule",
121
- "markdownListItem",
122
- "markdownListEnumeration",
123
- "markdownImage",
124
- "markdownImageText",
125
- "markdownCodeBlock",
126
- "syntaxComment",
127
- "syntaxKeyword",
128
- "syntaxFunction",
129
- "syntaxVariable",
130
- "syntaxString",
131
- "syntaxNumber",
132
- "syntaxType",
133
- "syntaxOperator",
134
- "syntaxPunctuation",
135
- ];
136
-
137
- // Fallbacks used when a theme key is missing
138
- export const FALLBACKS: Record<string, string> = {
83
+ // Fallbacks used when a theme key is missing: a complete theme in its own right.
84
+ export const FALLBACKS = {
139
85
  accent: "#808080",
140
86
  background: "#1e1e1e",
141
87
  backgroundElement: "#1e1e1e",
@@ -188,44 +134,54 @@ export const FALLBACKS: Record<string, string> = {
188
134
  text: "#d4d4d4",
189
135
  textMuted: "#808080",
190
136
  warning: "#808080",
191
- };
137
+ } satisfies Record<keyof ResolvedTheme, string>;
138
+
139
+ export const RESOLVED_KEYS =
140
+ // SAFETY: FALLBACKS satisfies a complete ResolvedTheme Record, so Object.keys contains only its keys.
141
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- Object.keys does not preserve Record key types
142
+ Object.keys(FALLBACKS) as (keyof ResolvedTheme)[];
192
143
 
193
144
  // ---------------------------------------------------------------------------
194
145
  // Resolution
195
146
  // ---------------------------------------------------------------------------
196
147
 
197
- export const resolveTheme = function resolveTheme(
198
- json: ThemeJSON,
199
- mode: "dark" | "light",
200
- ): ResolvedTheme {
148
+ /** A mode-specific colour pair is an object; a single colour is a string. */
149
+ const isVariant = function isVariant(value: ColorValue): value is Variant {
150
+ return value instanceof Object;
151
+ };
152
+
153
+ export const resolveTheme = function resolveTheme(json: ThemeJSON, mode: ColorMode): ResolvedTheme {
201
154
  const defs = json.defs ?? {};
202
155
 
203
156
  const resolveColor = function resolveColor(c: ColorValue, seen = new Set<string>()): string {
204
- if (typeof c === "string") {
205
- if (c === "transparent" || c === "none") {
206
- return "#00000000";
207
- }
208
- if (c.startsWith("#")) {
209
- return c;
210
- }
211
- if (seen.has(c)) {
212
- return "#808080";
213
- }
214
- if (defs[c] !== undefined && defs[c] !== null) {
215
- return resolveColor(defs[c], new Set(seen).add(c));
216
- }
217
- if (json.theme[c] !== undefined) {
218
- return resolveColor(json.theme[c], new Set(seen).add(c));
219
- }
157
+ if (isVariant(c)) {
158
+ return resolveColor(c[mode], seen);
159
+ }
160
+ if (c === "transparent" || c === "none") {
161
+ return "#00000000";
162
+ }
163
+ if (c.startsWith("#")) {
164
+ return c;
165
+ }
166
+ if (seen.has(c)) {
220
167
  return "#808080";
221
168
  }
222
- return resolveColor(c[mode], seen);
169
+ if (defs[c] !== undefined && defs[c] !== null) {
170
+ return resolveColor(defs[c], new Set(seen).add(c));
171
+ }
172
+ if (json.theme[c] !== undefined) {
173
+ return resolveColor(json.theme[c], new Set(seen).add(c));
174
+ }
175
+ return "#808080";
223
176
  };
224
177
 
225
- const result = {} as Record<string, string>;
178
+ // Start from the complete fallback theme, then resolve every key the JSON defines.
179
+ const result: ResolvedTheme = { ...FALLBACKS };
226
180
  for (const key of RESOLVED_KEYS) {
227
181
  const val = json.theme[key];
228
- result[key] = val === undefined ? (FALLBACKS[key] ?? "#808080") : resolveColor(val);
182
+ if (val !== undefined) {
183
+ result[key] = resolveColor(val);
184
+ }
229
185
  }
230
186
  // Dynamic fallbacks that reference other resolved keys
231
187
  if (json.theme.cursorLine === undefined) {
@@ -234,7 +190,5 @@ export const resolveTheme = function resolveTheme(
234
190
  if (json.theme.selection === undefined) {
235
191
  result.selection = result.backgroundPanel;
236
192
  }
237
- // Deferred(lint-sweep): replace with a typed exhaustive key map for compile-time completeness
238
- // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- RESOLVED_KEYS drives every required key and defaults missing values
239
- return result as unknown as ResolvedTheme;
193
+ return result;
240
194
  };
@@ -1,17 +0,0 @@
1
- interface CloseButtonProps {
2
- /** Called when the button is clicked (left mouse button). */
3
- onClose: () => void;
4
- /** Glyph to render (default "✕"). */
5
- glyph?: string;
6
- /** Foreground colour (defaults to theme.textMuted). */
7
- color?: string;
8
- }
9
- /**
10
- * Reusable clickable close control for overlays.
11
- *
12
- * Renders a small themed glyph that calls `onClose` on left-click. Purely
13
- * additive — overlays keep their existing Escape / cancel keyboard paths.
14
- */
15
- export declare const CloseButton: ({ onClose, glyph, color, }: CloseButtonProps) => React.ReactNode;
16
- export {};
17
- //# sourceMappingURL=close-button.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"close-button.d.ts","sourceRoot":"","sources":["../src/close-button.tsx"],"names":[],"mappings":"AAIA,UAAU,gBAAgB;IACxB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,+BAIrB,gBAAgB,KAAG,KAAK,CAAC,SAqB3B,CAAC"}
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx } from "@opentui/react/jsx-runtime";
2
- import { useCallback } from "react";
3
- import { useTheme } from "./context.js";
4
- /**
5
- * Reusable clickable close control for overlays.
6
- *
7
- * Renders a small themed glyph that calls `onClose` on left-click. Purely
8
- * additive — overlays keep their existing Escape / cancel keyboard paths.
9
- */
10
- export const CloseButton = function CloseButton({ onClose, glyph = "✕", color, }) {
11
- const { theme } = useTheme();
12
- const handleMouseDown = useCallback((event) => {
13
- // Left button only; let other buttons bubble.
14
- if (event.button !== 0) {
15
- return;
16
- }
17
- event.preventDefault();
18
- event.stopPropagation();
19
- onClose();
20
- }, [onClose]);
21
- return (_jsx("box", { flexShrink: 0, paddingLeft: 1, onMouseDown: handleMouseDown, children: _jsx("text", { content: glyph, fg: color ?? theme.textMuted }) }));
22
- };
23
- //# sourceMappingURL=close-button.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"close-button.js","sourceRoot":"","sources":["../src/close-button.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAWxC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,SAAS,WAAW,CAAC,EAC9C,OAAO,EACP,KAAK,GAAG,GAAG,EACX,KAAK,GACY;IACjB,MAAM,EAAE,KAAK,EAAE,GAAG,QAAQ,EAAE,CAAC;IAE7B,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,KAAiB,EAAE,EAAE;QACpB,8CAA8C;QAC9C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,KAAK,CAAC,cAAc,EAAE,CAAC;QACvB,KAAK,CAAC,eAAe,EAAE,CAAC;QACxB,OAAO,EAAE,CAAC;IACZ,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAC;IAEF,OAAO,CACL,cAAK,UAAU,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,eAAe,YAC9D,eAAM,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,IAAI,KAAK,CAAC,SAAS,GAAI,GAClD,CACP,CAAC;AACJ,CAAC,CAAC"}
@@ -1,14 +0,0 @@
1
- export interface ThemePickerEntry {
2
- id: string;
3
- title: string;
4
- }
5
- interface ThemePickerProps {
6
- entries: ThemePickerEntry[];
7
- currentTheme: string;
8
- onSelect: (name: string) => void;
9
- onClose: () => void;
10
- onNavigate: (name: string) => void;
11
- }
12
- export declare const ThemePicker: ({ entries, currentTheme, onSelect, onClose, onNavigate, }: ThemePickerProps) => React.ReactNode;
13
- export {};
14
- //# sourceMappingURL=theme-picker.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme-picker.d.ts","sourceRoot":"","sources":["../src/theme-picker.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,gBAAgB;IACxB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,eAAO,MAAM,WAAW,8DAMrB,gBAAgB,KAAG,KAAK,CAAC,SAmK3B,CAAC"}
@@ -1,109 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "@opentui/react/jsx-runtime";
2
- import { useState, useMemo, useCallback } from "react";
3
- import { useCommand } from "@tooee/commands";
4
- import { fuzzyMatch } from "@tooee/fuzzy";
5
- import { useTheme } from "./context.js";
6
- import { CloseButton } from "./close-button.js";
7
- export const ThemePicker = function ThemePicker({ entries, currentTheme, onSelect, onClose, onNavigate, }) {
8
- const { theme } = useTheme();
9
- const [filter, setFilter] = useState("");
10
- const [activeIndex, setActiveIndex] = useState(() => {
11
- const idx = entries.findIndex((e) => e.id === currentTheme);
12
- return Math.max(idx, 0);
13
- });
14
- const filtered = useMemo(() => {
15
- if (!filter) {
16
- return entries;
17
- }
18
- const results = [];
19
- for (const entry of entries) {
20
- const score = fuzzyMatch(filter, entry.title);
21
- if (score !== null) {
22
- results.push({ entry, score });
23
- }
24
- }
25
- results.sort((a, b) => b.score - a.score);
26
- return results.map((r) => r.entry);
27
- }, [entries, filter]);
28
- const handleSelect = useCallback(() => {
29
- const item = filtered[activeIndex];
30
- if (item !== undefined) {
31
- onSelect(item.id);
32
- }
33
- }, [filtered, activeIndex, onSelect]);
34
- const navigateTo = useCallback((index) => {
35
- setActiveIndex(index);
36
- const item = filtered[index];
37
- if (item !== undefined) {
38
- onNavigate(item.id);
39
- }
40
- }, [filtered, onNavigate]);
41
- const moveUp = useCallback(() => {
42
- navigateTo(Math.max(0, activeIndex - 1));
43
- }, [activeIndex, navigateTo]);
44
- const moveDown = useCallback(() => {
45
- navigateTo(Math.min(filtered.length - 1, activeIndex + 1));
46
- }, [activeIndex, filtered.length, navigateTo]);
47
- useCommand({
48
- handler: onClose,
49
- hidden: true,
50
- hotkey: "Escape",
51
- id: "theme-picker:close",
52
- modes: ["insert", "cursor"],
53
- title: "Close theme picker",
54
- });
55
- useCommand({
56
- handler: handleSelect,
57
- hidden: true,
58
- hotkey: "Enter",
59
- id: "theme-picker:select",
60
- modes: ["insert", "cursor"],
61
- title: "Select theme",
62
- });
63
- useCommand({
64
- handler: moveUp,
65
- hidden: true,
66
- hotkey: "up",
67
- id: "theme-picker:move-up",
68
- modes: ["insert", "cursor"],
69
- title: "Move up",
70
- });
71
- useCommand({
72
- handler: moveDown,
73
- hidden: true,
74
- hotkey: "down",
75
- id: "theme-picker:move-down",
76
- modes: ["insert", "cursor"],
77
- title: "Move down",
78
- });
79
- return (_jsxs("box", { position: "absolute", left: "20%", right: "20%", top: 2, maxHeight: "60%", flexDirection: "column", backgroundColor: theme.backgroundPanel, border: true, borderColor: theme.border, children: [_jsxs("box", { flexDirection: "row", paddingLeft: 1, paddingRight: 1, height: 1, children: [_jsx("text", { content: "\uD83C\uDFA8 ", fg: theme.accent }), _jsx("input", { focused: true, placeholder: "Filter themes...", onSubmit: handleSelect, onInput: (value) => {
80
- setFilter(value);
81
- setActiveIndex(0);
82
- // Preview first match
83
- if (value) {
84
- const results = [];
85
- for (const entry of entries) {
86
- const score = fuzzyMatch(value, entry.title);
87
- if (score !== null) {
88
- results.push({ entry, score });
89
- }
90
- }
91
- results.sort((a, b) => b.score - a.score);
92
- if (results.length > 0) {
93
- onNavigate(results[0].entry.id);
94
- }
95
- }
96
- else if (entries.length > 0) {
97
- onNavigate(entries[0].id);
98
- }
99
- }, backgroundColor: "transparent", focusedBackgroundColor: "transparent", textColor: theme.text, placeholderColor: theme.textMuted, cursorColor: theme.accent, style: { flexGrow: 1 } }), _jsx("text", { content: ` ${filtered.length}`, fg: theme.textMuted }), _jsx(CloseButton, { onClose: onClose })] }), _jsx("box", { height: 1, width: "100%", backgroundColor: theme.border }), _jsx("scrollbox", { focused: false, style: { flexGrow: 1 }, children: filtered.map((entry, i) => (_jsx("box", { flexDirection: "row", paddingLeft: 1, paddingRight: 1, height: 1, backgroundColor: i === activeIndex ? theme.backgroundElement : undefined, onMouseDown: (event) => {
100
- // Left-click applies the theme — same code path as Enter.
101
- if (event.button !== 0) {
102
- return;
103
- }
104
- event.preventDefault();
105
- event.stopPropagation();
106
- onSelect(entry.id);
107
- }, children: _jsx("text", { content: entry.title, fg: theme.text, style: { flexGrow: 1 } }) }, entry.id))) })] }));
108
- };
109
- //# sourceMappingURL=theme-picker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"theme-picker.js","sourceRoot":"","sources":["../src/theme-picker.tsx"],"names":[],"mappings":"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"}
@@ -1,45 +0,0 @@
1
- import { useCallback } from "react";
2
- import type { MouseEvent } from "@opentui/core";
3
- import { useTheme } from "./context.js";
4
-
5
- interface CloseButtonProps {
6
- /** Called when the button is clicked (left mouse button). */
7
- onClose: () => void;
8
- /** Glyph to render (default "✕"). */
9
- glyph?: string;
10
- /** Foreground colour (defaults to theme.textMuted). */
11
- color?: string;
12
- }
13
-
14
- /**
15
- * Reusable clickable close control for overlays.
16
- *
17
- * Renders a small themed glyph that calls `onClose` on left-click. Purely
18
- * additive — overlays keep their existing Escape / cancel keyboard paths.
19
- */
20
- export const CloseButton = function CloseButton({
21
- onClose,
22
- glyph = "✕",
23
- color,
24
- }: CloseButtonProps): React.ReactNode {
25
- const { theme } = useTheme();
26
-
27
- const handleMouseDown = useCallback(
28
- (event: MouseEvent) => {
29
- // Left button only; let other buttons bubble.
30
- if (event.button !== 0) {
31
- return;
32
- }
33
- event.preventDefault();
34
- event.stopPropagation();
35
- onClose();
36
- },
37
- [onClose],
38
- );
39
-
40
- return (
41
- <box flexShrink={0} paddingLeft={1} onMouseDown={handleMouseDown}>
42
- <text content={glyph} fg={color ?? theme.textMuted} />
43
- </box>
44
- );
45
- };