feat(app): color themes (#30824)

Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
Aarav Sareen
2026-06-05 00:53:36 +00:00
committed by GitHub
co-authored by LukeParkerDev
parent 75557000de
commit 605ae48c6f
23 changed files with 1040 additions and 87 deletions
@@ -16,14 +16,8 @@ export const DialogSettings: Component = () => {
const platform = usePlatform()
return (
<Dialog size="x-large" class="settings-v2-dialog" data-component="settings-v2-dialog">
<TabsV2
orientation="vertical"
variant="settings"
defaultValue="general"
class="settings-v2"
data-component="settings-v2"
>
<Dialog size="x-large" variant="settings" class="settings-v2-dialog">
<TabsV2 orientation="vertical" variant="settings" defaultValue="general" class="settings-v2">
<TabsV2.List>
<div class="flex flex-col justify-between h-full w-full">
<div class="flex flex-col gap-3 w-full">
@@ -1,11 +1,15 @@
@import "@opencode-ai/ui/v2/text-input-v2.css";
@import "@opencode-ai/ui/v2/button-v2.css";
[data-component="settings-v2"] {
[data-component="tabs-v2"][data-variant="settings"] {
height: 100%;
}
[data-component="settings-v2-dialog"] [data-slot="dialog-body"] {
[data-component="dialog-v2"][data-variant="settings"] [data-slot="dialog-container"] {
background: var(--v2-background-bg-base);
}
[data-component="dialog-v2"][data-variant="settings"] [data-slot="dialog-body"] {
padding: 0;
overflow: hidden;
}
@@ -81,10 +85,6 @@
box-shadow: inset 0 0 0 0.5px var(--v2-border-border-muted);
}
[data-slot="dialog-container"]:has(.settings-v2-dialog) {
box-shadow: var(--v2-elevation-overlay);
}
[data-component="settings-v2-row"] {
display: flex;
flex-wrap: wrap;
@@ -153,12 +153,12 @@
width: 100%;
}
[data-component="settings-v2-dialog"] [data-component="select-v2-root"] {
[data-component="dialog-v2"][data-variant="settings"] [data-component="select-v2-root"] {
width: fit-content;
max-width: 100%;
}
[data-component="settings-v2-dialog"] [data-component="button-v2"] {
[data-component="dialog-v2"][data-variant="settings"] [data-component="button-v2"] {
width: fit-content;
max-width: 100%;
}
+1 -1
View File
@@ -270,7 +270,7 @@ function HomeDesign() {
}
return (
<div class="rounded-[10px] shadow-[var(--v2-elevation-raised)] m-2 bg-background-base self-stretch flex-1">
<div class="rounded-[10px] shadow-[var(--v2-elevation-raised)] m-2 bg-v2-background-bg-base self-stretch flex-1">
<div class="mx-auto grid w-full h-full max-w-[1080px] gap-8 px-6 pb-16 lg:grid-cols-[280px_minmax(0,720px)]">
<HomeProjectColumn
projects={projects()}
+1 -1
View File
@@ -24,7 +24,7 @@ import { initI18n, t } from "./i18n"
import { initializationData, initializationReady } from "./initialization"
import { resetZoom, setPinchZoomEnabled, webviewZoom, zoomIn, zoomOut } from "./webview-zoom"
import "./styles.css"
import { useTheme } from "@opencode-ai/ui/theme"
import { useTheme } from "@opencode-ai/ui/theme/context"
const root = document.getElementById("root")
if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
+2 -1
View File
@@ -32,7 +32,8 @@
"test": "bun test src",
"test:ci": "mkdir -p .artifacts/unit && bun test src --reporter=junit --reporter-outfile=.artifacts/unit/junit.xml",
"dev": "vite",
"generate:tailwind": "bun run script/tailwind.ts"
"generate:tailwind": "bun run script/tailwind.ts",
"generate:v2-oc2": "bun run script/build-oc2-v2-overrides.ts"
},
"devDependencies": {
"@tailwindcss/vite": "catalog:",
@@ -0,0 +1,32 @@
#!/usr/bin/env bun
import { V2_PRIMITIVES_DEFAULT } from "../src/theme/v2/default-primitives"
import type { DesktopTheme } from "../src/theme/types"
const themePath = import.meta.dir + "/../src/theme/themes/oc-2.json"
const theme = (await Bun.file(themePath).json()) as DesktopTheme
const css = await Bun.file(import.meta.dir + "/../src/v2/styles/theme.css").text()
const light = { ...V2_PRIMITIVES_DEFAULT, ...readTokens("light") }
const dark = { ...V2_PRIMITIVES_DEFAULT, ...readTokens("dark") }
const next: DesktopTheme = {
...theme,
light: { ...theme.light, v2Overrides: light },
dark: { ...theme.dark, v2Overrides: dark },
}
await Bun.write(themePath, JSON.stringify(next, null, 2) + "\n")
console.log("Updated oc-2.json v2Overrides", Object.keys(light).length, "tokens per mode")
function readTokens(mode: "light" | "dark") {
const selector = mode === "light" ? ":root" : `\\[data-color-scheme="${mode}"\\]`
const block = css.match(new RegExp(`${selector} \\{([\\s\\S]*?)\\n \\}`))?.[1]
if (!block) throw new Error(`Missing ${mode} OC-2 tokens`)
return Object.fromEntries(
[...block.matchAll(/--(v2-[\w-]+):\s*([^;]+);/g)]
// Fonts and the fixed avatar foreground remain global CSS rather than theme overrides.
.filter(([, key]) => key !== "v2-avatar-fg" && key !== "v2-font-family-sans")
.map(([, key, value]) => [key, value!.replace(/\s+/g, " ").trim()]),
)
}
+8 -2
View File
@@ -1,9 +1,12 @@
// @refresh reload
import { createEffect, onMount } from "solid-js"
import { createStore } from "solid-js/store"
import { makeEventListener } from "@solid-primitives/event-listener"
import { createSimpleContext } from "../context/helper"
import oc2ThemeJson from "./themes/oc-2.json"
import { resolveThemeVariant, themeToCss } from "./resolve"
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
import type { DesktopTheme } from "./types"
export type ColorScheme = "light" | "dark" | "system"
@@ -132,15 +135,17 @@ function applyThemeCss(theme: DesktopTheme, themeId: string, mode: "light" | "da
const variant = isDark ? theme.dark : theme.light
const tokens = resolveThemeVariant(variant, isDark)
const css = themeToCss(tokens)
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
if (themeId !== "oc-2") {
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
}
const fullCss = `:root {
color-scheme: ${mode};
--text-mix-blend-mode: ${isDark ? "plus-lighter" : "multiply"};
${css}
${v2}
}`
document.getElementById("oc-theme-preload")?.remove()
@@ -160,7 +165,8 @@ function cacheThemeVariants(theme: DesktopTheme, themeId: string) {
const variant = isDark ? theme.dark : theme.light
const tokens = resolveThemeVariant(variant, isDark)
const css = themeToCss(tokens)
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, css)
const v2 = themeV2ToCss(resolveThemeVariantV2(variant, isDark))
write(isDark ? STORAGE_KEYS.THEME_CSS_DARK : STORAGE_KEYS.THEME_CSS_LIGHT, `${css}\n ${v2}`)
}
}
@@ -154,6 +154,13 @@
"additionalProperties": {
"$ref": "#/definitions/ColorValue"
}
},
"v2Overrides": {
"type": "object",
"description": "Optional direct overrides for any v2 CSS variable (without -- prefix)",
"additionalProperties": {
"type": "string"
}
}
}
}
+3
View File
@@ -8,6 +8,8 @@ export type {
ResolvedTheme,
ColorValue,
CssVarRef,
V2ColorValue,
ResolvedV2Theme,
} from "./types"
export {
@@ -30,6 +32,7 @@ export {
} from "./color"
export { resolveThemeVariant, resolveTheme, themeToCss } from "./resolve"
export { resolveThemeVariantV2, resolveThemeV2, themeV2ToCss, generateV2Primitives } from "./v2/resolve"
export { applyTheme, loadThemeFromUrl, getActiveTheme, removeTheme, setColorScheme } from "./loader"
export { ThemeProvider, useTheme, type ColorScheme } from "./context"
+14 -5
View File
@@ -1,5 +1,6 @@
import type { DesktopTheme, ResolvedTheme } from "./types"
import type { DesktopTheme, ResolvedTheme, ResolvedV2Theme } from "./types"
import { resolveThemeVariant, themeToCss } from "./resolve"
import { resolveThemeVariantV2, themeV2ToCss } from "./v2/resolve"
let activeTheme: DesktopTheme | null = null
const THEME_STYLE_ID = "opencode-theme"
@@ -19,17 +20,25 @@ export function applyTheme(theme: DesktopTheme, themeId?: string): void {
activeTheme = theme
const lightTokens = resolveThemeVariant(theme.light, false)
const darkTokens = resolveThemeVariant(theme.dark, true)
const lightV2Tokens = resolveThemeVariantV2(theme.light, false)
const darkV2Tokens = resolveThemeVariantV2(theme.dark, true)
const targetThemeId = themeId ?? theme.id
const css = buildThemeCss(lightTokens, darkTokens, targetThemeId)
const css = buildThemeCss(lightTokens, darkTokens, lightV2Tokens, darkV2Tokens, targetThemeId)
const themeStyleElement = ensureLoaderStyleElement()
themeStyleElement.textContent = css
document.documentElement.setAttribute("data-theme", targetThemeId)
}
function buildThemeCss(light: ResolvedTheme, dark: ResolvedTheme, themeId: string): string {
function buildThemeCss(
light: ResolvedTheme,
dark: ResolvedTheme,
lightV2: ResolvedV2Theme,
darkV2: ResolvedV2Theme,
themeId: string,
): string {
const isDefaultTheme = themeId === "oc-2"
const lightCss = themeToCss(light)
const darkCss = themeToCss(dark)
const lightCss = `${themeToCss(light)}\n ${themeV2ToCss(lightV2)}`
const darkCss = `${themeToCss(dark)}\n ${themeV2ToCss(darkV2)}`
if (isDefaultTheme) {
return `
+22
View File
@@ -43,6 +43,17 @@
"markdown-image": "#30b3ff",
"markdown-image-text": "#24f6d9",
"markdown-code-block": "#203022"
},
"v2Overrides": {
"v2-text-text-base": "#353535",
"v2-text-text-muted": "#748476",
"v2-text-text-faint": "#748476",
"v2-background-bg-accent": "var(--v2-green-600)",
"v2-text-text-accent": "var(--v2-green-600)",
"v2-text-text-accent-hover": "var(--v2-green-700)",
"v2-icon-icon-accent": "var(--v2-green-600)",
"v2-icon-icon-accent-hover": "var(--v2-green-700)",
"v2-border-border-focus": "var(--v2-green-500)"
}
},
"dark": {
@@ -86,6 +97,17 @@
"markdown-image": "#30b3ff",
"markdown-image-text": "#24f6d9",
"markdown-code-block": "#62ff94"
},
"v2Overrides": {
"v2-text-text-base": "#ececec",
"v2-text-text-muted": "#8ca391",
"v2-text-text-faint": "#8ca391",
"v2-background-bg-accent": "var(--v2-green-600)",
"v2-text-text-accent": "var(--v2-green-400)",
"v2-text-text-accent-hover": "var(--v2-green-300)",
"v2-icon-icon-accent": "var(--v2-green-400)",
"v2-icon-icon-accent-hover": "var(--v2-green-300)",
"v2-border-border-focus": "var(--v2-green-500)"
}
}
}
+376
View File
@@ -42,6 +42,194 @@
"syntax-diff-delete": "#ff8c00",
"syntax-diff-unknown": "#a753ae",
"surface-critical-base": "#FFF2F0"
},
"v2Overrides": {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
"v2-background-bg-base": "var(--v2-grey-100)",
"v2-background-bg-deep": "var(--v2-grey-200)",
"v2-background-bg-layer-01": "var(--v2-grey-200)",
"v2-background-bg-layer-02": "var(--v2-grey-300)",
"v2-background-bg-layer-03": "var(--v2-grey-400)",
"v2-background-bg-inverse": "var(--v2-grey-1000)",
"v2-background-bg-contrast": "var(--v2-grey-900)",
"v2-background-bg-button-neutral": "var(--v2-grey-100)",
"v2-background-bg-accent": "var(--v2-blue-600)",
"v2-text-text-base": "var(--v2-grey-1000)",
"v2-text-text-muted": "var(--v2-grey-700)",
"v2-text-text-faint": "var(--v2-grey-600)",
"v2-text-text-inverse": "var(--v2-grey-100)",
"v2-text-text-contrast": "var(--v2-grey-100)",
"v2-text-text-accent": "var(--v2-blue-600)",
"v2-text-text-accent-hover": "var(--v2-blue-700)",
"v2-icon-icon-base": "var(--v2-grey-800)",
"v2-icon-icon-muted": "var(--v2-grey-600)",
"v2-icon-icon-inverse": "var(--v2-grey-100)",
"v2-icon-icon-contrast": "var(--v2-grey-200)",
"v2-icon-icon-accent": "var(--v2-blue-600)",
"v2-icon-icon-accent-hover": "var(--v2-blue-700)",
"v2-border-border-muted": "var(--v2-alpha-dark-8)",
"v2-border-border-base": "var(--v2-alpha-dark-10)",
"v2-border-border-strong": "var(--v2-alpha-dark-20)",
"v2-border-border-inverse": "var(--v2-grey-1000)",
"v2-border-border-focus": "var(--v2-blue-500)",
"v2-overlay-simple-overlay-hover": "var(--v2-alpha-dark-4)",
"v2-overlay-simple-overlay-pressed": "var(--v2-alpha-dark-8)",
"v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-light-12)",
"v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-light-24)",
"v2-overlay-simple-overlay-scrim": "var(--v2-alpha-dark-40)",
"v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)",
"v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)",
"v2-overlay-simple-tab-active-scrim": "#fafafa00",
"v2-overlay-simple-tab-hover-scrim": "#eeeeee00",
"v2-overlay-simple-tab-scrim": "#fafafa00",
"v2-state-bg-success": "var(--v2-green-100)",
"v2-state-fg-success": "var(--v2-green-800)",
"v2-state-border-success": "var(--v2-green-300)",
"v2-state-bg-warning": "var(--v2-yellow-100)",
"v2-state-fg-warning": "var(--v2-yellow-800)",
"v2-state-border-warning": "var(--v2-yellow-300)",
"v2-state-bg-danger": "var(--v2-red-100)",
"v2-state-fg-danger": "var(--v2-red-800)",
"v2-state-border-danger": "var(--v2-red-300)",
"v2-state-bg-info": "var(--v2-blue-100)",
"v2-state-fg-info": "var(--v2-blue-800)",
"v2-state-border-info": "var(--v2-blue-300)",
"v2-avatar-bg-orange": "#ee7330ff",
"v2-avatar-border-orange": "#d16427ff",
"v2-avatar-bg-yellow": "#e7af36ff",
"v2-avatar-border-yellow": "#cb9f34ff",
"v2-avatar-bg-cyan": "#0096b8ff",
"v2-avatar-border-cyan": "#007d9bff",
"v2-avatar-bg-green": "#2eaf5aff",
"v2-avatar-border-green": "#198b43ff",
"v2-avatar-bg-red": "#d92e3cff",
"v2-avatar-border-red": "#b82d35ff",
"v2-avatar-bg-pink": "#e4429eff",
"v2-avatar-border-pink": "#c83d8bff",
"v2-avatar-bg-blue": "#3250dfff",
"v2-avatar-border-blue": "#2c47c8ff",
"v2-avatar-bg-purple": "#623be2ff",
"v2-avatar-border-purple": "#5230c2ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#3a3a3aff",
"v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-neutral": "0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-contrast": "0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off": "inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)",
"v2-elevation-switch-on": "inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)",
"v2-illustration-illustration-layer-01": "var(--v2-grey-300)",
"v2-illustration-illustration-layer-02": "var(--v2-grey-400)",
"v2-illustration-illustration-layer-03": "var(--v2-grey-500)"
}
},
"dark": {
@@ -83,6 +271,194 @@
"syntax-diff-delete": "#fab283",
"syntax-diff-unknown": "#edb2f1",
"surface-critical-base": "#1F0603"
},
"v2Overrides": {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
"v2-background-bg-base": "var(--v2-grey-1000)",
"v2-background-bg-deep": "var(--v2-grey-1100)",
"v2-background-bg-layer-01": "var(--v2-grey-900)",
"v2-background-bg-layer-02": "var(--v2-grey-800)",
"v2-background-bg-layer-03": "var(--v2-grey-700)",
"v2-background-bg-inverse": "var(--v2-grey-100)",
"v2-background-bg-contrast": "var(--v2-grey-700)",
"v2-background-bg-button-neutral": "var(--v2-alpha-light-6)",
"v2-background-bg-accent": "var(--v2-blue-600)",
"v2-text-text-base": "var(--v2-grey-200)",
"v2-text-text-muted": "var(--v2-grey-500)",
"v2-text-text-faint": "var(--v2-grey-600)",
"v2-text-text-inverse": "var(--v2-grey-1000)",
"v2-text-text-contrast": "var(--v2-grey-100)",
"v2-text-text-accent": "var(--v2-blue-400)",
"v2-text-text-accent-hover": "var(--v2-blue-300)",
"v2-icon-icon-base": "var(--v2-grey-400)",
"v2-icon-icon-muted": "var(--v2-grey-600)",
"v2-icon-icon-inverse": "var(--v2-grey-1000)",
"v2-icon-icon-contrast": "var(--v2-grey-200)",
"v2-icon-icon-accent": "var(--v2-blue-400)",
"v2-icon-icon-accent-hover": "var(--v2-blue-300)",
"v2-border-border-muted": "var(--v2-alpha-light-8)",
"v2-border-border-base": "var(--v2-alpha-light-10)",
"v2-border-border-strong": "var(--v2-alpha-light-20)",
"v2-border-border-inverse": "var(--v2-grey-100)",
"v2-border-border-focus": "var(--v2-blue-500)",
"v2-overlay-simple-overlay-hover": "var(--v2-alpha-light-6)",
"v2-overlay-simple-overlay-pressed": "var(--v2-alpha-light-10)",
"v2-overlay-simple-overlay-contrast-hover": "var(--v2-alpha-dark-24)",
"v2-overlay-simple-overlay-contrast-pressed": "var(--v2-alpha-dark-40)",
"v2-overlay-simple-overlay-scrim": "var(--v2-alpha-light-30)",
"v2-overlay-gradient-depth-overlay-depth-top": "var(--v2-alpha-light-100)",
"v2-overlay-gradient-depth-overlay-depth-bot": "var(--v2-alpha-light-0)",
"v2-overlay-simple-tab-active-scrim": "#24242400",
"v2-overlay-simple-tab-hover-scrim": "#3a3a3a00",
"v2-overlay-simple-tab-scrim": "#08080800",
"v2-state-bg-success": "var(--v2-green-1200)",
"v2-state-fg-success": "var(--v2-green-500)",
"v2-state-border-success": "var(--v2-green-900)",
"v2-state-bg-warning": "var(--v2-yellow-1200)",
"v2-state-fg-warning": "var(--v2-yellow-500)",
"v2-state-border-warning": "var(--v2-yellow-900)",
"v2-state-bg-danger": "var(--v2-red-1200)",
"v2-state-fg-danger": "var(--v2-red-500)",
"v2-state-border-danger": "var(--v2-red-900)",
"v2-state-bg-info": "var(--v2-blue-1200)",
"v2-state-fg-info": "var(--v2-blue-500)",
"v2-state-border-info": "var(--v2-blue-900)",
"v2-avatar-bg-orange": "#723d22ff",
"v2-avatar-border-orange": "#ff8648ff",
"v2-avatar-bg-yellow": "#68552bff",
"v2-avatar-border-yellow": "#e7af36ff",
"v2-avatar-bg-cyan": "#005a6eff",
"v2-avatar-border-cyan": "#0096b8ff",
"v2-avatar-bg-green": "#196130ff",
"v2-avatar-border-green": "#49c970ff",
"v2-avatar-bg-red": "#7a1f23ff",
"v2-avatar-border-red": "#d92e3cff",
"v2-avatar-bg-pink": "#8c2d61ff",
"v2-avatar-border-pink": "#e4429eff",
"v2-avatar-bg-blue": "#263fa9ff",
"v2-avatar-border-blue": "#7698fdff",
"v2-avatar-bg-purple": "#361f83ff",
"v2-avatar-border-purple": "#7152f4ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#aeaeaeff",
"v2-elevation-raised": "0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-floating": "0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-overlay": "0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-button-neutral": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)",
"v2-elevation-button-contrast": "0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-elevation-switch-on": "inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-illustration-illustration-layer-01": "var(--v2-grey-900)",
"v2-illustration-illustration-layer-02": "var(--v2-grey-800)",
"v2-illustration-illustration-layer-03": "var(--v2-grey-700)"
}
}
}
+5
View File
@@ -34,6 +34,7 @@ export interface ThemePaletteColors {
type ThemeVariantBase = {
overrides?: Record<string, ColorValue>
v2Overrides?: Record<string, V2ColorValue>
}
export type ThemeVariant =
@@ -67,4 +68,8 @@ export type CssVarRef = `var(--${string})`
export type ColorValue = HexColor | CssVarRef
export type V2ColorValue = HexColor | CssVarRef | string
export type ResolvedTheme = Record<ThemeToken, ColorValue>
export type ResolvedV2Theme = Record<string, V2ColorValue>
+48
View File
@@ -0,0 +1,48 @@
import type { V2ColorValue } from "../types"
/** Fixed project avatar colors (OC-2); theme-independent like v1 `avatar-background-*`. */
export const V2_AVATAR_FG = "#ffffffff"
export const V2_AVATAR_LIGHT: Record<string, V2ColorValue> = {
"v2-avatar-fg": V2_AVATAR_FG,
"v2-avatar-bg-orange": "#ee7330ff",
"v2-avatar-border-orange": "#d16427ff",
"v2-avatar-bg-yellow": "#e7af36ff",
"v2-avatar-border-yellow": "#cb9f34ff",
"v2-avatar-bg-cyan": "#0096b8ff",
"v2-avatar-border-cyan": "#007d9bff",
"v2-avatar-bg-green": "#2eaf5aff",
"v2-avatar-border-green": "#198b43ff",
"v2-avatar-bg-red": "#d92e3cff",
"v2-avatar-border-red": "#b82d35ff",
"v2-avatar-bg-pink": "#e4429eff",
"v2-avatar-border-pink": "#c83d8bff",
"v2-avatar-bg-blue": "#3250dfff",
"v2-avatar-border-blue": "#2c47c8ff",
"v2-avatar-bg-purple": "#623be2ff",
"v2-avatar-border-purple": "#5230c2ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#3a3a3aff",
}
export const V2_AVATAR_DARK: Record<string, V2ColorValue> = {
"v2-avatar-fg": V2_AVATAR_FG,
"v2-avatar-bg-orange": "#723d22ff",
"v2-avatar-border-orange": "#ff8648ff",
"v2-avatar-bg-yellow": "#68552bff",
"v2-avatar-border-yellow": "#e7af36ff",
"v2-avatar-bg-cyan": "#005a6eff",
"v2-avatar-border-cyan": "#0096b8ff",
"v2-avatar-bg-green": "#196130ff",
"v2-avatar-border-green": "#49c970ff",
"v2-avatar-bg-red": "#7a1f23ff",
"v2-avatar-border-red": "#d92e3cff",
"v2-avatar-bg-pink": "#8c2d61ff",
"v2-avatar-border-pink": "#e4429eff",
"v2-avatar-bg-blue": "#263fa9ff",
"v2-avatar-border-blue": "#7698fdff",
"v2-avatar-bg-purple": "#361f83ff",
"v2-avatar-border-purple": "#7152f4ff",
"v2-avatar-bg-gray": "#5c5c5cff",
"v2-avatar-border-gray": "#aeaeaeff",
}
@@ -0,0 +1,113 @@
import type { V2ColorValue } from "../types"
/** Default v2 hue ramps from `v2/styles/colors.css` (OC-2). Alpha ramps live in CSS only. */
export const V2_PRIMITIVES_DEFAULT: Record<string, V2ColorValue> = {
"v2-grey-100": "#ffffffff",
"v2-grey-200": "#fafafaff",
"v2-grey-300": "#eeeeeeff",
"v2-grey-400": "#d4d4d4ff",
"v2-grey-500": "#aeaeaeff",
"v2-grey-600": "#808080ff",
"v2-grey-700": "#5c5c5cff",
"v2-grey-800": "#3a3a3aff",
"v2-grey-900": "#242424ff",
"v2-grey-1000": "#161616ff",
"v2-grey-1100": "#080808ff",
"v2-grey-1200": "#000000ff",
"v2-red-100": "#fcecebff",
"v2-red-200": "#f6d5d3ff",
"v2-red-300": "#f2bbb7ff",
"v2-red-400": "#f29b96ff",
"v2-red-500": "#f17471ff",
"v2-red-600": "#f1484fff",
"v2-red-700": "#d92e3cff",
"v2-red-800": "#b82d35ff",
"v2-red-900": "#97252bff",
"v2-red-1000": "#7a1f23ff",
"v2-red-1100": "#5f1a1cff",
"v2-red-1200": "#461516ff",
"v2-orange-100": "#fdf2edff",
"v2-orange-200": "#ffe7dcff",
"v2-orange-300": "#ffd8c6ff",
"v2-orange-400": "#ffc1a4ff",
"v2-orange-500": "#ffa478ff",
"v2-orange-600": "#ff8648ff",
"v2-orange-700": "#ee7330ff",
"v2-orange-800": "#d16427ff",
"v2-orange-900": "#b35624ff",
"v2-orange-1000": "#954c27ff",
"v2-orange-1100": "#723d22ff",
"v2-orange-1200": "#5a2c14ff",
"v2-yellow-100": "#fefaecff",
"v2-yellow-200": "#fcefd0ff",
"v2-yellow-300": "#f7e5b5ff",
"v2-yellow-400": "#f3da9bff",
"v2-yellow-500": "#f2cf76ff",
"v2-yellow-600": "#f6c251ff",
"v2-yellow-700": "#e7af36ff",
"v2-yellow-800": "#cb9f34ff",
"v2-yellow-900": "#ac8833ff",
"v2-yellow-1000": "#8e7231ff",
"v2-yellow-1100": "#68552bff",
"v2-yellow-1200": "#4b4025ff",
"v2-green-100": "#e7f9eaff",
"v2-green-200": "#d0f0d5ff",
"v2-green-300": "#b8e9c1ff",
"v2-green-400": "#96e3a6ff",
"v2-green-500": "#6bd586ff",
"v2-green-600": "#49c970ff",
"v2-green-700": "#2eaf5aff",
"v2-green-800": "#198b43ff",
"v2-green-900": "#1d783cff",
"v2-green-1000": "#196130ff",
"v2-green-1100": "#164c26ff",
"v2-green-1200": "#14361dff",
"v2-cyan-100": "#e2f7fbff",
"v2-cyan-200": "#c4edf4ff",
"v2-cyan-300": "#a3e4efff",
"v2-cyan-400": "#65d9ebff",
"v2-cyan-500": "#00c5dfff",
"v2-cyan-600": "#00abcfff",
"v2-cyan-700": "#0096b8ff",
"v2-cyan-800": "#007d9bff",
"v2-cyan-900": "#006c85ff",
"v2-cyan-1000": "#005a6eff",
"v2-cyan-1100": "#004756ff",
"v2-cyan-1200": "#00353fff",
"v2-blue-100": "#ecf1feff",
"v2-blue-200": "#d7e2fcff",
"v2-blue-300": "#c3d4fdff",
"v2-blue-400": "#a2bcffff",
"v2-blue-500": "#7698fdff",
"v2-blue-600": "#3b5cf6ff",
"v2-blue-700": "#3250dfff",
"v2-blue-800": "#2c47c8ff",
"v2-blue-900": "#263fa9ff",
"v2-blue-1000": "#22388fff",
"v2-blue-1100": "#1c2e70ff",
"v2-blue-1200": "#1b2852ff",
"v2-purple-100": "#ebecfeff",
"v2-purple-200": "#d5d5fcff",
"v2-purple-300": "#b9b8f5ff",
"v2-purple-400": "#9e99f7ff",
"v2-purple-500": "#8271f8ff",
"v2-purple-600": "#7152f4ff",
"v2-purple-700": "#623be2ff",
"v2-purple-800": "#5230c2ff",
"v2-purple-900": "#442aa1ff",
"v2-purple-1000": "#361f83ff",
"v2-purple-1100": "#2b1b6aff",
"v2-purple-1200": "#221358ff",
"v2-pink-100": "#fdecf3ff",
"v2-pink-200": "#f7d5e4ff",
"v2-pink-300": "#fabcd8ff",
"v2-pink-400": "#f799c6ff",
"v2-pink-500": "#f26cb2ff",
"v2-pink-600": "#f64aabff",
"v2-pink-700": "#e4429eff",
"v2-pink-800": "#c83d8bff",
"v2-pink-900": "#aa3576ff",
"v2-pink-1000": "#8c2d61ff",
"v2-pink-1100": "#6f284fff",
"v2-pink-1200": "#5c1d3fff",
}
+24
View File
@@ -0,0 +1,24 @@
import { blend, hexToOklch, shift } from "../color"
import type { ColorValue, HexColor, V2ColorValue } from "../types"
export function mapV2Foreground(
ink: HexColor,
isDark: boolean,
overrides: Record<string, ColorValue> = {},
): Record<string, V2ColorValue> {
const tint = hexToOklch(ink)
const body = shift(ink, {
l: isDark ? Math.max(0, 0.88 - tint.l) * 0.4 : -Math.max(0, tint.l - 0.18) * 0.24,
c: isDark ? 1.04 : 1.02,
})
return {
"v2-text-text-base": isDark
? blend("#ffffff", body, 0.9)
: shift(body, { l: -0.07, c: 1.04 }),
"v2-text-text-muted":
overrides["text-weak"] ??
shift(body, { l: isDark ? -0.11 : 0.11, c: 0.9 }),
"v2-text-text-faint": shift(body, { l: isDark ? -0.2 : 0.21, c: isDark ? 0.78 : 0.72 }),
}
}
+150
View File
@@ -0,0 +1,150 @@
import type { V2ColorValue } from "../types"
import { V2_AVATAR_DARK, V2_AVATAR_LIGHT } from "./avatar"
const ref = (name: string): V2ColorValue => `var(--${name})`
const light: Record<string, V2ColorValue> = {
"v2-background-bg-base": ref("v2-grey-100"),
"v2-background-bg-deep": ref("v2-grey-200"),
"v2-background-bg-layer-01": ref("v2-grey-300"),
"v2-background-bg-layer-02": ref("v2-grey-400"),
"v2-background-bg-layer-03": ref("v2-grey-500"),
"v2-background-bg-inverse": ref("v2-grey-1000"),
"v2-background-bg-contrast": ref("v2-grey-900"),
"v2-background-bg-button-neutral": ref("v2-grey-100"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-text-text-inverse": ref("v2-grey-100"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-600"),
"v2-text-text-accent-hover": ref("v2-blue-700"),
"v2-icon-icon-base": ref("v2-grey-1000"),
"v2-icon-icon-muted": ref("v2-grey-800"),
"v2-icon-icon-inverse": ref("v2-grey-100"),
"v2-icon-icon-contrast": ref("v2-grey-200"),
"v2-icon-icon-accent": ref("v2-blue-600"),
"v2-icon-icon-accent-hover": ref("v2-blue-700"),
"v2-border-border-muted": ref("v2-alpha-dark-8"),
"v2-border-border-base": ref("v2-alpha-dark-10"),
"v2-border-border-strong": ref("v2-alpha-dark-20"),
"v2-border-border-inverse": ref("v2-grey-1000"),
"v2-border-border-focus": ref("v2-blue-500"),
"v2-overlay-simple-overlay-hover": ref("v2-alpha-dark-4"),
"v2-overlay-simple-overlay-pressed": ref("v2-alpha-dark-8"),
"v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-light-12"),
"v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-light-24"),
"v2-overlay-simple-overlay-scrim": ref("v2-alpha-dark-40"),
"v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"),
"v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"),
"v2-overlay-simple-tab-active-scrim": "#fafafa00",
"v2-overlay-simple-tab-hover-scrim": "#eeeeee00",
"v2-overlay-simple-tab-scrim": "#fafafa00",
"v2-state-bg-success": ref("v2-green-100"),
"v2-state-fg-success": ref("v2-green-800"),
"v2-state-border-success": ref("v2-green-300"),
"v2-state-bg-warning": ref("v2-yellow-100"),
"v2-state-fg-warning": ref("v2-yellow-800"),
"v2-state-border-warning": ref("v2-yellow-300"),
"v2-state-bg-danger": ref("v2-red-100"),
"v2-state-fg-danger": ref("v2-red-800"),
"v2-state-border-danger": ref("v2-red-300"),
"v2-state-bg-info": ref("v2-blue-100"),
"v2-state-fg-info": ref("v2-blue-800"),
"v2-state-border-info": ref("v2-blue-300"),
...V2_AVATAR_LIGHT,
"v2-elevation-raised":
"0px 2px 4px 0px var(--v2-alpha-dark-4), 0px 1px 2px -1px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-floating":
"0px 8px 16px 0px var(--v2-alpha-dark-4), 0px 4px 8px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-overlay":
"0px 16px 32px 0px var(--v2-alpha-dark-4), 0px 8px 16px 0px var(--v2-alpha-dark-8), 0px 0px 0px 0.5px var(--v2-alpha-dark-12), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-neutral":
"0px 1px 1.5px 0px var(--v2-alpha-dark-10), 0px 0px 0px 0.5px var(--v2-alpha-dark-14), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-button-contrast":
"0px 1px 1.5px 0px var(--v2-alpha-dark-20), 0px 0px 0px 0.5px var(--v2-grey-800), inset 0px 1px 2px 0px var(--v2-alpha-light-14), inset 0px -1px 2px 0px var(--v2-alpha-dark-6), 0px 0px 0px 0px var(--v2-alpha-dark-0)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off":
"inset 0px 1px 1px 0px var(--v2-alpha-dark-8), inset 0px 0.5px 0.5px 0px var(--v2-alpha-dark-8), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-10)",
"v2-elevation-switch-on":
"inset 0px 2px 2px 0px var(--v2-alpha-dark-10), inset 0px 1px 1px 0px var(--v2-alpha-dark-10), inset 0px 0px 0px 0.5px var(--v2-alpha-dark-20)",
"v2-illustration-illustration-layer-01": ref("v2-grey-300"),
"v2-illustration-illustration-layer-02": ref("v2-grey-400"),
"v2-illustration-illustration-layer-03": ref("v2-grey-500"),
}
const dark: Record<string, V2ColorValue> = {
"v2-background-bg-base": ref("v2-grey-1000"),
"v2-background-bg-deep": ref("v2-grey-1100"),
"v2-background-bg-layer-01": ref("v2-grey-800"),
"v2-background-bg-layer-02": ref("v2-grey-600"),
"v2-background-bg-layer-03": ref("v2-grey-500"),
"v2-background-bg-inverse": ref("v2-grey-100"),
"v2-background-bg-contrast": ref("v2-grey-700"),
"v2-background-bg-button-neutral": ref("v2-alpha-light-6"),
"v2-background-bg-accent": ref("v2-blue-600"),
"v2-text-text-inverse": ref("v2-grey-1000"),
"v2-text-text-contrast": ref("v2-grey-100"),
"v2-text-text-accent": ref("v2-blue-400"),
"v2-text-text-accent-hover": ref("v2-blue-300"),
"v2-icon-icon-base": ref("v2-grey-300"),
"v2-icon-icon-muted": ref("v2-grey-400"),
"v2-icon-icon-inverse": ref("v2-grey-1000"),
"v2-icon-icon-contrast": ref("v2-grey-200"),
"v2-icon-icon-accent": ref("v2-blue-400"),
"v2-icon-icon-accent-hover": ref("v2-blue-300"),
"v2-border-border-muted": ref("v2-alpha-light-8"),
"v2-border-border-base": ref("v2-alpha-light-10"),
"v2-border-border-strong": ref("v2-alpha-light-20"),
"v2-border-border-inverse": ref("v2-grey-100"),
"v2-border-border-focus": ref("v2-blue-500"),
"v2-overlay-simple-overlay-hover": ref("v2-alpha-light-6"),
"v2-overlay-simple-overlay-pressed": ref("v2-alpha-light-10"),
"v2-overlay-simple-overlay-contrast-hover": ref("v2-alpha-dark-24"),
"v2-overlay-simple-overlay-contrast-pressed": ref("v2-alpha-dark-40"),
"v2-overlay-simple-overlay-scrim": ref("v2-alpha-light-30"),
"v2-overlay-gradient-depth-overlay-depth-top": ref("v2-alpha-light-100"),
"v2-overlay-gradient-depth-overlay-depth-bot": ref("v2-alpha-light-0"),
"v2-overlay-simple-tab-active-scrim": "#24242400",
"v2-overlay-simple-tab-hover-scrim": "#3a3a3a00",
"v2-overlay-simple-tab-scrim": "#08080800",
"v2-state-bg-success": ref("v2-green-1200"),
"v2-state-fg-success": ref("v2-green-500"),
"v2-state-border-success": ref("v2-green-900"),
"v2-state-bg-warning": ref("v2-yellow-1200"),
"v2-state-fg-warning": ref("v2-yellow-500"),
"v2-state-border-warning": ref("v2-yellow-900"),
"v2-state-bg-danger": ref("v2-red-1200"),
"v2-state-fg-danger": ref("v2-red-500"),
"v2-state-border-danger": ref("v2-red-900"),
"v2-state-bg-info": ref("v2-blue-1200"),
"v2-state-fg-info": ref("v2-blue-500"),
"v2-state-border-info": ref("v2-blue-900"),
...V2_AVATAR_DARK,
"v2-elevation-raised":
"0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-floating":
"0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 4px 8px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-overlay":
"0px 16px 32px 0px var(--v2-alpha-dark-30), 0px 8px 16px 0px var(--v2-alpha-dark-30), 0px 0px 0px 0.5px var(--v2-alpha-light-16), 0px -0.5px 0px 0px var(--v2-alpha-light-6)",
"v2-elevation-button-neutral":
"0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-20), 0px -0.5px 0px 0px var(--v2-alpha-light-10)",
"v2-elevation-button-contrast":
"0px 1px 2px 0px var(--v2-alpha-dark-40), 0px 0px 0px 0.5px var(--v2-alpha-light-40), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0px var(--v2-alpha-light-0), 0px -0.5px 0px 0px var(--v2-alpha-light-30)",
"v2-elevation-elements": "0px 0.5px 0.5px 0px var(--v2-alpha-dark-40)",
"v2-elevation-switch-off":
"inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-elevation-switch-on":
"inset 0px -0.5px 0px 0px var(--v2-alpha-light-10), inset 0px 0px 0px 0px var(--v2-alpha-light-0), inset 0px 0px 0px 0.5px var(--v2-alpha-light-16)",
"v2-illustration-illustration-layer-01": ref("v2-grey-900"),
"v2-illustration-illustration-layer-02": ref("v2-grey-800"),
"v2-illustration-illustration-layer-03": ref("v2-grey-700"),
}
export function mapV2Semantics(isDark: boolean): Record<string, V2ColorValue> {
return isDark ? dark : light
}
export function mergeV2Tokens(
...layers: Record<string, V2ColorValue>[]
): Record<string, V2ColorValue> {
return Object.assign({}, ...layers)
}
+153
View File
@@ -0,0 +1,153 @@
// @refresh reload
import { generateNeutralScale, hexToOklch, oklchToHex, shift } from "../color"
import { mapV2Foreground } from "./foreground"
import { mapV2Semantics, mergeV2Tokens } from "./mapping"
import type { DesktopTheme, HexColor, ResolvedV2Theme, ThemeVariant, V2ColorValue } from "../types"
import { V2_PRIMITIVES_DEFAULT } from "./default-primitives"
const V2_STEPS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200] as const
interface PaletteInput {
neutral: HexColor
ink: HexColor
primary: HexColor
accent: HexColor
success: HexColor
warning: HexColor
error: HexColor
info: HexColor
interactive: HexColor
diffAdd: HexColor
diffDelete: HexColor
}
function clamp(v: number, min: number, max: number) {
return Math.max(min, Math.min(max, v))
}
/** v2 ramps: 100 = lightest, 1200 = darkest — wider spread than v1 `generateScale`. */
function generateV2HueScale(seed: HexColor, isDark: boolean): HexColor[] {
const base = hexToOklch(seed)
const chromaBoost = isDark ? 1 : 1.05
const lightSteps = [
0.99,
0.965,
0.93,
0.885,
0.835,
clamp(base.l, 0.48, 0.72),
clamp(base.l - 0.07, 0.4, 0.64),
clamp(base.l - 0.14, 0.32, 0.55),
clamp(base.l - 0.21, 0.24, 0.46),
clamp(base.l - 0.28, 0.17, 0.38),
clamp(base.l - 0.34, 0.12, 0.3),
clamp(base.l - 0.4, 0.08, 0.22),
]
const chromaMultipliers = [0.28, 0.48, 0.68, 0.86, 1.02, 1.28, 1.34, 1.28, 1.18, 1.08, 0.98, 0.88]
return lightSteps.map((l, i) =>
oklchToHex({
l,
c: base.c * chromaMultipliers[i]! * chromaBoost,
h: base.h,
}),
)
}
/** Grey ramp: 100 = lightest, 1200 = darkest. Derived from palette neutral → ink like v1. */
function generateV2NeutralScale(neutral: HexColor, ink: HexColor, isDark: boolean): HexColor[] {
const scale = generateNeutralScale(neutral, isDark, ink)
return isDark ? scale.toReversed() : scale
}
function assignHueRamp(prefix: string, scale: HexColor[]): Record<string, V2ColorValue> {
const tokens: Record<string, V2ColorValue> = {}
for (let i = 0; i < V2_STEPS.length; i++) {
tokens[`v2-${prefix}-${V2_STEPS[i]}`] = scale[i]!
}
return tokens
}
function readPalette(variant: ThemeVariant): PaletteInput {
if ("palette" in variant && variant.palette) {
const palette = variant.palette
return {
neutral: palette.neutral,
ink: palette.ink,
primary: palette.primary,
accent: palette.accent ?? palette.info,
success: palette.success,
warning: palette.warning,
error: palette.error,
info: palette.info,
interactive: palette.interactive ?? palette.primary,
diffAdd: palette.diffAdd ?? shift(palette.success, { c: 0.55, l: 0.14 }),
diffDelete: palette.diffDelete ?? palette.error,
}
}
if ("seeds" in variant && variant.seeds) {
const seeds = variant.seeds
return {
neutral: seeds.neutral,
ink: seeds.neutral,
primary: seeds.primary,
accent: seeds.info,
success: seeds.success,
warning: seeds.warning,
error: seeds.error,
info: seeds.info,
interactive: seeds.interactive,
diffAdd: seeds.diffAdd,
diffDelete: seeds.diffDelete,
}
}
throw new Error("Theme variant requires `palette` or `seeds`")
}
/** Build v2 primitive ramps (100 = lightest). Alpha ramps are static in `v2/styles/colors.css`. */
export function generateV2Primitives(variant: ThemeVariant, isDark: boolean): Record<string, V2ColorValue> {
const colors = readPalette(variant)
const grey = generateV2NeutralScale(colors.neutral, colors.ink, isDark)
const blue = generateV2HueScale(colors.interactive, isDark)
const green = generateV2HueScale(colors.success, isDark)
const yellow = generateV2HueScale(colors.warning, isDark)
const red = generateV2HueScale(colors.error, isDark)
const purple = generateV2HueScale(colors.accent, isDark)
const pink = generateV2HueScale(colors.info, isDark)
const orange = generateV2HueScale(shift(colors.warning, { h: -22, l: -0.082, c: 0.94 }), isDark)
const cyan = generateV2HueScale(shift(colors.info, { h: -12, l: 0.128, c: 1.12 }), isDark)
return {
...V2_PRIMITIVES_DEFAULT,
...assignHueRamp("grey", grey),
...assignHueRamp("blue", blue),
...assignHueRamp("green", green),
...assignHueRamp("yellow", yellow),
...assignHueRamp("red", red),
...assignHueRamp("purple", purple),
...assignHueRamp("pink", pink),
...assignHueRamp("orange", orange),
...assignHueRamp("cyan", cyan),
}
}
export function resolveThemeVariantV2(variant: ThemeVariant, isDark: boolean): ResolvedV2Theme {
const primitives = generateV2Primitives(variant, isDark)
const semantics = mapV2Semantics(isDark)
const foreground = mapV2Foreground(readPalette(variant).ink, isDark, variant.overrides)
return mergeV2Tokens(primitives, semantics, foreground, variant.v2Overrides ?? {})
}
export function resolveThemeV2(theme: DesktopTheme): { light: ResolvedV2Theme; dark: ResolvedV2Theme } {
return {
light: resolveThemeVariantV2(theme.light, false),
dark: resolveThemeVariantV2(theme.dark, true),
}
}
export function themeV2ToCss(tokens: ResolvedV2Theme): string {
return Object.entries(tokens)
.map(([key, value]) => `--${key}: ${value};`)
.join("\n ")
}
+3 -2
View File
@@ -7,7 +7,7 @@
background-color: var(--v2-overlay-simple-overlay-scrim);
}
[data-component="dialog"] {
[data-component="dialog-v2"] {
position: fixed;
inset: 0;
z-index: 50;
@@ -25,7 +25,7 @@
background: var(--v2-background-bg-layer-01);
box-shadow: var(--v2-elevation-overlay);
border-radius: 6px;
overflow: visible;
overflow: hidden;
pointer-events: auto;
[data-slot="dialog-content"] {
@@ -37,6 +37,7 @@
max-height: 100%;
flex: 1;
overflow: auto;
background: transparent;
scrollbar-width: none;
-ms-overflow-style: none;
@@ -17,7 +17,7 @@ Dialog content wrapper built on Kobalte's dialog primitive with v2 styling.
- Focus trapping and aria attributes provided by Kobalte Dialog.
### Theming/tokens
- Uses \`data-component="dialog"\` and slot attributes.
- Uses \`data-component="dialog-v2"\` and slot attributes.
`
export default {
+8 -2
View File
@@ -7,6 +7,7 @@ export interface DialogProps extends ParentProps {
description?: JSXElement
action?: JSXElement
size?: "normal" | "large" | "x-large"
variant?: "default" | "settings"
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
fit?: boolean
@@ -17,14 +18,19 @@ export function DialogFooter(props: ParentProps) {
}
export function Dialog(props: DialogProps) {
const [local] = splitProps(props, ["title", "description", "action", "size", "class", "classList", "fit", "children"])
const [local] = splitProps(props, ["title", "description", "action", "size", "variant", "class", "classList", "fit", "children"])
const title = children(() => local.title)
const description = children(() => local.description)
const action = children(() => local.action)
const hasHeader = () => title() || action()
return (
<div data-component="dialog" data-fit={local.fit ? true : undefined} data-size={local.size || "normal"}>
<div
data-component="dialog-v2"
data-variant={local.variant === "settings" ? "settings" : undefined}
data-fit={local.fit ? true : undefined}
data-size={local.size || "normal"}
>
<div data-slot="dialog-container">
<Kobalte.Content
data-slot="dialog-content"
@@ -20,7 +20,7 @@
letter-spacing: 0.05px;
font-variant-numeric: tabular-nums;
text-transform: uppercase;
color: var(--v2-grey-100);
color: var(--v2-avatar-fg);
text-shadow: 0 0 4px var(--v2-alpha-dark-20);
user-select: none;
-webkit-user-select: none;
+58 -55
View File
@@ -63,25 +63,26 @@
--v2-state-fg-info: var(--v2-blue-800);
--v2-state-border-info: var(--v2-blue-300);
/* ── Project avatar ── */
--v2-avatar-bg-orange: var(--v2-orange-700);
--v2-avatar-border-orange: var(--v2-orange-800);
--v2-avatar-bg-yellow: var(--v2-yellow-700);
--v2-avatar-border-yellow: var(--v2-yellow-800);
--v2-avatar-bg-cyan: var(--v2-cyan-700);
--v2-avatar-border-cyan: var(--v2-cyan-800);
--v2-avatar-bg-green: var(--v2-green-700);
--v2-avatar-border-green: var(--v2-green-800);
--v2-avatar-bg-red: var(--v2-red-700);
--v2-avatar-border-red: var(--v2-red-800);
--v2-avatar-bg-pink: var(--v2-pink-700);
--v2-avatar-border-pink: var(--v2-pink-800);
--v2-avatar-bg-blue: var(--v2-blue-700);
--v2-avatar-border-blue: var(--v2-blue-800);
--v2-avatar-bg-purple: var(--v2-purple-700);
--v2-avatar-border-purple: var(--v2-purple-800);
--v2-avatar-bg-gray: var(--v2-grey-700);
--v2-avatar-border-gray: var(--v2-grey-800);
/* ── Project avatar (fixed; theme-independent) ── */
--v2-avatar-fg: #ffffffff;
--v2-avatar-bg-orange: #ee7330ff;
--v2-avatar-border-orange: #d16427ff;
--v2-avatar-bg-yellow: #e7af36ff;
--v2-avatar-border-yellow: #cb9f34ff;
--v2-avatar-bg-cyan: #0096b8ff;
--v2-avatar-border-cyan: #007d9bff;
--v2-avatar-bg-green: #2eaf5aff;
--v2-avatar-border-green: #198b43ff;
--v2-avatar-bg-red: #d92e3cff;
--v2-avatar-border-red: #b82d35ff;
--v2-avatar-bg-pink: #e4429eff;
--v2-avatar-border-pink: #c83d8bff;
--v2-avatar-bg-blue: #3250dfff;
--v2-avatar-border-blue: #2c47c8ff;
--v2-avatar-bg-purple: #623be2ff;
--v2-avatar-border-purple: #5230c2ff;
--v2-avatar-bg-gray: #5c5c5cff;
--v2-avatar-border-gray: #3a3a3aff;
/* ── Elevation ── */
--v2-elevation-raised:
@@ -306,24 +307,25 @@
--v2-illustration-illustration-layer-02: var(--v2-grey-400);
--v2-illustration-illustration-layer-03: var(--v2-grey-500);
--v2-avatar-bg-orange: var(--v2-orange-700);
--v2-avatar-border-orange: var(--v2-orange-800);
--v2-avatar-bg-yellow: var(--v2-yellow-700);
--v2-avatar-border-yellow: var(--v2-yellow-800);
--v2-avatar-bg-cyan: var(--v2-cyan-700);
--v2-avatar-border-cyan: var(--v2-cyan-800);
--v2-avatar-bg-green: var(--v2-green-700);
--v2-avatar-border-green: var(--v2-green-800);
--v2-avatar-bg-red: var(--v2-red-700);
--v2-avatar-border-red: var(--v2-red-800);
--v2-avatar-bg-pink: var(--v2-pink-700);
--v2-avatar-border-pink: var(--v2-pink-800);
--v2-avatar-bg-blue: var(--v2-blue-700);
--v2-avatar-border-blue: var(--v2-blue-800);
--v2-avatar-bg-purple: var(--v2-purple-700);
--v2-avatar-border-purple: var(--v2-purple-800);
--v2-avatar-bg-gray: var(--v2-grey-700);
--v2-avatar-border-gray: var(--v2-grey-800);
--v2-avatar-fg: #ffffffff;
--v2-avatar-bg-orange: #ee7330ff;
--v2-avatar-border-orange: #d16427ff;
--v2-avatar-bg-yellow: #e7af36ff;
--v2-avatar-border-yellow: #cb9f34ff;
--v2-avatar-bg-cyan: #0096b8ff;
--v2-avatar-border-cyan: #007d9bff;
--v2-avatar-bg-green: #2eaf5aff;
--v2-avatar-border-green: #198b43ff;
--v2-avatar-bg-red: #d92e3cff;
--v2-avatar-border-red: #b82d35ff;
--v2-avatar-bg-pink: #e4429eff;
--v2-avatar-border-pink: #c83d8bff;
--v2-avatar-bg-blue: #3250dfff;
--v2-avatar-border-blue: #2c47c8ff;
--v2-avatar-bg-purple: #623be2ff;
--v2-avatar-border-purple: #5230c2ff;
--v2-avatar-bg-gray: #5c5c5cff;
--v2-avatar-border-gray: #3a3a3aff;
}
/* Explicit dark mode via data attribute (Storybook toggle, runtime JS) */
@@ -385,24 +387,25 @@
--v2-state-fg-info: var(--v2-blue-500);
--v2-state-border-info: var(--v2-blue-900);
--v2-avatar-bg-orange: var(--v2-orange-1100);
--v2-avatar-border-orange: var(--v2-orange-600);
--v2-avatar-bg-yellow: var(--v2-yellow-1100);
--v2-avatar-border-yellow: var(--v2-yellow-700);
--v2-avatar-bg-cyan: var(--v2-cyan-1000);
--v2-avatar-border-cyan: var(--v2-cyan-700);
--v2-avatar-bg-green: var(--v2-green-1000);
--v2-avatar-border-green: var(--v2-green-600);
--v2-avatar-bg-red: var(--v2-red-1000);
--v2-avatar-border-red: var(--v2-red-700);
--v2-avatar-bg-pink: var(--v2-pink-1000);
--v2-avatar-border-pink: var(--v2-pink-700);
--v2-avatar-bg-blue: var(--v2-blue-900);
--v2-avatar-border-blue: var(--v2-blue-500);
--v2-avatar-bg-purple: var(--v2-purple-1000);
--v2-avatar-border-purple: var(--v2-purple-600);
--v2-avatar-bg-gray: var(--v2-grey-700);
--v2-avatar-border-gray: var(--v2-grey-500);
--v2-avatar-fg: #ffffffff;
--v2-avatar-bg-orange: #723d22ff;
--v2-avatar-border-orange: #ff8648ff;
--v2-avatar-bg-yellow: #68552bff;
--v2-avatar-border-yellow: #e7af36ff;
--v2-avatar-bg-cyan: #005a6eff;
--v2-avatar-border-cyan: #0096b8ff;
--v2-avatar-bg-green: #196130ff;
--v2-avatar-border-green: #49c970ff;
--v2-avatar-bg-red: #7a1f23ff;
--v2-avatar-border-red: #d92e3cff;
--v2-avatar-bg-pink: #8c2d61ff;
--v2-avatar-border-pink: #e4429eff;
--v2-avatar-bg-blue: #263fa9ff;
--v2-avatar-border-blue: #7698fdff;
--v2-avatar-bg-purple: #361f83ff;
--v2-avatar-border-purple: #7152f4ff;
--v2-avatar-bg-gray: #5c5c5cff;
--v2-avatar-border-gray: #aeaeaeff;
--v2-elevation-raised:
0px 2px 4px 0px var(--v2-alpha-dark-30), 0px 1px 2px 0px var(--v2-alpha-dark-30),