@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.
Files changed (68) hide show
  1. package/package.json +1 -1
  2. package/src/actions/button/button.tsx +1 -0
  3. package/src/actions/icon-button/icon-button.tsx +1 -0
  4. package/src/actions/split-button/split-button.tsx +3 -0
  5. package/src/components/animated-number.tsx +13 -0
  6. package/src/components/app-icon.tsx +2 -0
  7. package/src/components/card.tsx +21 -0
  8. package/src/components/collapsible.tsx +1 -0
  9. package/src/components/context-menu.tsx +34 -0
  10. package/src/components/dock-surface.tsx +3 -0
  11. package/src/components/file-icon.tsx +12 -0
  12. package/src/components/image-preview.tsx +1 -0
  13. package/src/components/list.tsx +32 -0
  14. package/src/components/motion-spring.tsx +5 -0
  15. package/src/components/popover.tsx +12 -0
  16. package/src/components/provider-icon.tsx +1 -0
  17. package/src/components/resize-handle.tsx +8 -0
  18. package/src/components/scroll-view.tsx +39 -0
  19. package/src/components/spinner.tsx +2 -0
  20. package/src/components/text-field.tsx +6 -0
  21. package/src/components/text-reveal.tsx +19 -0
  22. package/src/components/text-strikethrough.tsx +8 -0
  23. package/src/components/typewriter.tsx +4 -0
  24. package/src/context/dialog.tsx +12 -0
  25. package/src/context/file.tsx +1 -0
  26. package/src/context/helper.tsx +4 -0
  27. package/src/context/i18n.tsx +20 -0
  28. package/src/context/index.ts +3 -0
  29. package/src/context/marked-base.tsx +7 -0
  30. package/src/context/marked-parser.tsx +7 -0
  31. package/src/context/marked.tsx +3 -0
  32. package/src/context/worker-pool.tsx +2 -0
  33. package/src/data-display/avatar/avatar.tsx +4 -0
  34. package/src/data-display/badge/badge.tsx +1 -0
  35. package/src/data-display/diff-changes/diff-changes.tsx +2 -0
  36. package/src/data-display/keybind/keybind.tsx +1 -0
  37. package/src/data-display/line-comment/line-comment.tsx +22 -0
  38. package/src/data-display/project-avatar/project-avatar.tsx +10 -0
  39. package/src/feedback/loader/loader.tsx +1 -0
  40. package/src/feedback/toast/description.ts +1 -0
  41. package/src/forms/checkbox/checkbox.tsx +1 -0
  42. package/src/forms/field/field.tsx +8 -0
  43. package/src/forms/inline-input/inline-input.tsx +4 -0
  44. package/src/forms/radio/radio.tsx +2 -0
  45. package/src/forms/select/select.tsx +11 -0
  46. package/src/forms/switch/switch.tsx +1 -0
  47. package/src/forms/text-input/text-input.tsx +3 -0
  48. package/src/hooks/create-auto-scroll.tsx +34 -0
  49. package/src/hooks/index.ts +1 -0
  50. package/src/hooks/use-filtered-list.tsx +14 -0
  51. package/src/i18n/no.ts +1 -0
  52. package/src/layout/divider/divider.tsx +1 -0
  53. package/src/navigation/menu/menu.tsx +13 -0
  54. package/src/navigation/segmented-control/segmented-control.tsx +16 -0
  55. package/src/navigation/tab-state-indicator.tsx +1 -0
  56. package/src/navigation/tabs/tabs.tsx +18 -0
  57. package/src/overlays/dialog/dialog.tsx +3 -0
  58. package/src/overlays/tooltip/tooltip.tsx +15 -0
  59. package/src/storybook/scaffold.tsx +3 -0
  60. package/src/theme/color.ts +27 -0
  61. package/src/theme/context.tsx +58 -0
  62. package/src/theme/default-themes.ts +35 -0
  63. package/src/theme/index.ts +3 -0
  64. package/src/theme/loader.ts +11 -0
  65. package/src/theme/resolve.ts +28 -0
  66. package/src/theme/v2/foreground.ts +8 -0
  67. package/src/theme/v2/resolve.ts +10 -0
  68. 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 () => {
@@ -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
  }
@@ -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> = {
@@ -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 {
@@ -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