@opencode-ai/ui 0.0.0-beta-202606251302
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/LICENSE +21 -0
- package/package.json +110 -0
- package/src/assets/audio/alert-01.aac +0 -0
- package/src/assets/audio/alert-01.mp3 +0 -0
- package/src/assets/audio/alert-02.aac +0 -0
- package/src/assets/audio/alert-02.mp3 +0 -0
- package/src/assets/audio/alert-03.aac +0 -0
- package/src/assets/audio/alert-03.mp3 +0 -0
- package/src/assets/audio/alert-04.aac +0 -0
- package/src/assets/audio/alert-04.mp3 +0 -0
- package/src/assets/audio/alert-05.aac +0 -0
- package/src/assets/audio/alert-05.mp3 +0 -0
- package/src/assets/audio/alert-06.aac +0 -0
- package/src/assets/audio/alert-06.mp3 +0 -0
- package/src/assets/audio/alert-07.aac +0 -0
- package/src/assets/audio/alert-07.mp3 +0 -0
- package/src/assets/audio/alert-08.aac +0 -0
- package/src/assets/audio/alert-08.mp3 +0 -0
- package/src/assets/audio/alert-09.aac +0 -0
- package/src/assets/audio/alert-09.mp3 +0 -0
- package/src/assets/audio/alert-10.aac +0 -0
- package/src/assets/audio/alert-10.mp3 +0 -0
- package/src/assets/audio/bip-bop-01.aac +0 -0
- package/src/assets/audio/bip-bop-01.mp3 +0 -0
- package/src/assets/audio/bip-bop-02.aac +0 -0
- package/src/assets/audio/bip-bop-02.mp3 +0 -0
- package/src/assets/audio/bip-bop-03.aac +0 -0
- package/src/assets/audio/bip-bop-03.mp3 +0 -0
- package/src/assets/audio/bip-bop-04.aac +0 -0
- package/src/assets/audio/bip-bop-04.mp3 +0 -0
- package/src/assets/audio/bip-bop-05.aac +0 -0
- package/src/assets/audio/bip-bop-05.mp3 +0 -0
- package/src/assets/audio/bip-bop-06.aac +0 -0
- package/src/assets/audio/bip-bop-06.mp3 +0 -0
- package/src/assets/audio/bip-bop-07.aac +0 -0
- package/src/assets/audio/bip-bop-07.mp3 +0 -0
- package/src/assets/audio/bip-bop-08.aac +0 -0
- package/src/assets/audio/bip-bop-08.mp3 +0 -0
- package/src/assets/audio/bip-bop-09.aac +0 -0
- package/src/assets/audio/bip-bop-09.mp3 +0 -0
- package/src/assets/audio/bip-bop-10.aac +0 -0
- package/src/assets/audio/bip-bop-10.mp3 +0 -0
- package/src/assets/audio/nope-01.aac +0 -0
- package/src/assets/audio/nope-01.mp3 +0 -0
- package/src/assets/audio/nope-02.aac +0 -0
- package/src/assets/audio/nope-02.mp3 +0 -0
- package/src/assets/audio/nope-03.aac +0 -0
- package/src/assets/audio/nope-03.mp3 +0 -0
- package/src/assets/audio/nope-04.aac +0 -0
- package/src/assets/audio/nope-04.mp3 +0 -0
- package/src/assets/audio/nope-05.aac +0 -0
- package/src/assets/audio/nope-05.mp3 +0 -0
- package/src/assets/audio/nope-06.aac +0 -0
- package/src/assets/audio/nope-06.mp3 +0 -0
- package/src/assets/audio/nope-07.aac +0 -0
- package/src/assets/audio/nope-07.mp3 +0 -0
- package/src/assets/audio/nope-08.aac +0 -0
- package/src/assets/audio/nope-08.mp3 +0 -0
- package/src/assets/audio/nope-09.aac +0 -0
- package/src/assets/audio/nope-09.mp3 +0 -0
- package/src/assets/audio/nope-10.aac +0 -0
- package/src/assets/audio/nope-10.mp3 +0 -0
- package/src/assets/audio/nope-11.aac +0 -0
- package/src/assets/audio/nope-11.mp3 +0 -0
- package/src/assets/audio/nope-12.aac +0 -0
- package/src/assets/audio/nope-12.mp3 +0 -0
- package/src/assets/audio/staplebops-01.aac +0 -0
- package/src/assets/audio/staplebops-01.mp3 +0 -0
- package/src/assets/audio/staplebops-02.aac +0 -0
- package/src/assets/audio/staplebops-02.mp3 +0 -0
- package/src/assets/audio/staplebops-03.aac +0 -0
- package/src/assets/audio/staplebops-03.mp3 +0 -0
- package/src/assets/audio/staplebops-04.aac +0 -0
- package/src/assets/audio/staplebops-04.mp3 +0 -0
- package/src/assets/audio/staplebops-05.aac +0 -0
- package/src/assets/audio/staplebops-05.mp3 +0 -0
- package/src/assets/audio/staplebops-06.aac +0 -0
- package/src/assets/audio/staplebops-06.mp3 +0 -0
- package/src/assets/audio/staplebops-07.aac +0 -0
- package/src/assets/audio/staplebops-07.mp3 +0 -0
- package/src/assets/audio/yup-01.aac +0 -0
- package/src/assets/audio/yup-01.mp3 +0 -0
- package/src/assets/audio/yup-02.aac +0 -0
- package/src/assets/audio/yup-02.mp3 +0 -0
- package/src/assets/audio/yup-03.aac +0 -0
- package/src/assets/audio/yup-03.mp3 +0 -0
- package/src/assets/audio/yup-04.aac +0 -0
- package/src/assets/audio/yup-04.mp3 +0 -0
- package/src/assets/audio/yup-05.aac +0 -0
- package/src/assets/audio/yup-05.mp3 +0 -0
- package/src/assets/audio/yup-06.aac +0 -0
- package/src/assets/audio/yup-06.mp3 +0 -0
- package/src/assets/fonts/Inter.ttf +0 -0
- package/src/assets/fonts/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
- package/src/assets/icons/app/android-studio.svg +369 -0
- package/src/assets/icons/app/antigravity.svg +97 -0
- package/src/assets/icons/app/cursor.svg +16 -0
- package/src/assets/icons/app/file-explorer.svg +20 -0
- package/src/assets/icons/app/finder.png +0 -0
- package/src/assets/icons/app/ghostty.svg +13 -0
- package/src/assets/icons/app/iterm2.svg +13 -0
- package/src/assets/icons/app/powershell.svg +14 -0
- package/src/assets/icons/app/sublimetext.svg +17 -0
- package/src/assets/icons/app/terminal.png +0 -0
- package/src/assets/icons/app/textmate.png +0 -0
- package/src/assets/icons/app/vscode.svg +39 -0
- package/src/assets/icons/app/warp.png +0 -0
- package/src/assets/icons/app/xcode.png +0 -0
- package/src/assets/icons/app/zed-dark.svg +15 -0
- package/src/assets/icons/app/zed.svg +15 -0
- package/src/components/accordion.css +123 -0
- package/src/components/accordion.tsx +92 -0
- package/src/components/animated-number.css +75 -0
- package/src/components/animated-number.tsx +109 -0
- package/src/components/app-icon.css +5 -0
- package/src/components/app-icon.tsx +85 -0
- package/src/components/app-icons/sprite.svg +114 -0
- package/src/components/app-icons/types.ts +21 -0
- package/src/components/avatar.css +49 -0
- package/src/components/avatar.tsx +55 -0
- package/src/components/button.css +194 -0
- package/src/components/button.tsx +33 -0
- package/src/components/card.css +94 -0
- package/src/components/card.tsx +123 -0
- package/src/components/checkbox.css +131 -0
- package/src/components/checkbox.tsx +43 -0
- package/src/components/collapsible.css +148 -0
- package/src/components/collapsible.tsx +48 -0
- package/src/components/context-menu.css +134 -0
- package/src/components/context-menu.tsx +308 -0
- package/src/components/dialog.css +181 -0
- package/src/components/dialog.tsx +72 -0
- package/src/components/diff-changes.css +42 -0
- package/src/components/diff-changes.tsx +115 -0
- package/src/components/dock-surface.css +23 -0
- package/src/components/dock-surface.tsx +54 -0
- package/src/components/dropdown-menu.css +135 -0
- package/src/components/dropdown-menu.tsx +308 -0
- package/src/components/favicon.tsx +13 -0
- package/src/components/file-icon.css +26 -0
- package/src/components/file-icon.tsx +588 -0
- package/src/components/file-icons/sprite.svg +11707 -0
- package/src/components/file-icons/types.ts +1095 -0
- package/src/components/font.tsx +1 -0
- package/src/components/hover-card.css +61 -0
- package/src/components/hover-card.tsx +32 -0
- package/src/components/icon-button.css +181 -0
- package/src/components/icon-button.tsx +29 -0
- package/src/components/icon.css +34 -0
- package/src/components/icon.tsx +169 -0
- package/src/components/image-preview.css +63 -0
- package/src/components/image-preview.tsx +32 -0
- package/src/components/inline-input.css +17 -0
- package/src/components/inline-input.tsx +22 -0
- package/src/components/keybind.css +18 -0
- package/src/components/keybind.tsx +20 -0
- package/src/components/list.css +331 -0
- package/src/components/list.tsx +394 -0
- package/src/components/logo.css +4 -0
- package/src/components/logo.tsx +62 -0
- package/src/components/motion-spring.tsx +58 -0
- package/src/components/popover.css +98 -0
- package/src/components/popover.tsx +153 -0
- package/src/components/progress-circle.css +12 -0
- package/src/components/progress-circle.tsx +57 -0
- package/src/components/progress.css +63 -0
- package/src/components/progress.tsx +39 -0
- package/src/components/provider-icon.css +5 -0
- package/src/components/provider-icon.tsx +25 -0
- package/src/components/provider-icons/sprite.svg +1135 -0
- package/src/components/provider-icons/types.ts +105 -0
- package/src/components/radio-group.css +187 -0
- package/src/components/radio-group.tsx +83 -0
- package/src/components/resize-handle.css +58 -0
- package/src/components/resize-handle.tsx +82 -0
- package/src/components/scroll-view.css +66 -0
- package/src/components/scroll-view.tsx +250 -0
- package/src/components/select.css +202 -0
- package/src/components/select.tsx +174 -0
- package/src/components/spinner.css +6 -0
- package/src/components/spinner.tsx +52 -0
- package/src/components/sticky-accordion-header.css +6 -0
- package/src/components/sticky-accordion-header.tsx +18 -0
- package/src/components/switch.css +132 -0
- package/src/components/switch.tsx +29 -0
- package/src/components/tabs.css +635 -0
- package/src/components/tabs.tsx +125 -0
- package/src/components/tag.css +37 -0
- package/src/components/tag.tsx +22 -0
- package/src/components/text-field.css +134 -0
- package/src/components/text-field.tsx +128 -0
- package/src/components/text-reveal.css +150 -0
- package/src/components/text-reveal.tsx +143 -0
- package/src/components/text-shimmer.css +119 -0
- package/src/components/text-shimmer.tsx +62 -0
- package/src/components/text-strikethrough.css +27 -0
- package/src/components/text-strikethrough.tsx +84 -0
- package/src/components/toast.css +236 -0
- package/src/components/toast.tsx +185 -0
- package/src/components/tooltip.css +74 -0
- package/src/components/tooltip.tsx +161 -0
- package/src/components/typewriter.css +14 -0
- package/src/components/typewriter.tsx +55 -0
- package/src/context/dialog.tsx +197 -0
- package/src/context/file.tsx +10 -0
- package/src/context/helper.tsx +38 -0
- package/src/context/i18n.tsx +38 -0
- package/src/context/index.ts +4 -0
- package/src/context/marked.tsx +522 -0
- package/src/context/worker-pool.tsx +20 -0
- package/src/custom-elements.d.ts +17 -0
- package/src/hooks/create-auto-scroll.tsx +237 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/use-filtered-list.tsx +134 -0
- package/src/i18n/ar.ts +168 -0
- package/src/i18n/br.ts +168 -0
- package/src/i18n/bs.ts +172 -0
- package/src/i18n/da.ts +167 -0
- package/src/i18n/de.ts +173 -0
- package/src/i18n/en.ts +176 -0
- package/src/i18n/es.ts +168 -0
- package/src/i18n/fr.ts +168 -0
- package/src/i18n/ja.ts +167 -0
- package/src/i18n/ko.ts +168 -0
- package/src/i18n/no.ts +171 -0
- package/src/i18n/pl.ts +167 -0
- package/src/i18n/ru.ts +167 -0
- package/src/i18n/th.ts +169 -0
- package/src/i18n/tr.ts +174 -0
- package/src/i18n/uk.ts +167 -0
- package/src/i18n/zh.ts +171 -0
- package/src/i18n/zht.ts +171 -0
- package/src/storybook/fixtures.ts +51 -0
- package/src/storybook/scaffold.tsx +62 -0
- package/src/styles/animations.css +141 -0
- package/src/styles/base.css +404 -0
- package/src/styles/colors.css +772 -0
- package/src/styles/index.css +53 -0
- package/src/styles/tailwind/colors.css +285 -0
- package/src/styles/tailwind/index.css +78 -0
- package/src/styles/tailwind/utilities.css +131 -0
- package/src/styles/theme.css +609 -0
- package/src/styles/utilities.css +118 -0
- package/src/theme/color.ts +299 -0
- package/src/theme/context.tsx +370 -0
- package/src/theme/default-themes.ts +116 -0
- package/src/theme/index.ts +78 -0
- package/src/theme/loader.ts +112 -0
- package/src/theme/resolve.ts +540 -0
- package/src/theme/themes/amoled.json +49 -0
- package/src/theme/themes/aura.json +51 -0
- package/src/theme/themes/ayu.json +51 -0
- package/src/theme/themes/carbonfox.json +53 -0
- package/src/theme/themes/catppuccin-frappe.json +85 -0
- package/src/theme/themes/catppuccin-macchiato.json +85 -0
- package/src/theme/themes/catppuccin.json +45 -0
- package/src/theme/themes/cobalt2.json +87 -0
- package/src/theme/themes/cursor.json +91 -0
- package/src/theme/themes/dracula.json +49 -0
- package/src/theme/themes/everforest.json +89 -0
- package/src/theme/themes/flexoki.json +86 -0
- package/src/theme/themes/github.json +85 -0
- package/src/theme/themes/gruvbox.json +45 -0
- package/src/theme/themes/kanagawa.json +89 -0
- package/src/theme/themes/lucent-orng.json +87 -0
- package/src/theme/themes/material.json +87 -0
- package/src/theme/themes/matrix.json +113 -0
- package/src/theme/themes/mercury.json +86 -0
- package/src/theme/themes/monokai.json +49 -0
- package/src/theme/themes/nightowl.json +46 -0
- package/src/theme/themes/nord.json +46 -0
- package/src/theme/themes/oc-2.json +468 -0
- package/src/theme/themes/one-dark.json +89 -0
- package/src/theme/themes/onedarkpro.json +45 -0
- package/src/theme/themes/opencode.json +89 -0
- package/src/theme/themes/orng.json +87 -0
- package/src/theme/themes/osaka-jade.json +88 -0
- package/src/theme/themes/palenight.json +85 -0
- package/src/theme/themes/rosepine.json +85 -0
- package/src/theme/themes/shadesofpurple.json +51 -0
- package/src/theme/themes/solarized.json +49 -0
- package/src/theme/themes/synthwave84.json +87 -0
- package/src/theme/themes/tokyonight.json +47 -0
- package/src/theme/themes/vercel.json +90 -0
- package/src/theme/themes/vesper.json +51 -0
- package/src/theme/themes/zenburn.json +87 -0
- package/src/theme/types.ts +75 -0
- package/src/theme/v2/avatar.ts +48 -0
- package/src/theme/v2/default-primitives.ts +114 -0
- package/src/theme/v2/foreground.ts +60 -0
- package/src/theme/v2/mapping.ts +138 -0
- package/src/theme/v2/resolve.ts +153 -0
- package/src/v2/components/accordion-v2.css +139 -0
- package/src/v2/components/accordion-v2.tsx +86 -0
- package/src/v2/components/avatar-v2.css +70 -0
- package/src/v2/components/avatar-v2.tsx +59 -0
- package/src/v2/components/badge-v2.css +27 -0
- package/src/v2/components/badge-v2.tsx +20 -0
- package/src/v2/components/button-v2.css +186 -0
- package/src/v2/components/button-v2.tsx +35 -0
- package/src/v2/components/checkbox-v2.css +184 -0
- package/src/v2/components/checkbox-v2.tsx +65 -0
- package/src/v2/components/dialog-v2.css +150 -0
- package/src/v2/components/dialog-v2.tsx +93 -0
- package/src/v2/components/diff-changes-v2.css +24 -0
- package/src/v2/components/diff-changes-v2.tsx +28 -0
- package/src/v2/components/field-v2.css +94 -0
- package/src/v2/components/field-v2.tsx +265 -0
- package/src/v2/components/icon-button-v2.css +155 -0
- package/src/v2/components/icon-button-v2.tsx +37 -0
- package/src/v2/components/icon.tsx +129 -0
- package/src/v2/components/inline-input-v2.css +218 -0
- package/src/v2/components/inline-input-v2.tsx +90 -0
- package/src/v2/components/keybind-v2.css +76 -0
- package/src/v2/components/keybind-v2.tsx +30 -0
- package/src/v2/components/line-comment-v2.css +204 -0
- package/src/v2/components/line-comment-v2.tsx +155 -0
- package/src/v2/components/menu-v2.css +190 -0
- package/src/v2/components/menu-v2.tsx +225 -0
- package/src/v2/components/project-avatar-v2.css +126 -0
- package/src/v2/components/project-avatar-v2.tsx +64 -0
- package/src/v2/components/radio-v2.css +202 -0
- package/src/v2/components/radio-v2.tsx +72 -0
- package/src/v2/components/segmented-control-v2.css +80 -0
- package/src/v2/components/segmented-control-v2.tsx +208 -0
- package/src/v2/components/select-v2.css +285 -0
- package/src/v2/components/select-v2.tsx +208 -0
- package/src/v2/components/switch-v2.css +154 -0
- package/src/v2/components/switch-v2.tsx +28 -0
- package/src/v2/components/tab-state-indicator.tsx +37 -0
- package/src/v2/components/tabs-v2.css +225 -0
- package/src/v2/components/tabs-v2.tsx +147 -0
- package/src/v2/components/text-input-v2.css +145 -0
- package/src/v2/components/text-input-v2.tsx +67 -0
- package/src/v2/components/text-shimmer-v2.css +125 -0
- package/src/v2/components/text-shimmer-v2.tsx +63 -0
- package/src/v2/components/textarea-v2.css +78 -0
- package/src/v2/components/textarea-v2.tsx +31 -0
- package/src/v2/components/toast-v2.css +215 -0
- package/src/v2/components/toast-v2.tsx +144 -0
- package/src/v2/components/tooltip-v2.css +53 -0
- package/src/v2/components/tooltip-v2.tsx +146 -0
- package/src/v2/components/wordmark-v2.tsx +92 -0
- package/src/v2/styles/colors.css +172 -0
- package/src/v2/styles/tailwind.css +2 -0
- package/src/v2/styles/theme.css +441 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
// @refresh reload
|
|
2
|
+
|
|
3
|
+
import { createEffect, onMount } from "solid-js"
|
|
4
|
+
import { createStore } from "solid-js/store"
|
|
5
|
+
import { makeEventListener } from "@solid-primitives/event-listener"
|
|
6
|
+
import { createSimpleContext } from "../context/helper"
|
|
7
|
+
import oc2ThemeJson from "./themes/oc-2.json"
|
|
8
|
+
import { resolveThemeVariant, themeToCss } from "./resolve"
|
|
9
|
+
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
|
|
10
|
+
import type { DesktopTheme } from "./types"
|
|
11
|
+
|
|
12
|
+
export type ColorScheme = "light" | "dark" | "system"
|
|
13
|
+
|
|
14
|
+
const STORAGE_KEYS = {
|
|
15
|
+
THEME_ID: "opencode-theme-id",
|
|
16
|
+
COLOR_SCHEME: "opencode-color-scheme",
|
|
17
|
+
THEME_CSS_LIGHT: "opencode-theme-css-light",
|
|
18
|
+
THEME_CSS_DARK: "opencode-theme-css-dark",
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
const THEME_STYLE_ID = "oc-theme"
|
|
22
|
+
let files: Record<string, () => Promise<{ default: DesktopTheme }>> | undefined
|
|
23
|
+
let ids: string[] | undefined
|
|
24
|
+
let known: Set<string> | undefined
|
|
25
|
+
|
|
26
|
+
function getFiles() {
|
|
27
|
+
if (files) return files
|
|
28
|
+
files = import.meta.glob<{ default: DesktopTheme }>("./themes/*.json")
|
|
29
|
+
return files
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function themeIDs() {
|
|
33
|
+
if (ids) return ids
|
|
34
|
+
ids = Object.keys(getFiles())
|
|
35
|
+
.map((path) => path.slice("./themes/".length, -".json".length))
|
|
36
|
+
.sort()
|
|
37
|
+
return ids
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function knownThemes() {
|
|
41
|
+
if (known) return known
|
|
42
|
+
known = new Set(themeIDs())
|
|
43
|
+
return known
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const names: Record<string, string> = {
|
|
47
|
+
"oc-2": "OC-2",
|
|
48
|
+
amoled: "AMOLED",
|
|
49
|
+
aura: "Aura",
|
|
50
|
+
ayu: "Ayu",
|
|
51
|
+
carbonfox: "Carbonfox",
|
|
52
|
+
catppuccin: "Catppuccin",
|
|
53
|
+
"catppuccin-frappe": "Catppuccin Frappe",
|
|
54
|
+
"catppuccin-macchiato": "Catppuccin Macchiato",
|
|
55
|
+
cobalt2: "Cobalt2",
|
|
56
|
+
cursor: "Cursor",
|
|
57
|
+
dracula: "Dracula",
|
|
58
|
+
everforest: "Everforest",
|
|
59
|
+
flexoki: "Flexoki",
|
|
60
|
+
github: "GitHub",
|
|
61
|
+
gruvbox: "Gruvbox",
|
|
62
|
+
kanagawa: "Kanagawa",
|
|
63
|
+
"lucent-orng": "Lucent Orng",
|
|
64
|
+
material: "Material",
|
|
65
|
+
matrix: "Matrix",
|
|
66
|
+
mercury: "Mercury",
|
|
67
|
+
monokai: "Monokai",
|
|
68
|
+
nightowl: "Night Owl",
|
|
69
|
+
nord: "Nord",
|
|
70
|
+
"one-dark": "One Dark",
|
|
71
|
+
onedarkpro: "One Dark Pro",
|
|
72
|
+
opencode: "OpenCode",
|
|
73
|
+
orng: "Orng",
|
|
74
|
+
"osaka-jade": "Osaka Jade",
|
|
75
|
+
palenight: "Palenight",
|
|
76
|
+
rosepine: "Rose Pine",
|
|
77
|
+
shadesofpurple: "Shades of Purple",
|
|
78
|
+
solarized: "Solarized",
|
|
79
|
+
synthwave84: "Synthwave '84",
|
|
80
|
+
tokyonight: "Tokyonight",
|
|
81
|
+
vercel: "Vercel",
|
|
82
|
+
vesper: "Vesper",
|
|
83
|
+
zenburn: "Zenburn",
|
|
84
|
+
}
|
|
85
|
+
const oc2Theme = oc2ThemeJson as DesktopTheme
|
|
86
|
+
|
|
87
|
+
function normalize(id: string | null | undefined) {
|
|
88
|
+
return id === "oc-1" ? "oc-2" : id
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function read(key: string) {
|
|
92
|
+
if (typeof localStorage !== "object") return null
|
|
93
|
+
try {
|
|
94
|
+
return localStorage.getItem(key)
|
|
95
|
+
} catch {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function write(key: string, value: string) {
|
|
101
|
+
if (typeof localStorage !== "object") return
|
|
102
|
+
try {
|
|
103
|
+
localStorage.setItem(key, value)
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function drop(key: string) {
|
|
108
|
+
if (typeof localStorage !== "object") return
|
|
109
|
+
try {
|
|
110
|
+
localStorage.removeItem(key)
|
|
111
|
+
} catch {}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function clear() {
|
|
115
|
+
drop(STORAGE_KEYS.THEME_CSS_LIGHT)
|
|
116
|
+
drop(STORAGE_KEYS.THEME_CSS_DARK)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function ensureThemeStyleElement(): HTMLStyleElement {
|
|
120
|
+
const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
|
|
121
|
+
if (existing) return existing
|
|
122
|
+
const element = document.createElement("style")
|
|
123
|
+
element.id = THEME_STYLE_ID
|
|
124
|
+
document.head.appendChild(element)
|
|
125
|
+
return element
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getSystemMode(): "light" | "dark" {
|
|
129
|
+
if (typeof window !== "object") return "light"
|
|
130
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "dark") {
|
|
134
|
+
const isDark = mode === "dark"
|
|
135
|
+
const variant = isDark ? theme.dark : theme.light
|
|
136
|
+
const tokens = resolveThemeVariant(variant, isDark)
|
|
137
|
+
const css = themeToCss(tokens)
|
|
138
|
+
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
|
|
139
|
+
|
|
140
|
+
if (themeId !== "oc-2") {
|
|
141
|
+
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const fullCss = `:root {
|
|
145
|
+
color-scheme: ${mode};
|
|
146
|
+
--text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"};
|
|
147
|
+
${css}
|
|
148
|
+
${v2}
|
|
149
|
+
}`
|
|
150
|
+
|
|
151
|
+
document.getElementById("oc-theme-preload")?.remove()
|
|
152
|
+
ensureThemeStyleElement().textContent = fullCss
|
|
153
|
+
document.documentElement.dataset.theme = themeId
|
|
154
|
+
document.documentElement.dataset.colorScheme = mode
|
|
155
|
+
document.documentElement.style.backgroundColor = isDark ? "#080808" : "#fafafa"
|
|
156
|
+
|
|
157
|
+
// Update theme-color meta tag to match light/dark mode
|
|
158
|
+
const meta = document.querySelector('meta[name="theme-color"]')
|
|
159
|
+
if (meta) meta.setAttribute("content", isDark ? "#080808" : "#fafafa")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
|
|
163
|
+
if (themeId === "oc-2") return
|
|
164
|
+
for (const mode of ["light", "dark"] as const) {
|
|
165
|
+
const isDark = mode === "dark"
|
|
166
|
+
const variant = isDark ? theme.dark : theme.light
|
|
167
|
+
const tokens = resolveThemeVariant(variant, isDark)
|
|
168
|
+
const css = themeToCss(tokens)
|
|
169
|
+
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
|
|
170
|
+
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
|
175
|
+
name: "Theme",
|
|
176
|
+
init: (props: { defaultTheme?: string; onThemeApplied?: (theme: DesktopTheme, mode: "light" | "dark") => void }) => {
|
|
177
|
+
const themeId = normalize(read(STORAGE_KEYS.THEME_ID) ?? props.defaultTheme) ?? "oc-2"
|
|
178
|
+
const colorScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system"
|
|
179
|
+
const mode = colorScheme === "system" ? getSystemMode() : colorScheme
|
|
180
|
+
const [store, setStore] = createStore({
|
|
181
|
+
themes: {
|
|
182
|
+
"oc-2": oc2Theme,
|
|
183
|
+
} as Record<string, DesktopTheme>,
|
|
184
|
+
themeId,
|
|
185
|
+
colorScheme,
|
|
186
|
+
mode,
|
|
187
|
+
previewThemeId: null as string | null,
|
|
188
|
+
previewScheme: null as ColorScheme | null,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const loads = new Map<string, Promise<DesktopTheme | undefined>>()
|
|
192
|
+
|
|
193
|
+
const load = (id: string) => {
|
|
194
|
+
const next = normalize(id)
|
|
195
|
+
if (!next) return Promise.resolve(undefined)
|
|
196
|
+
const hit = store.themes[next]
|
|
197
|
+
if (hit) return Promise.resolve(hit)
|
|
198
|
+
const pending = loads.get(next)
|
|
199
|
+
if (pending) return pending
|
|
200
|
+
const file = getFiles()[`./themes/${next}.json`]
|
|
201
|
+
if (!file) return Promise.resolve(undefined)
|
|
202
|
+
const task = file()
|
|
203
|
+
.then((mod) => {
|
|
204
|
+
const theme = mod.default
|
|
205
|
+
setStore("themes", next, theme)
|
|
206
|
+
return theme
|
|
207
|
+
})
|
|
208
|
+
.finally(() => {
|
|
209
|
+
loads.delete(next)
|
|
210
|
+
})
|
|
211
|
+
loads.set(next, task)
|
|
212
|
+
return task
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const applyTheme = (theme: DesktopTheme, themeId: string, mode: "light" | "dark") => {
|
|
216
|
+
applyThemeCss(theme, themeId, mode)
|
|
217
|
+
props.onThemeApplied?.(theme, mode)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const ids = () => {
|
|
221
|
+
const extra = Object.keys(store.themes)
|
|
222
|
+
.filter((id) => !knownThemes().has(id))
|
|
223
|
+
.sort()
|
|
224
|
+
const all = themeIDs()
|
|
225
|
+
if (extra.length === 0) return all
|
|
226
|
+
return [...all, ...extra]
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const loadThemes = () => Promise.all(themeIDs().map(load)).then(() => store.themes)
|
|
230
|
+
|
|
231
|
+
const onStorage = (e: StorageEvent) => {
|
|
232
|
+
if (e.key === STORAGE_KEYS.THEME_ID && e.newValue) {
|
|
233
|
+
const next = normalize(e.newValue)
|
|
234
|
+
if (!next) return
|
|
235
|
+
if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) return
|
|
236
|
+
setStore("themeId", next)
|
|
237
|
+
if (next === "oc-2") {
|
|
238
|
+
clear()
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
void load(next).then((theme) => {
|
|
242
|
+
if (!theme || store.themeId !== next) return
|
|
243
|
+
cacheThemeVariants(theme, next)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
if (e.key === STORAGE_KEYS.COLOR_SCHEME && e.newValue) {
|
|
247
|
+
setStore("colorScheme", e.newValue as ColorScheme)
|
|
248
|
+
setStore("mode", e.newValue === "system" ? getSystemMode() : (e.newValue as "light" | "dark"))
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
onMount(() => {
|
|
253
|
+
makeEventListener(window, "storage", onStorage)
|
|
254
|
+
|
|
255
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
|
256
|
+
const onMedia = () => {
|
|
257
|
+
if (store.colorScheme !== "system") return
|
|
258
|
+
setStore("mode", getSystemMode())
|
|
259
|
+
}
|
|
260
|
+
makeEventListener(mediaQuery, "change", onMedia)
|
|
261
|
+
|
|
262
|
+
const rawTheme = read(STORAGE_KEYS.THEME_ID)
|
|
263
|
+
const savedTheme = normalize(rawTheme ?? props.defaultTheme) ?? "oc-2"
|
|
264
|
+
const savedScheme = (read(STORAGE_KEYS.COLOR_SCHEME) as ColorScheme | null) ?? "system"
|
|
265
|
+
if (rawTheme && rawTheme !== savedTheme) {
|
|
266
|
+
write(STORAGE_KEYS.THEME_ID, savedTheme)
|
|
267
|
+
clear()
|
|
268
|
+
}
|
|
269
|
+
if (savedTheme !== store.themeId) setStore("themeId", savedTheme)
|
|
270
|
+
if (savedScheme !== store.colorScheme) setStore("colorScheme", savedScheme)
|
|
271
|
+
setStore("mode", savedScheme === "system" ? getSystemMode() : savedScheme)
|
|
272
|
+
void load(savedTheme).then((theme) => {
|
|
273
|
+
if (!theme || store.themeId !== savedTheme) return
|
|
274
|
+
cacheThemeVariants(theme, savedTheme)
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
createEffect(() => {
|
|
279
|
+
const theme = store.themes[store.themeId]
|
|
280
|
+
if (!theme) return
|
|
281
|
+
applyTheme(theme, store.themeId, store.mode)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
const setTheme = (id: string) => {
|
|
285
|
+
const next = normalize(id)
|
|
286
|
+
if (!next) {
|
|
287
|
+
console.warn(`Theme "${id}" not found`)
|
|
288
|
+
return
|
|
289
|
+
}
|
|
290
|
+
if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) {
|
|
291
|
+
console.warn(`Theme "${id}" not found`)
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
setStore("themeId", next)
|
|
295
|
+
if (next === "oc-2") {
|
|
296
|
+
write(STORAGE_KEYS.THEME_ID, next)
|
|
297
|
+
clear()
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
void load(next).then((theme) => {
|
|
301
|
+
if (!theme || store.themeId !== next) return
|
|
302
|
+
cacheThemeVariants(theme, next)
|
|
303
|
+
write(STORAGE_KEYS.THEME_ID, next)
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const setColorScheme = (scheme: ColorScheme) => {
|
|
308
|
+
setStore("colorScheme", scheme)
|
|
309
|
+
write(STORAGE_KEYS.COLOR_SCHEME, scheme)
|
|
310
|
+
setStore("mode", scheme === "system" ? getSystemMode() : scheme)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
themeId: () => store.themeId,
|
|
315
|
+
colorScheme: () => store.colorScheme,
|
|
316
|
+
mode: () => store.mode,
|
|
317
|
+
ids,
|
|
318
|
+
name: (id: string) => store.themes[id]?.name ?? names[id] ?? id,
|
|
319
|
+
loadThemes,
|
|
320
|
+
themes: () => store.themes,
|
|
321
|
+
setTheme,
|
|
322
|
+
setColorScheme,
|
|
323
|
+
registerTheme: (theme: DesktopTheme) => setStore("themes", theme.id, theme),
|
|
324
|
+
previewTheme: (id: string) => {
|
|
325
|
+
const next = normalize(id)
|
|
326
|
+
if (!next) return
|
|
327
|
+
if (next !== "oc-2" && !knownThemes().has(next) && !store.themes[next]) return
|
|
328
|
+
setStore("previewThemeId", next)
|
|
329
|
+
void load(next).then((theme) => {
|
|
330
|
+
if (!theme || store.previewThemeId !== next) return
|
|
331
|
+
const mode = store.previewScheme
|
|
332
|
+
? store.previewScheme === "system"
|
|
333
|
+
? getSystemMode()
|
|
334
|
+
: store.previewScheme
|
|
335
|
+
: store.mode
|
|
336
|
+
applyTheme(theme, next, mode)
|
|
337
|
+
})
|
|
338
|
+
},
|
|
339
|
+
previewColorScheme: (scheme: ColorScheme) => {
|
|
340
|
+
setStore("previewScheme", scheme)
|
|
341
|
+
const mode = scheme === "system" ? getSystemMode() : scheme
|
|
342
|
+
const id = store.previewThemeId ?? store.themeId
|
|
343
|
+
void load(id).then((theme) => {
|
|
344
|
+
if (!theme) return
|
|
345
|
+
if ((store.previewThemeId ?? store.themeId) !== id) return
|
|
346
|
+
if (store.previewScheme !== scheme) return
|
|
347
|
+
applyTheme(theme, id, mode)
|
|
348
|
+
})
|
|
349
|
+
},
|
|
350
|
+
commitPreview: () => {
|
|
351
|
+
if (store.previewThemeId) {
|
|
352
|
+
setTheme(store.previewThemeId)
|
|
353
|
+
}
|
|
354
|
+
if (store.previewScheme) {
|
|
355
|
+
setColorScheme(store.previewScheme)
|
|
356
|
+
}
|
|
357
|
+
setStore("previewThemeId", null)
|
|
358
|
+
setStore("previewScheme", null)
|
|
359
|
+
},
|
|
360
|
+
cancelPreview: () => {
|
|
361
|
+
setStore("previewThemeId", null)
|
|
362
|
+
setStore("previewScheme", null)
|
|
363
|
+
void load(store.themeId).then((theme) => {
|
|
364
|
+
if (!theme) return
|
|
365
|
+
applyTheme(theme, store.themeId, store.mode)
|
|
366
|
+
})
|
|
367
|
+
},
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
})
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { DesktopTheme } from "./types"
|
|
2
|
+
import oc2ThemeJson from "./themes/oc-2.json"
|
|
3
|
+
import amoledThemeJson from "./themes/amoled.json"
|
|
4
|
+
import auraThemeJson from "./themes/aura.json"
|
|
5
|
+
import ayuThemeJson from "./themes/ayu.json"
|
|
6
|
+
import carbonfoxThemeJson from "./themes/carbonfox.json"
|
|
7
|
+
import catppuccinThemeJson from "./themes/catppuccin.json"
|
|
8
|
+
import catppuccinFrappeThemeJson from "./themes/catppuccin-frappe.json"
|
|
9
|
+
import catppuccinMacchiatoThemeJson from "./themes/catppuccin-macchiato.json"
|
|
10
|
+
import cobalt2ThemeJson from "./themes/cobalt2.json"
|
|
11
|
+
import cursorThemeJson from "./themes/cursor.json"
|
|
12
|
+
import draculaThemeJson from "./themes/dracula.json"
|
|
13
|
+
import everforestThemeJson from "./themes/everforest.json"
|
|
14
|
+
import flexokiThemeJson from "./themes/flexoki.json"
|
|
15
|
+
import githubThemeJson from "./themes/github.json"
|
|
16
|
+
import gruvboxThemeJson from "./themes/gruvbox.json"
|
|
17
|
+
import kanagawaThemeJson from "./themes/kanagawa.json"
|
|
18
|
+
import lucentOrngThemeJson from "./themes/lucent-orng.json"
|
|
19
|
+
import materialThemeJson from "./themes/material.json"
|
|
20
|
+
import matrixThemeJson from "./themes/matrix.json"
|
|
21
|
+
import mercuryThemeJson from "./themes/mercury.json"
|
|
22
|
+
import monokaiThemeJson from "./themes/monokai.json"
|
|
23
|
+
import nightowlThemeJson from "./themes/nightowl.json"
|
|
24
|
+
import nordThemeJson from "./themes/nord.json"
|
|
25
|
+
import oneDarkThemeJson from "./themes/one-dark.json"
|
|
26
|
+
import oneDarkProThemeJson from "./themes/onedarkpro.json"
|
|
27
|
+
import opencodeThemeJson from "./themes/opencode.json"
|
|
28
|
+
import orngThemeJson from "./themes/orng.json"
|
|
29
|
+
import osakaJadeThemeJson from "./themes/osaka-jade.json"
|
|
30
|
+
import palenightThemeJson from "./themes/palenight.json"
|
|
31
|
+
import rosepineThemeJson from "./themes/rosepine.json"
|
|
32
|
+
import shadesOfPurpleThemeJson from "./themes/shadesofpurple.json"
|
|
33
|
+
import solarizedThemeJson from "./themes/solarized.json"
|
|
34
|
+
import synthwave84ThemeJson from "./themes/synthwave84.json"
|
|
35
|
+
import tokyonightThemeJson from "./themes/tokyonight.json"
|
|
36
|
+
import vercelThemeJson from "./themes/vercel.json"
|
|
37
|
+
import vesperThemeJson from "./themes/vesper.json"
|
|
38
|
+
import zenburnThemeJson from "./themes/zenburn.json"
|
|
39
|
+
|
|
40
|
+
export const oc2Theme = oc2ThemeJson as DesktopTheme
|
|
41
|
+
export const amoledTheme = amoledThemeJson as DesktopTheme
|
|
42
|
+
export const auraTheme = auraThemeJson as DesktopTheme
|
|
43
|
+
export const ayuTheme = ayuThemeJson as DesktopTheme
|
|
44
|
+
export const carbonfoxTheme = carbonfoxThemeJson as DesktopTheme
|
|
45
|
+
export const catppuccinTheme = catppuccinThemeJson as DesktopTheme
|
|
46
|
+
export const catppuccinFrappeTheme = catppuccinFrappeThemeJson as DesktopTheme
|
|
47
|
+
export const catppuccinMacchiatoTheme = catppuccinMacchiatoThemeJson as DesktopTheme
|
|
48
|
+
export const cobalt2Theme = cobalt2ThemeJson as DesktopTheme
|
|
49
|
+
export const cursorTheme = cursorThemeJson as DesktopTheme
|
|
50
|
+
export const draculaTheme = draculaThemeJson as DesktopTheme
|
|
51
|
+
export const everforestTheme = everforestThemeJson as DesktopTheme
|
|
52
|
+
export const flexokiTheme = flexokiThemeJson as DesktopTheme
|
|
53
|
+
export const githubTheme = githubThemeJson as DesktopTheme
|
|
54
|
+
export const gruvboxTheme = gruvboxThemeJson as DesktopTheme
|
|
55
|
+
export const kanagawaTheme = kanagawaThemeJson as DesktopTheme
|
|
56
|
+
export const lucentOrngTheme = lucentOrngThemeJson as DesktopTheme
|
|
57
|
+
export const materialTheme = materialThemeJson as DesktopTheme
|
|
58
|
+
export const matrixTheme = matrixThemeJson as DesktopTheme
|
|
59
|
+
export const mercuryTheme = mercuryThemeJson as DesktopTheme
|
|
60
|
+
export const monokaiTheme = monokaiThemeJson as DesktopTheme
|
|
61
|
+
export const nightowlTheme = nightowlThemeJson as DesktopTheme
|
|
62
|
+
export const nordTheme = nordThemeJson as DesktopTheme
|
|
63
|
+
export const oneDarkTheme = oneDarkThemeJson as DesktopTheme
|
|
64
|
+
export const oneDarkProTheme = oneDarkProThemeJson as DesktopTheme
|
|
65
|
+
export const opencodeTheme = opencodeThemeJson as DesktopTheme
|
|
66
|
+
export const orngTheme = orngThemeJson as DesktopTheme
|
|
67
|
+
export const osakaJadeTheme = osakaJadeThemeJson as DesktopTheme
|
|
68
|
+
export const palenightTheme = palenightThemeJson as DesktopTheme
|
|
69
|
+
export const rosepineTheme = rosepineThemeJson as DesktopTheme
|
|
70
|
+
export const shadesOfPurpleTheme = shadesOfPurpleThemeJson as DesktopTheme
|
|
71
|
+
export const solarizedTheme = solarizedThemeJson as DesktopTheme
|
|
72
|
+
export const synthwave84Theme = synthwave84ThemeJson as DesktopTheme
|
|
73
|
+
export const tokyonightTheme = tokyonightThemeJson as DesktopTheme
|
|
74
|
+
export const vercelTheme = vercelThemeJson as DesktopTheme
|
|
75
|
+
export const vesperTheme = vesperThemeJson as DesktopTheme
|
|
76
|
+
export const zenburnTheme = zenburnThemeJson as DesktopTheme
|
|
77
|
+
|
|
78
|
+
export const DEFAULT_THEMES: Record<string, DesktopTheme> = {
|
|
79
|
+
"oc-2": oc2Theme,
|
|
80
|
+
amoled: amoledTheme,
|
|
81
|
+
aura: auraTheme,
|
|
82
|
+
ayu: ayuTheme,
|
|
83
|
+
carbonfox: carbonfoxTheme,
|
|
84
|
+
catppuccin: catppuccinTheme,
|
|
85
|
+
"catppuccin-frappe": catppuccinFrappeTheme,
|
|
86
|
+
"catppuccin-macchiato": catppuccinMacchiatoTheme,
|
|
87
|
+
cobalt2: cobalt2Theme,
|
|
88
|
+
cursor: cursorTheme,
|
|
89
|
+
dracula: draculaTheme,
|
|
90
|
+
everforest: everforestTheme,
|
|
91
|
+
flexoki: flexokiTheme,
|
|
92
|
+
github: githubTheme,
|
|
93
|
+
gruvbox: gruvboxTheme,
|
|
94
|
+
kanagawa: kanagawaTheme,
|
|
95
|
+
"lucent-orng": lucentOrngTheme,
|
|
96
|
+
material: materialTheme,
|
|
97
|
+
matrix: matrixTheme,
|
|
98
|
+
mercury: mercuryTheme,
|
|
99
|
+
monokai: monokaiTheme,
|
|
100
|
+
nightowl: nightowlTheme,
|
|
101
|
+
nord: nordTheme,
|
|
102
|
+
"one-dark": oneDarkTheme,
|
|
103
|
+
onedarkpro: oneDarkProTheme,
|
|
104
|
+
opencode: opencodeTheme,
|
|
105
|
+
orng: orngTheme,
|
|
106
|
+
"osaka-jade": osakaJadeTheme,
|
|
107
|
+
palenight: palenightTheme,
|
|
108
|
+
rosepine: rosepineTheme,
|
|
109
|
+
shadesofpurple: shadesOfPurpleTheme,
|
|
110
|
+
solarized: solarizedTheme,
|
|
111
|
+
synthwave84: synthwave84Theme,
|
|
112
|
+
tokyonight: tokyonightTheme,
|
|
113
|
+
vercel: vercelTheme,
|
|
114
|
+
vesper: vesperTheme,
|
|
115
|
+
zenburn: zenburnTheme,
|
|
116
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
export type {
|
|
2
|
+
DesktopTheme,
|
|
3
|
+
ThemePaletteColors,
|
|
4
|
+
ThemeSeedColors,
|
|
5
|
+
ThemeVariant,
|
|
6
|
+
HexColor,
|
|
7
|
+
OklchColor,
|
|
8
|
+
ResolvedTheme,
|
|
9
|
+
ColorValue,
|
|
10
|
+
CssVarRef,
|
|
11
|
+
V2ColorValue,
|
|
12
|
+
ResolvedV2Theme,
|
|
13
|
+
} from "./types"
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
hexToRgb,
|
|
17
|
+
rgbToHex,
|
|
18
|
+
hexToOklch,
|
|
19
|
+
oklchToHex,
|
|
20
|
+
rgbToOklch,
|
|
21
|
+
oklchToRgb,
|
|
22
|
+
generateScale,
|
|
23
|
+
generateNeutralScale,
|
|
24
|
+
generateAlphaScale,
|
|
25
|
+
fitOklch,
|
|
26
|
+
blend,
|
|
27
|
+
mixColors,
|
|
28
|
+
shift,
|
|
29
|
+
lighten,
|
|
30
|
+
darken,
|
|
31
|
+
withAlpha,
|
|
32
|
+
} from "./color"
|
|
33
|
+
|
|
34
|
+
export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve"
|
|
35
|
+
export { resolveThemeVariantV2, resolveThemeV2, themeV2ToCss, generateV2Primitives } from "./v2/resolve"
|
|
36
|
+
export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader"
|
|
37
|
+
export { ThemeProvider, useTheme, type ColorScheme } from "./context"
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
DEFAULT_THEMES,
|
|
41
|
+
oc2Theme,
|
|
42
|
+
amoledTheme,
|
|
43
|
+
auraTheme,
|
|
44
|
+
ayuTheme,
|
|
45
|
+
carbonfoxTheme,
|
|
46
|
+
catppuccinTheme,
|
|
47
|
+
catppuccinFrappeTheme,
|
|
48
|
+
catppuccinMacchiatoTheme,
|
|
49
|
+
cobalt2Theme,
|
|
50
|
+
cursorTheme,
|
|
51
|
+
draculaTheme,
|
|
52
|
+
everforestTheme,
|
|
53
|
+
flexokiTheme,
|
|
54
|
+
githubTheme,
|
|
55
|
+
gruvboxTheme,
|
|
56
|
+
kanagawaTheme,
|
|
57
|
+
lucentOrngTheme,
|
|
58
|
+
materialTheme,
|
|
59
|
+
matrixTheme,
|
|
60
|
+
mercuryTheme,
|
|
61
|
+
monokaiTheme,
|
|
62
|
+
nightowlTheme,
|
|
63
|
+
nordTheme,
|
|
64
|
+
oneDarkTheme,
|
|
65
|
+
oneDarkProTheme,
|
|
66
|
+
opencodeTheme,
|
|
67
|
+
orngTheme,
|
|
68
|
+
osakaJadeTheme,
|
|
69
|
+
palenightTheme,
|
|
70
|
+
rosepineTheme,
|
|
71
|
+
shadesOfPurpleTheme,
|
|
72
|
+
solarizedTheme,
|
|
73
|
+
synthwave84Theme,
|
|
74
|
+
tokyonightTheme,
|
|
75
|
+
vercelTheme,
|
|
76
|
+
vesperTheme,
|
|
77
|
+
zenburnTheme,
|
|
78
|
+
} from "./default-themes"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { DesktopTheme, ResolvedTheme, ResolvedV2Theme } from "./types"
|
|
2
|
+
import { resolveThemeVariant, themeToCss } from "./resolve"
|
|
3
|
+
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
|
|
4
|
+
|
|
5
|
+
let activeTheme: DesktopTheme | null = null
|
|
6
|
+
const THEME_STYLE_ID = "opencode-theme"
|
|
7
|
+
|
|
8
|
+
function ensureLoaderStyleElement(): HTMLStyleElement {
|
|
9
|
+
const existing = document.getElementById(THEME_STYLE_ID) as HTMLStyleElement | null
|
|
10
|
+
if (existing) {
|
|
11
|
+
return existing
|
|
12
|
+
}
|
|
13
|
+
const element = document.createElement("style")
|
|
14
|
+
element.id = THEME_STYLE_ID
|
|
15
|
+
document.head.appendChild(element)
|
|
16
|
+
return element
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function applyTheme(theme: DesktopTheme, themeId?: string): void {
|
|
20
|
+
activeTheme = theme
|
|
21
|
+
const lightTokens = resolveThemeVariant(theme.light, false)
|
|
22
|
+
const darkTokens = resolveThemeVariant(theme.dark, true)
|
|
23
|
+
const lightV2Tokens = resolveThemeVariantV2(theme.light, false)
|
|
24
|
+
const darkV2Tokens = resolveThemeVariantV2(theme.dark, true)
|
|
25
|
+
const targetThemeId = themeId ?? theme.id
|
|
26
|
+
const css = buildThemeCss(lightTokens, darkTokens, lightV2Tokens, darkV2Tokens, targetThemeId)
|
|
27
|
+
const themeStyleElement = ensureLoaderStyleElement()
|
|
28
|
+
themeStyleElement.textContent = css
|
|
29
|
+
document.documentElement.setAttribute("data-theme", targetThemeId)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function buildThemeCss(
|
|
33
|
+
light: ResolvedTheme,
|
|
34
|
+
dark: ResolvedTheme,
|
|
35
|
+
lightV2: ResolvedV2Theme,
|
|
36
|
+
darkV2: ResolvedV2Theme,
|
|
37
|
+
themeId: string,
|
|
38
|
+
): string {
|
|
39
|
+
const isDefaultTheme = themeId === "oc-2"
|
|
40
|
+
const lightCss = `${themeToCss(light)}\n ${themeV2ToCss(lightV2)}`
|
|
41
|
+
const darkCss = `${themeToCss(dark)}\n ${themeV2ToCss(darkV2)}`
|
|
42
|
+
|
|
43
|
+
if (isDefaultTheme) {
|
|
44
|
+
return `
|
|
45
|
+
:root {
|
|
46
|
+
color-scheme: light;
|
|
47
|
+
--text-mix-blend-mode: multiply;
|
|
48
|
+
|
|
49
|
+
${lightCss}
|
|
50
|
+
|
|
51
|
+
@media (prefers-color-scheme: dark) {
|
|
52
|
+
color-scheme: dark;
|
|
53
|
+
--text-mix-blend-mode: plus-lighter;
|
|
54
|
+
|
|
55
|
+
${darkCss}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `
|
|
62
|
+
html[data-theme="${themeId}"] {
|
|
63
|
+
color-scheme: light;
|
|
64
|
+
--text-mix-blend-mode: multiply;
|
|
65
|
+
|
|
66
|
+
${lightCss}
|
|
67
|
+
|
|
68
|
+
@media (prefers-color-scheme: dark) {
|
|
69
|
+
color-scheme: dark;
|
|
70
|
+
--text-mix-blend-mode: plus-lighter;
|
|
71
|
+
|
|
72
|
+
${darkCss}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function loadThemeFromUrl(url: string): Promise<DesktopTheme> {
|
|
79
|
+
const response = await fetch(url)
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`Failed to load theme from ${url}: ${response.statusText}`)
|
|
82
|
+
}
|
|
83
|
+
return response.json()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getActiveTheme(): DesktopTheme | null {
|
|
87
|
+
const activeId = document.documentElement.getAttribute("data-theme")
|
|
88
|
+
if (!activeId) {
|
|
89
|
+
return null
|
|
90
|
+
}
|
|
91
|
+
if (activeTheme?.id === activeId) {
|
|
92
|
+
return activeTheme
|
|
93
|
+
}
|
|
94
|
+
return null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function removeTheme(): void {
|
|
98
|
+
activeTheme = null
|
|
99
|
+
const existingElement = document.getElementById(THEME_STYLE_ID)
|
|
100
|
+
if (existingElement) {
|
|
101
|
+
existingElement.remove()
|
|
102
|
+
}
|
|
103
|
+
document.documentElement.removeAttribute("data-theme")
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function setColorScheme(scheme: "light" | "dark" | "auto"): void {
|
|
107
|
+
if (scheme === "auto") {
|
|
108
|
+
document.documentElement.style.removeProperty("color-scheme")
|
|
109
|
+
} else {
|
|
110
|
+
document.documentElement.style.setProperty("color-scheme", scheme)
|
|
111
|
+
}
|
|
112
|
+
}
|