feat(app): color themes (#30824)
Co-authored-by: LukeParkerDev <10430890+Hona@users.noreply.github.com>
This commit is contained in:
co-authored by
LukeParkerDev
parent
75557000de
commit
605ae48c6f
@@ -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",
|
||||
}
|
||||
@@ -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 }),
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 ")
|
||||
}
|
||||
Reference in New Issue
Block a user