@opencode/ui 0.0.0-dev-20607 → 0.0.0-dev-20610
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 +1 -1
- package/src/actions/button/button.tsx +1 -0
- package/src/actions/icon-button/icon-button.tsx +1 -0
- package/src/actions/split-button/split-button.tsx +3 -0
- package/src/components/animated-number.tsx +13 -0
- package/src/components/app-icon.tsx +2 -0
- package/src/components/card.tsx +21 -0
- package/src/components/collapsible.tsx +1 -0
- package/src/components/context-menu.tsx +34 -0
- package/src/components/dock-surface.tsx +3 -0
- package/src/components/file-icon.tsx +12 -0
- package/src/components/image-preview.tsx +1 -0
- package/src/components/list.tsx +32 -0
- package/src/components/motion-spring.tsx +5 -0
- package/src/components/popover.tsx +12 -0
- package/src/components/provider-icon.tsx +1 -0
- package/src/components/resize-handle.tsx +8 -0
- package/src/components/scroll-view.tsx +39 -0
- package/src/components/spinner.tsx +2 -0
- package/src/components/text-field.tsx +6 -0
- package/src/components/text-reveal.tsx +19 -0
- package/src/components/text-strikethrough.tsx +8 -0
- package/src/components/typewriter.tsx +4 -0
- package/src/context/dialog.tsx +12 -0
- package/src/context/file.tsx +1 -0
- package/src/context/helper.tsx +4 -0
- package/src/context/i18n.tsx +20 -0
- package/src/context/index.ts +3 -0
- package/src/context/marked-base.tsx +7 -0
- package/src/context/marked-parser.tsx +7 -0
- package/src/context/marked.tsx +3 -0
- package/src/context/worker-pool.tsx +2 -0
- package/src/data-display/avatar/avatar.tsx +4 -0
- package/src/data-display/badge/badge.tsx +1 -0
- package/src/data-display/diff-changes/diff-changes.tsx +2 -0
- package/src/data-display/keybind/keybind.tsx +1 -0
- package/src/data-display/line-comment/line-comment.tsx +22 -0
- package/src/data-display/project-avatar/project-avatar.tsx +10 -0
- package/src/feedback/loader/loader.tsx +1 -0
- package/src/feedback/toast/description.ts +1 -0
- package/src/forms/checkbox/checkbox.tsx +1 -0
- package/src/forms/field/field.tsx +8 -0
- package/src/forms/inline-input/inline-input.tsx +4 -0
- package/src/forms/radio/radio.tsx +2 -0
- package/src/forms/select/select.tsx +11 -0
- package/src/forms/switch/switch.tsx +1 -0
- package/src/forms/text-input/text-input.tsx +3 -0
- package/src/hooks/create-auto-scroll.tsx +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-filtered-list.tsx +14 -0
- package/src/i18n/no.ts +1 -0
- package/src/layout/divider/divider.tsx +1 -0
- package/src/navigation/menu/menu.tsx +13 -0
- package/src/navigation/segmented-control/segmented-control.tsx +16 -0
- package/src/navigation/tab-state-indicator.tsx +1 -0
- package/src/navigation/tabs/tabs.tsx +18 -0
- package/src/overlays/dialog/dialog.tsx +3 -0
- package/src/overlays/tooltip/tooltip.tsx +15 -0
- package/src/storybook/scaffold.tsx +3 -0
- package/src/theme/color.ts +27 -0
- package/src/theme/context.tsx +58 -0
- package/src/theme/default-themes.ts +35 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/loader.ts +11 -0
- package/src/theme/resolve.ts +28 -0
- package/src/theme/v2/foreground.ts +8 -0
- package/src/theme/v2/resolve.ts +10 -0
- package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
|
@@ -7,14 +7,17 @@ function fn(value: unknown): value is (...args: never[]) => unknown {
|
|
|
7
7
|
|
|
8
8
|
function pick(mod: Record<string, unknown>, name?: string) {
|
|
9
9
|
if (name && fn(mod[name])) return mod[name]
|
|
10
|
+
|
|
10
11
|
if (fn(mod.default)) return mod.default
|
|
11
12
|
|
|
12
13
|
const preferred = Object.keys(mod)
|
|
13
14
|
.filter((k) => k[0] && k[0] === k[0].toUpperCase())
|
|
14
15
|
.find((k) => fn(mod[k]))
|
|
16
|
+
|
|
15
17
|
if (preferred) return mod[preferred]
|
|
16
18
|
|
|
17
19
|
const first = Object.keys(mod).find((k) => fn(mod[k]))
|
|
20
|
+
|
|
18
21
|
if (first) return mod[first]
|
|
19
22
|
|
|
20
23
|
return () => {
|
package/src/theme/color.ts
CHANGED
|
@@ -10,6 +10,7 @@ function hue(v: number) {
|
|
|
10
10
|
|
|
11
11
|
export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } {
|
|
12
12
|
const h = hex.replace("#", "")
|
|
13
|
+
|
|
13
14
|
const full =
|
|
14
15
|
h.length === 3 || h.length === 4
|
|
15
16
|
? h
|
|
@@ -17,9 +18,11 @@ export function hexToRgb(hex: HexColor): { r: number; g: number; b: number } {
|
|
|
17
18
|
.map((c) => c + c)
|
|
18
19
|
.join("")
|
|
19
20
|
: h
|
|
21
|
+
|
|
20
22
|
const rgb = full.length === 8 ? full.slice(0, 6) : full
|
|
21
23
|
|
|
22
24
|
const num = parseInt(rgb, 16)
|
|
25
|
+
|
|
23
26
|
return {
|
|
24
27
|
r: ((num >> 16) & 255) / 255,
|
|
25
28
|
g: ((num >> 8) & 255) / 255,
|
|
@@ -31,18 +34,22 @@ export function rgbToHex(r: number, g: number, b: number): HexColor {
|
|
|
31
34
|
const toHex = (v: number) => {
|
|
32
35
|
const clamped = clamp(v, 0, 1)
|
|
33
36
|
const int = Math.round(clamped * 255)
|
|
37
|
+
|
|
34
38
|
return int.toString(16).padStart(2, "0")
|
|
35
39
|
}
|
|
40
|
+
|
|
36
41
|
return `#${toHex(r)}${toHex(g)}${toHex(b)}`
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
function linearToSrgb(c: number): number {
|
|
40
45
|
if (c <= 0.0031308) return c * 12.92
|
|
46
|
+
|
|
41
47
|
return 1.055 * Math.pow(c, 1 / 2.4) - 0.055
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
function srgbToLinear(c: number): number {
|
|
45
51
|
if (c <= 0.04045) return c / 12.92
|
|
52
|
+
|
|
46
53
|
return Math.pow((c + 0.055) / 1.055, 2.4)
|
|
47
54
|
}
|
|
48
55
|
|
|
@@ -65,6 +72,7 @@ export function rgbToOklch(r: number, g: number, b: number): OklchColor {
|
|
|
65
72
|
|
|
66
73
|
const C = Math.sqrt(a * a + bOk * bOk)
|
|
67
74
|
let H = Math.atan2(bOk, a) * (180 / Math.PI)
|
|
75
|
+
|
|
68
76
|
if (H < 0) H += 360
|
|
69
77
|
|
|
70
78
|
return { l: L, c: C, h: H }
|
|
@@ -97,6 +105,7 @@ export function oklchToRgb(oklch: OklchColor): { r: number; g: number; b: number
|
|
|
97
105
|
|
|
98
106
|
export function hexToOklch(hex: HexColor): OklchColor {
|
|
99
107
|
const { r, g, b } = hexToRgb(hex)
|
|
108
|
+
|
|
100
109
|
return rgbToOklch(r, g, b)
|
|
101
110
|
}
|
|
102
111
|
|
|
@@ -108,15 +117,18 @@ export function fitOklch(oklch: OklchColor): OklchColor {
|
|
|
108
117
|
}
|
|
109
118
|
|
|
110
119
|
const rgb = oklchToRgb(base)
|
|
120
|
+
|
|
111
121
|
if (rgb.r >= 0 && rgb.r <= 1 && rgb.g >= 0 && rgb.g <= 1 && rgb.b >= 0 && rgb.b <= 1) {
|
|
112
122
|
return base
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
let c = base.c
|
|
126
|
+
|
|
116
127
|
for (let i = 0; i < 24; i++) {
|
|
117
128
|
c *= 0.9
|
|
118
129
|
const next = { ...base, c }
|
|
119
130
|
const out = oklchToRgb(next)
|
|
131
|
+
|
|
120
132
|
if (out.r >= 0 && out.r <= 1 && out.g >= 0 && out.g <= 1 && out.b >= 0 && out.b <= 1) {
|
|
121
133
|
return next
|
|
122
134
|
}
|
|
@@ -127,6 +139,7 @@ export function fitOklch(oklch: OklchColor): OklchColor {
|
|
|
127
139
|
|
|
128
140
|
export function oklchToHex(oklch: OklchColor): HexColor {
|
|
129
141
|
const { r, g, b } = oklchToRgb(fitOklch(oklch))
|
|
142
|
+
|
|
130
143
|
return rgbToHex(r, g, b)
|
|
131
144
|
}
|
|
132
145
|
|
|
@@ -171,26 +184,31 @@ export function generateScale(seed: HexColor, isDark: boolean): HexColor[] {
|
|
|
171
184
|
export function generateNeutralScale(seed: HexColor, isDark: boolean, ink?: HexColor): HexColor[] {
|
|
172
185
|
if (ink) {
|
|
173
186
|
const base = hexToOklch(seed)
|
|
187
|
+
|
|
174
188
|
const lift = (tone: number) =>
|
|
175
189
|
oklchToHex({
|
|
176
190
|
l: base.l + (1 - base.l) * tone,
|
|
177
191
|
c: base.c * Math.max(0, 1 - tone),
|
|
178
192
|
h: base.h,
|
|
179
193
|
})
|
|
194
|
+
|
|
180
195
|
const sink = (tone: number) =>
|
|
181
196
|
oklchToHex({
|
|
182
197
|
l: base.l * (1 - tone),
|
|
183
198
|
c: base.c * Math.max(0, 1 - tone * (isDark ? 0.12 : 0.3)),
|
|
184
199
|
h: base.h,
|
|
185
200
|
})
|
|
201
|
+
|
|
186
202
|
const bg = isDark
|
|
187
203
|
? sink(clamp(0.19 + Math.max(0, base.l - 0.12) * 0.33 + base.c * 1.95, 0.17, 0.27))
|
|
188
204
|
: base.l < 0.82
|
|
189
205
|
? lift(0.86)
|
|
190
206
|
: lift(clamp(0.1 + base.c * 3.2 + Math.max(0, 0.95 - base.l) * 0.35, 0.1, 0.28))
|
|
207
|
+
|
|
191
208
|
const steps = isDark
|
|
192
209
|
? [0, 0.018, 0.039, 0.064, 0.097, 0.143, 0.212, 0.31, 0.46, 0.649, 0.845, 0.984]
|
|
193
210
|
: [0, 0.022, 0.042, 0.068, 0.102, 0.146, 0.208, 0.296, 0.432, 0.61, 0.81, 0.965]
|
|
211
|
+
|
|
194
212
|
return steps.map((step) => mixColors(bg, ink, step))
|
|
195
213
|
}
|
|
196
214
|
|
|
@@ -247,6 +265,7 @@ export function mixColors(color1: HexColor, color2: HexColor, amount: number): H
|
|
|
247
265
|
|
|
248
266
|
export function shift(color: HexColor, value: { l?: number; c?: number; h?: number }): HexColor {
|
|
249
267
|
const base = hexToOklch(color)
|
|
268
|
+
|
|
250
269
|
return oklchToHex({
|
|
251
270
|
l: base.l + (value.l ?? 0),
|
|
252
271
|
c: base.c * (value.c ?? 1),
|
|
@@ -256,20 +275,25 @@ export function shift(color: HexColor, value: { l?: number; c?: number; h?: numb
|
|
|
256
275
|
|
|
257
276
|
export function contrastRatio(foreground: HexColor, background: HexColor) {
|
|
258
277
|
const linear = (c: number) => (c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4)
|
|
278
|
+
|
|
259
279
|
const luminance = (hex: HexColor) => {
|
|
260
280
|
const { r, g, b } = hexToRgb(hex)
|
|
281
|
+
|
|
261
282
|
return 0.2126 * linear(r) + 0.7152 * linear(g) + 0.0722 * linear(b)
|
|
262
283
|
}
|
|
284
|
+
|
|
263
285
|
const fg = luminance(foreground)
|
|
264
286
|
const bg = luminance(background)
|
|
265
287
|
const lighter = Math.max(fg, bg)
|
|
266
288
|
const darker = Math.min(fg, bg)
|
|
289
|
+
|
|
267
290
|
return (lighter + 0.05) / (darker + 0.05)
|
|
268
291
|
}
|
|
269
292
|
|
|
270
293
|
export function blend(color: HexColor, background: HexColor, alpha: number): HexColor {
|
|
271
294
|
const fg = hexToRgb(color)
|
|
272
295
|
const bg = hexToRgb(background)
|
|
296
|
+
|
|
273
297
|
return rgbToHex(
|
|
274
298
|
fg.r * alpha + bg.r * (1 - alpha),
|
|
275
299
|
fg.g * alpha + bg.g * (1 - alpha),
|
|
@@ -279,6 +303,7 @@ export function blend(color: HexColor, background: HexColor, alpha: number): Hex
|
|
|
279
303
|
|
|
280
304
|
export function lighten(color: HexColor, amount: number): HexColor {
|
|
281
305
|
const oklch = hexToOklch(color)
|
|
306
|
+
|
|
282
307
|
return oklchToHex({
|
|
283
308
|
...oklch,
|
|
284
309
|
l: clamp(oklch.l + amount, 0, 1),
|
|
@@ -287,6 +312,7 @@ export function lighten(color: HexColor, amount: number): HexColor {
|
|
|
287
312
|
|
|
288
313
|
export function darken(color: HexColor, amount: number): HexColor {
|
|
289
314
|
const oklch = hexToOklch(color)
|
|
315
|
+
|
|
290
316
|
return oklchToHex({
|
|
291
317
|
...oklch,
|
|
292
318
|
l: clamp(oklch.l - amount, 0, 1),
|
|
@@ -295,5 +321,6 @@ export function darken(color: HexColor, amount: number): HexColor {
|
|
|
295
321
|
|
|
296
322
|
export function withAlpha(color: HexColor, alpha: number): string {
|
|
297
323
|
const { r, g, b } = hexToRgb(color)
|
|
324
|
+
|
|
298
325
|
return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${alpha})`
|
|
299
326
|
}
|
package/src/theme/context.tsx
CHANGED
|
@@ -19,13 +19,17 @@ const STORAGE_KEYS = {
|
|
|
19
19
|
} as const
|
|
20
20
|
|
|
21
21
|
const THEME_STYLE_ID = "oc-theme"
|
|
22
|
+
|
|
22
23
|
let files: Record<string, () => Promise<{ default: DesktopTheme }>> | undefined
|
|
24
|
+
|
|
23
25
|
let ids: string[] | undefined
|
|
26
|
+
|
|
24
27
|
let known: Set<string> | undefined
|
|
25
28
|
|
|
26
29
|
function getFiles() {
|
|
27
30
|
if (files) return files
|
|
28
31
|
files = import.meta.glob<{ default: DesktopTheme }>("./themes/*.json")
|
|
32
|
+
|
|
29
33
|
return files
|
|
30
34
|
}
|
|
31
35
|
|
|
@@ -34,12 +38,14 @@ function themeIDs() {
|
|
|
34
38
|
ids = Object.keys(getFiles())
|
|
35
39
|
.map((path) => path.slice("./themes/".length, -".json".length))
|
|
36
40
|
.sort()
|
|
41
|
+
|
|
37
42
|
return ids
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
function knownThemes() {
|
|
41
46
|
if (known) return known
|
|
42
47
|
known = new Set(themeIDs())
|
|
48
|
+
|
|
43
49
|
return known
|
|
44
50
|
}
|
|
45
51
|
|
|
@@ -81,15 +87,18 @@ const names: Record<string, string> = {
|
|
|
81
87
|
vesper: "Vesper",
|
|
82
88
|
zenburn: "Zenburn",
|
|
83
89
|
}
|
|
90
|
+
|
|
84
91
|
const oc2Theme = oc2ThemeJson as DesktopTheme
|
|
85
92
|
|
|
86
93
|
function resolveStoredTheme(id: string | null | undefined, registered?: Record<string, DesktopTheme>) {
|
|
87
94
|
if (id === "oc-2" || (id && (knownThemes().has(id) || registered?.[id]))) return id
|
|
95
|
+
|
|
88
96
|
return "oc-2"
|
|
89
97
|
}
|
|
90
98
|
|
|
91
99
|
function read(key: string) {
|
|
92
100
|
if (typeof localStorage !== "object") return null
|
|
101
|
+
|
|
93
102
|
try {
|
|
94
103
|
return localStorage.getItem(key)
|
|
95
104
|
} catch {
|
|
@@ -99,6 +108,7 @@ function read(key: string) {
|
|
|
99
108
|
|
|
100
109
|
function write(key: string, value: string) {
|
|
101
110
|
if (typeof localStorage !== "object") return
|
|
111
|
+
|
|
102
112
|
try {
|
|
103
113
|
localStorage.setItem(key, value)
|
|
104
114
|
} catch {}
|
|
@@ -106,6 +116,7 @@ function write(key: string, value: string) {
|
|
|
106
116
|
|
|
107
117
|
function drop(key: string) {
|
|
108
118
|
if (typeof localStorage !== "object") return
|
|
119
|
+
|
|
109
120
|
try {
|
|
110
121
|
localStorage.removeItem(key)
|
|
111
122
|
} catch {}
|
|
@@ -118,15 +129,18 @@ function clear() {
|
|
|
118
129
|
|
|
119
130
|
function ensureThemeStyleElement(): HTMLStyleElement {
|
|
120
131
|
const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
|
|
132
|
+
|
|
121
133
|
if (existing) return existing
|
|
122
134
|
const element = document.createElement("style")
|
|
123
135
|
element.id = THEME_STYLE_ID
|
|
124
136
|
document.head.appendChild(element)
|
|
137
|
+
|
|
125
138
|
return element
|
|
126
139
|
}
|
|
127
140
|
|
|
128
141
|
function getSystemMode(): "light" | "dark" {
|
|
129
142
|
if (typeof window !== "object") return "light"
|
|
143
|
+
|
|
130
144
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
131
145
|
}
|
|
132
146
|
|
|
@@ -156,11 +170,13 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da
|
|
|
156
170
|
|
|
157
171
|
// Update theme-color meta tag to match light/dark mode
|
|
158
172
|
const meta = document.querySelector('meta[name="theme-color"]')
|
|
173
|
+
|
|
159
174
|
if (meta) meta.setAttribute("content", isDark ? "#080808" : "#fafafa")
|
|
160
175
|
}
|
|
161
176
|
|
|
162
177
|
function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
|
|
163
178
|
if (themeId === "oc-2") return
|
|
179
|
+
|
|
164
180
|
for (const mode of ["light", "dark"] as const) {
|
|
165
181
|
const isDark = mode === "dark"
|
|
166
182
|
const variant = isDark ? theme.dark : theme.light
|
|
@@ -179,12 +195,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
179
195
|
}) => {
|
|
180
196
|
const rawTheme = read(STORAGE_KEYS.THEME_ID) ?? props.defaultTheme
|
|
181
197
|
const themeId = resolveStoredTheme(rawTheme)
|
|
198
|
+
|
|
182
199
|
if (rawTheme && rawTheme !== themeId) {
|
|
183
200
|
write(STORAGE_KEYS.THEME_ID, themeId)
|
|
184
201
|
clear()
|
|
185
202
|
}
|
|
203
|
+
|
|
186
204
|
const colorScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system"
|
|
187
205
|
const mode = colorScheme === "system" ? getSystemMode() : colorScheme
|
|
206
|
+
|
|
188
207
|
const [store, setStore] = createStore({
|
|
189
208
|
themes: {
|
|
190
209
|
"oc-2": oc2Theme,
|
|
@@ -200,23 +219,31 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
200
219
|
|
|
201
220
|
const load = (id: string) => {
|
|
202
221
|
const next = id
|
|
222
|
+
|
|
203
223
|
if (!next) return Promise.resolve(undefined)
|
|
204
224
|
const hit = store.themes[next]
|
|
225
|
+
|
|
205
226
|
if (hit) return Promise.resolve(hit)
|
|
206
227
|
const pending = loads.get(next)
|
|
228
|
+
|
|
207
229
|
if (pending) return pending
|
|
208
230
|
const file = getFiles()[`./themes/${next}.json`]
|
|
231
|
+
|
|
209
232
|
if (!file) return Promise.resolve(undefined)
|
|
233
|
+
|
|
210
234
|
const task = file()
|
|
211
235
|
.then((mod) => {
|
|
212
236
|
const theme = mod.default
|
|
213
237
|
setStore("themes", next, theme)
|
|
238
|
+
|
|
214
239
|
return theme
|
|
215
240
|
})
|
|
216
241
|
.finally(() => {
|
|
217
242
|
loads.delete(next)
|
|
218
243
|
})
|
|
244
|
+
|
|
219
245
|
loads.set(next, task)
|
|
246
|
+
|
|
220
247
|
return task
|
|
221
248
|
}
|
|
222
249
|
|
|
@@ -229,8 +256,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
229
256
|
const extra = Object.keys(store.themes)
|
|
230
257
|
.filter((id) => !knownThemes().has(id))
|
|
231
258
|
.sort()
|
|
259
|
+
|
|
232
260
|
const all = themeIDs()
|
|
261
|
+
|
|
233
262
|
if (extra.length === 0) return all
|
|
263
|
+
|
|
234
264
|
return [...all, ...extra]
|
|
235
265
|
}
|
|
236
266
|
|
|
@@ -239,20 +269,26 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
239
269
|
const onStorage = (e: StorageEvent) => {
|
|
240
270
|
if (e.key === STORAGE_KEYS.THEME_ID && e.newValue) {
|
|
241
271
|
const next = resolveStoredTheme(e.newValue, store.themes)
|
|
272
|
+
|
|
242
273
|
if (next !== e.newValue) {
|
|
243
274
|
write(STORAGE_KEYS.THEME_ID, next)
|
|
244
275
|
clear()
|
|
245
276
|
}
|
|
277
|
+
|
|
246
278
|
setStore("themeId", next)
|
|
279
|
+
|
|
247
280
|
if (next === "oc-2") {
|
|
248
281
|
clear()
|
|
282
|
+
|
|
249
283
|
return
|
|
250
284
|
}
|
|
285
|
+
|
|
251
286
|
void load(next).then((theme) => {
|
|
252
287
|
if (!theme || store.themeId !== next) return
|
|
253
288
|
cacheThemeVariants(theme, next)
|
|
254
289
|
})
|
|
255
290
|
}
|
|
291
|
+
|
|
256
292
|
if (e.key === STORAGE_KEYS.COLOR_SCHEME && e.newValue) {
|
|
257
293
|
setStore("colorScheme", e.newValue as ColorScheme)
|
|
258
294
|
setStore("mode", e.newValue === "system" ? getSystemMode() : (e.newValue as "light" | "dark"))
|
|
@@ -263,20 +299,25 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
263
299
|
makeEventListener(window, "storage", onStorage)
|
|
264
300
|
|
|
265
301
|
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
|
302
|
+
|
|
266
303
|
const onMedia = () => {
|
|
267
304
|
if (store.colorScheme !== "system") return
|
|
268
305
|
setStore("mode", getSystemMode())
|
|
269
306
|
}
|
|
307
|
+
|
|
270
308
|
makeEventListener(mediaQuery, "change", onMedia)
|
|
271
309
|
|
|
272
310
|
const rawTheme = read(STORAGE_KEYS.THEME_ID) ?? props.defaultTheme
|
|
273
311
|
const savedTheme = resolveStoredTheme(rawTheme, store.themes)
|
|
274
312
|
const savedScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system"
|
|
313
|
+
|
|
275
314
|
if (rawTheme && rawTheme !== savedTheme) {
|
|
276
315
|
write(STORAGE_KEYS.THEME_ID, savedTheme)
|
|
277
316
|
clear()
|
|
278
317
|
}
|
|
318
|
+
|
|
279
319
|
if (savedTheme !== store.themeId) setStore("themeId", savedTheme)
|
|
320
|
+
|
|
280
321
|
if (savedScheme !== store.colorScheme) setStore("colorScheme", savedScheme)
|
|
281
322
|
setStore("mode", savedScheme === "system" ? getSystemMode() : savedScheme)
|
|
282
323
|
void load(savedTheme).then((theme) => {
|
|
@@ -287,26 +328,35 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
287
328
|
|
|
288
329
|
createEffect(() => {
|
|
289
330
|
const theme = store.themes[store.themeId]
|
|
331
|
+
|
|
290
332
|
if (!theme) return
|
|
291
333
|
applyTheme(theme, store.themeId, store.mode, store.colorScheme)
|
|
292
334
|
})
|
|
293
335
|
|
|
294
336
|
const setTheme = (id: string) => {
|
|
295
337
|
const next = id
|
|
338
|
+
|
|
296
339
|
if (!next) {
|
|
297
340
|
console.warn(`Theme "${id}" not found`)
|
|
341
|
+
|
|
298
342
|
return
|
|
299
343
|
}
|
|
344
|
+
|
|
300
345
|
if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) {
|
|
301
346
|
console.warn(`Theme "${id}" not found`)
|
|
347
|
+
|
|
302
348
|
return
|
|
303
349
|
}
|
|
350
|
+
|
|
304
351
|
setStore("themeId", next)
|
|
352
|
+
|
|
305
353
|
if (next === "oc-2") {
|
|
306
354
|
write(STORAGE_KEYS.THEME_ID, next)
|
|
307
355
|
clear()
|
|
356
|
+
|
|
308
357
|
return
|
|
309
358
|
}
|
|
359
|
+
|
|
310
360
|
void load(next).then((theme) => {
|
|
311
361
|
if (!theme || store.themeId !== next) return
|
|
312
362
|
cacheThemeVariants(theme, next)
|
|
@@ -333,16 +383,20 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
333
383
|
registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
|
|
334
384
|
previewTheme: (id: string) => {
|
|
335
385
|
const next = id
|
|
386
|
+
|
|
336
387
|
if (!next) return
|
|
388
|
+
|
|
337
389
|
if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) return
|
|
338
390
|
setStore("previewThemeId", next)
|
|
339
391
|
void load(next).then((theme) => {
|
|
340
392
|
if (!theme || store.previewThemeId !== next) return
|
|
393
|
+
|
|
341
394
|
const mode = store.previewScheme
|
|
342
395
|
? store.previewScheme === "system"
|
|
343
396
|
? getSystemMode()
|
|
344
397
|
: store.previewScheme
|
|
345
398
|
: store.mode
|
|
399
|
+
|
|
346
400
|
applyTheme(theme, next, mode, store.previewScheme ?? store.colorScheme)
|
|
347
401
|
})
|
|
348
402
|
},
|
|
@@ -352,7 +406,9 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
352
406
|
const id = store.previewThemeId ?? store.themeId
|
|
353
407
|
void load(id).then((theme) => {
|
|
354
408
|
if (!theme) return
|
|
409
|
+
|
|
355
410
|
if ((store.previewThemeId ?? store.themeId) !== id) return
|
|
411
|
+
|
|
356
412
|
if (store.previewScheme !== scheme) return
|
|
357
413
|
applyTheme(theme, id, mode, scheme)
|
|
358
414
|
})
|
|
@@ -361,9 +417,11 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
|
361
417
|
if (store.previewThemeId) {
|
|
362
418
|
setTheme(store.previewThemeId)
|
|
363
419
|
}
|
|
420
|
+
|
|
364
421
|
if (store.previewScheme) {
|
|
365
422
|
setColorScheme(store.previewScheme)
|
|
366
423
|
}
|
|
424
|
+
|
|
367
425
|
setStore("previewThemeId", null)
|
|
368
426
|
setStore("previewScheme", null)
|
|
369
427
|
},
|
|
@@ -37,40 +37,75 @@ import vesperThemeJson from "./themes/vesper.json"
|
|
|
37
37
|
import zenburnThemeJson from "./themes/zenburn.json"
|
|
38
38
|
|
|
39
39
|
export const oc2Theme = oc2ThemeJson as DesktopTheme
|
|
40
|
+
|
|
40
41
|
export const amoledTheme = amoledThemeJson as DesktopTheme
|
|
42
|
+
|
|
41
43
|
export const auraTheme = auraThemeJson as DesktopTheme
|
|
44
|
+
|
|
42
45
|
export const ayuTheme = ayuThemeJson as DesktopTheme
|
|
46
|
+
|
|
43
47
|
export const carbonfoxTheme = carbonfoxThemeJson as DesktopTheme
|
|
48
|
+
|
|
44
49
|
export const catppuccinTheme = catppuccinThemeJson as DesktopTheme
|
|
50
|
+
|
|
45
51
|
export const catppuccinFrappeTheme = catppuccinFrappeThemeJson as DesktopTheme
|
|
52
|
+
|
|
46
53
|
export const catppuccinMacchiatoTheme = catppuccinMacchiatoThemeJson as DesktopTheme
|
|
54
|
+
|
|
47
55
|
export const cobalt2Theme = cobalt2ThemeJson as DesktopTheme
|
|
56
|
+
|
|
48
57
|
export const cursorTheme = cursorThemeJson as DesktopTheme
|
|
58
|
+
|
|
49
59
|
export const draculaTheme = draculaThemeJson as DesktopTheme
|
|
60
|
+
|
|
50
61
|
export const everforestTheme = everforestThemeJson as DesktopTheme
|
|
62
|
+
|
|
51
63
|
export const flexokiTheme = flexokiThemeJson as DesktopTheme
|
|
64
|
+
|
|
52
65
|
export const githubTheme = githubThemeJson as DesktopTheme
|
|
66
|
+
|
|
53
67
|
export const gruvboxTheme = gruvboxThemeJson as DesktopTheme
|
|
68
|
+
|
|
54
69
|
export const kanagawaTheme = kanagawaThemeJson as DesktopTheme
|
|
70
|
+
|
|
55
71
|
export const lucentOrngTheme = lucentOrngThemeJson as DesktopTheme
|
|
72
|
+
|
|
56
73
|
export const materialTheme = materialThemeJson as DesktopTheme
|
|
74
|
+
|
|
57
75
|
export const matrixTheme = matrixThemeJson as DesktopTheme
|
|
76
|
+
|
|
58
77
|
export const mercuryTheme = mercuryThemeJson as DesktopTheme
|
|
78
|
+
|
|
59
79
|
export const monokaiTheme = monokaiThemeJson as DesktopTheme
|
|
80
|
+
|
|
60
81
|
export const nightowlTheme = nightowlThemeJson as DesktopTheme
|
|
82
|
+
|
|
61
83
|
export const nordTheme = nordThemeJson as DesktopTheme
|
|
84
|
+
|
|
62
85
|
export const oneDarkTheme = oneDarkThemeJson as DesktopTheme
|
|
86
|
+
|
|
63
87
|
export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme
|
|
88
|
+
|
|
64
89
|
export const orngTheme = orngThemeJson as DesktopTheme
|
|
90
|
+
|
|
65
91
|
export const osakaJadeTheme = osakaJadeThemeJson as DesktopTheme
|
|
92
|
+
|
|
66
93
|
export const palenightTheme = palenightThemeJson as DesktopTheme
|
|
94
|
+
|
|
67
95
|
export const rosepineTheme = rosepineThemeJson as DesktopTheme
|
|
96
|
+
|
|
68
97
|
export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme
|
|
98
|
+
|
|
69
99
|
export const solarizedTheme = solarizedThemeJson as DesktopTheme
|
|
100
|
+
|
|
70
101
|
export const synthwave84Theme = synthwave84ThemeJson as DesktopTheme
|
|
102
|
+
|
|
71
103
|
export const tokyonightTheme = tokyonightThemeJson as DesktopTheme
|
|
104
|
+
|
|
72
105
|
export const vercelTheme = vercelThemeJson as DesktopTheme
|
|
106
|
+
|
|
73
107
|
export const vesperTheme = vesperThemeJson as DesktopTheme
|
|
108
|
+
|
|
74
109
|
export const zenburnTheme = zenburnThemeJson as DesktopTheme
|
|
75
110
|
|
|
76
111
|
export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
|
package/src/theme/index.ts
CHANGED
|
@@ -32,8 +32,11 @@ export {
|
|
|
32
32
|
} from "./color"
|
|
33
33
|
|
|
34
34
|
export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve"
|
|
35
|
+
|
|
35
36
|
export { resolveThemeVariantV2, resolveThemeV2, themeV2ToCss, generateV2Primitives } from "./v2/resolve"
|
|
37
|
+
|
|
36
38
|
export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader"
|
|
39
|
+
|
|
37
40
|
export { ThemeProvider, useTheme, type ColorScheme } from "./context"
|
|
38
41
|
|
|
39
42
|
export {
|
package/src/theme/loader.ts
CHANGED
|
@@ -3,16 +3,20 @@ import { resolveThemeVariant, themeToCss } from "./resolve"
|
|
|
3
3
|
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
|
|
4
4
|
|
|
5
5
|
let activeTheme: DesktopTheme | null = null
|
|
6
|
+
|
|
6
7
|
const THEME_STYLE_ID = "opencode-theme"
|
|
7
8
|
|
|
8
9
|
function ensureLoaderStyleElement(): HTMLStyleElement {
|
|
9
10
|
const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
|
|
11
|
+
|
|
10
12
|
if (existing) {
|
|
11
13
|
return existing
|
|
12
14
|
}
|
|
15
|
+
|
|
13
16
|
const element = document.createElement("style")
|
|
14
17
|
element.id = THEME_STYLE_ID
|
|
15
18
|
document.head.appendChild(element)
|
|
19
|
+
|
|
16
20
|
return element
|
|
17
21
|
}
|
|
18
22
|
|
|
@@ -77,29 +81,36 @@ html[data-theme="${themeId}"] {
|
|
|
77
81
|
|
|
78
82
|
export async function loadThemeFromUrl(url: string): Promise<DesktopTheme> {
|
|
79
83
|
const response = await fetch(url)
|
|
84
|
+
|
|
80
85
|
if (!response.ok) {
|
|
81
86
|
throw new Error(`Failed to load theme from ${url}: ${response.statusText}`)
|
|
82
87
|
}
|
|
88
|
+
|
|
83
89
|
return response.json()
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
export function getActiveTheme(): DesktopTheme | null {
|
|
87
93
|
const activeId = document.documentElement.getAttribute("data-theme")
|
|
94
|
+
|
|
88
95
|
if (!activeId) {
|
|
89
96
|
return null
|
|
90
97
|
}
|
|
98
|
+
|
|
91
99
|
if (activeTheme?.id === activeId) {
|
|
92
100
|
return activeTheme
|
|
93
101
|
}
|
|
102
|
+
|
|
94
103
|
return null
|
|
95
104
|
}
|
|
96
105
|
|
|
97
106
|
export function removeTheme(): void {
|
|
98
107
|
activeTheme = null
|
|
99
108
|
const existingElement = document.getElementById(THEME_STYLE_ID)
|
|
109
|
+
|
|
100
110
|
if (existingElement) {
|
|
101
111
|
existingElement.remove()
|
|
102
112
|
}
|
|
113
|
+
|
|
103
114
|
document.documentElement.removeAttribute("data-theme")
|
|
104
115
|
}
|
|
105
116
|
|