refactor(theme): rename surface tokens to raised (#49655)

This commit is contained in:
James Long
2026-09-17 22:05:42 -04:00
committed by GitHub
Unverified
parent b278ef6b82
commit 0ac458b3b3
16 changed files with 67 additions and 61 deletions
+16 -14
View File
@@ -136,9 +136,10 @@ export const DEFAULT_THEME = {
},
background: {
default: "$hue.neutral.200",
surface: {
offset: "$hue.neutral.300",
overlay: "$hue.neutral.400",
raised: {
base: "$hue.neutral.300",
high: "$hue.neutral.400",
max: "$hue.neutral.500",
},
action: {
primary: {
@@ -161,7 +162,7 @@ export const DEFAULT_THEME = {
},
formfield: {
default: "$background.default",
$hovered: "$background.surface.offset",
$hovered: "$background.raised.base",
$focused: "$background.action.primary.default",
$pressed: "$hue.interactive.800",
$disabled: "$background.default",
@@ -220,14 +221,14 @@ export const DEFAULT_THEME = {
"@context:elevated": {
text: { action: { primary: { default: "$hue.neutral.100" } } },
background: {
default: "$background.surface.offset",
action: { primary: { default: "$hue.interactive.500", $hovered: "$background.surface.overlay" } },
default: "$background.raised.base",
action: { primary: { default: "$hue.interactive.500", $hovered: "$background.raised.high" } },
},
},
"@context:overlay": {
text: { action: { primary: { default: "$hue.neutral.100" } } },
background: {
default: "$background.surface.overlay",
default: "$background.raised.high",
action: { primary: { default: "$hue.interactive.500" } },
},
},
@@ -357,9 +358,10 @@ export const DEFAULT_THEME = {
},
background: {
default: "$hue.neutral.800",
surface: {
offset: "$hue.neutral.700",
overlay: "$hue.neutral.600",
raised: {
base: "$hue.neutral.700",
high: "$hue.neutral.600",
max: "$hue.neutral.500",
},
action: {
primary: {
@@ -382,7 +384,7 @@ export const DEFAULT_THEME = {
},
formfield: {
default: "$background.default",
$hovered: "$background.surface.offset",
$hovered: "$background.raised.base",
$focused: "$background.action.primary.default",
$pressed: "$hue.interactive.800",
$disabled: "$background.default",
@@ -441,14 +443,14 @@ export const DEFAULT_THEME = {
"@context:elevated": {
text: { action: { primary: { default: "$hue.neutral.200" } } },
background: {
default: "$background.surface.offset",
action: { primary: { default: "$hue.interactive.400", $hovered: "$background.surface.overlay" } },
default: "$background.raised.base",
action: { primary: { default: "$hue.interactive.400", $hovered: "$background.raised.high" } },
},
},
"@context:overlay": {
text: { action: { primary: { default: "$hue.neutral.200" } } },
background: {
default: "$background.surface.overlay",
default: "$background.raised.high",
action: { primary: { default: "$hue.interactive.400" } },
},
},
+1 -1
View File
@@ -15,7 +15,7 @@ export function fallback(mode: Mode): ThemeTokensDefinition {
},
background: {
default: red,
surface: { offset: red, overlay: red },
raised: { base: red, high: red, max: red },
action: Object.fromEntries(ActionVariant.literals.map((variant) => [variant, { default: red }])),
formfield: { default: red },
feedback: Object.fromEntries(FeedbackKind.literals.map((kind) => [kind, { default: red }])),
+4 -3
View File
@@ -129,10 +129,11 @@ export type TextDefinition = Schema.Schema.Type<typeof TextDefinition>
const BackgroundDefinition = Schema.Struct({
default: Schema.optional(ColorValue),
surface: Schema.optional(
raised: Schema.optional(
Schema.Struct({
offset: Schema.optional(ColorValue),
overlay: Schema.optional(ColorValue),
base: Schema.optional(ColorValue),
high: Schema.optional(ColorValue),
max: Schema.optional(ColorValue),
}),
),
action: Schema.optional(ActionColorDefinition),
+4 -3
View File
@@ -40,9 +40,10 @@ export type ResolvedThemeTokens = {
}
readonly background: {
readonly default: RGBA
readonly surface: {
readonly offset: RGBA
readonly overlay: RGBA
readonly raised: {
readonly base: RGBA
readonly high: RGBA
readonly max: RGBA
}
readonly action: Readonly<Record<ActionVariant, StatefulColor>>
readonly formfield: FormfieldColor
+8 -6
View File
@@ -57,6 +57,7 @@ function migrateMode(theme: Theme, mode: Mode): FileThemeDefinition {
const background = mode === "light" ? "$hue.neutral.200" : "$hue.neutral.800"
const backgroundPanel = mode === "light" ? "$hue.neutral.300" : "$hue.neutral.700"
const backgroundMenu = mode === "light" ? "$hue.neutral.400" : "$hue.neutral.600"
const backgroundRaisedMax = "$hue.neutral.500"
return referenceHues({
hue: {
@@ -102,9 +103,10 @@ function migrateMode(theme: Theme, mode: Mode): FileThemeDefinition {
},
background: {
default: background,
surface: {
offset: backgroundPanel,
overlay: backgroundMenu,
raised: {
base: backgroundPanel,
high: backgroundMenu,
max: backgroundRaisedMax,
},
action: {
primary: { default: "transparent", $hovered: backgroundPanel, $focused: primary, $selected: "transparent" },
@@ -173,11 +175,11 @@ function migrateMode(theme: Theme, mode: Mode): FileThemeDefinition {
},
"@context:elevated": {
background: {
default: "$background.surface.offset",
action: { primary: { $hovered: "$background.surface.overlay" } },
default: "$background.raised.base",
action: { primary: { $hovered: "$background.raised.high" } },
},
},
"@context:overlay": { background: { default: "$background.surface.overlay" } },
"@context:overlay": { background: { default: "$background.raised.high" } },
})
}
+4 -4
View File
@@ -350,7 +350,7 @@ export function Prompt(props: PromptProps) {
createEffect(() => {
if (!input || input.isDestroyed) return
input.cursorColor = disabled() ? theme.background.surface.offset : theme.text.default
input.cursorColor = disabled() ? theme.background.raised.base : theme.text.default
if (config.cursor) input.cursorStyle = config.cursor
})
@@ -1642,7 +1642,7 @@ export function Prompt(props: PromptProps) {
})
const maxHeight = createMemo(() => Math.max(6, Math.floor(dimensions().height / 3)))
const promptBg = createMemo(() => theme.raise(theme.background.surface.offset))
const promptBg = createMemo(() => theme.raise(theme.background.raised.base))
return (
<>
@@ -1799,7 +1799,7 @@ export function Prompt(props: PromptProps) {
setTimeout(() => {
// setTimeout is a workaround and needs to be addressed properly
if (!input || input.isDestroyed) return
input.cursorColor = disabled() ? theme.background.surface.offset : theme.text.default
input.cursorColor = disabled() ? theme.background.raised.base : theme.text.default
if (config.cursor) input.cursorStyle = config.cursor
}, 0)
}}
@@ -1818,7 +1818,7 @@ export function Prompt(props: PromptProps) {
r.stopPropagation()
}}
focusedBackgroundColor="transparent"
cursorColor={disabled() ? theme.background.surface.offset : theme.text.default}
cursorColor={disabled() ? theme.background.raised.base : theme.text.default}
syntaxStyle={syntax()}
/>
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
+1 -1
View File
@@ -1530,7 +1530,7 @@ function HorizontalSessionTabs(props: {
const lifted = (hovered() === tab.sessionID || dragged()) && !selected()
const base = lifted ? theme.background.action.primary.hovered : theme.background.default
// A dragged tab lifts to full selected elevation while it is held.
return tint(base, theme.raise(theme.background.surface.offset), dragged() ? 1 : selection())
return tint(base, theme.raise(theme.background.raised.base), dragged() ? 1 : selection())
})
const pulseColor = () => tint(background(), theme.text.default, 0.45)
// The edge flash washes toward a brighter stop on the same background-to-text ramp,
@@ -887,7 +887,7 @@ export function DiffViewerContent(props: {
)
edge.backgroundColor =
entry && reviewedFileNames().has(entry.file.file)
? theme.background.surface.overlay
? theme.background.raised.high
: theme.diff.background.context
}
renderer.registerLifecyclePass(edge)
@@ -911,7 +911,7 @@ export function DiffViewerContent(props: {
{(entry, index) => {
const reviewed = () => reviewedFileNames().has(entry.file.file)
const background = () =>
reviewed() ? theme.background.surface.overlay : theme.diff.background.context
reviewed() ? theme.background.raised.high : theme.diff.background.context
const image = () => isDiffImageFile(entry.file.file)
const countsWidth = () =>
(image() ? 6 : String(entry.file.additions).length + String(entry.file.deletions).length + 5) +
+2 -2
View File
@@ -161,7 +161,7 @@ function map(
surface: exact(elevated.background.default),
pane: exact(theme.contextual.overlay.background.default),
border: exact(theme.border.default),
line: exact(theme.background.surface.overlay),
line: exact(theme.background.raised.high),
},
entry: {
system: { body: scrollback(theme.text.subdued) },
@@ -174,7 +174,7 @@ function map(
splash: {
left: nearestIndexed(indexed, theme.text.subdued),
right: nearestIndexed(indexed, theme.text.default),
leftShadow: nearestIndexed(indexed, theme.background.surface.offset),
leftShadow: nearestIndexed(indexed, theme.background.raised.base),
},
block: {
text: scrollback(theme.text.default),
+2 -2
View File
@@ -1292,7 +1292,7 @@ export function Session(props: {
paddingLeft: 1,
visible: showScrollbar(),
trackOptions: {
backgroundColor: theme.raise(theme.background.surface.offset),
backgroundColor: theme.raise(theme.background.raised.base),
foregroundColor: theme.border.default,
},
}}
@@ -1836,7 +1836,7 @@ function SessionReasoningGroupView(props: {
<box
border={["left"]}
customBorderChars={SplitBorder.customBorderChars}
borderColor={theme.raise(theme.background.surface.offset)}
borderColor={theme.raise(theme.background.raised.base)}
paddingLeft={1}
>
<code
+1 -1
View File
@@ -762,7 +762,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
backgroundColor={
active()
? actionFocused()
? theme.background.surface.overlay
? theme.background.raised.high
: (option.bg ?? theme.background.action.primary.focused)
: RGBA.fromInts(0, 0, 0, 0)
}
+1 -1
View File
@@ -99,7 +99,7 @@ function expectFooter(actual: RunTheme, theme: ResolvedTheme) {
surface: theme.contextual.elevated.background.default,
pane: theme.contextual.overlay.background.default,
border: theme.border.default,
line: theme.background.surface.overlay,
line: theme.background.raised.high,
}
Object.entries(expected).forEach(([key, color]) => {
expect(rgba(actual.footer[key as keyof typeof expected]).toInts()).toEqual(color.toInts())
+8 -8
View File
@@ -19,12 +19,12 @@ test("provides reactive properties, states, contexts, and color operations", ()
expect(theme.hue.interactive[500]).toBe(resolved().hue.interactive[500])
expect(theme.hue.gray[200]).toBe(resolved().hue.gray[200])
expect(theme.categorical.map((scale) => scale[500])).toEqual(resolved().categorical.map((scale) => scale[500]))
expect(theme.increase(theme.background.surface.offset, 1)).toBe(resolved().hue.neutral[400])
expect(theme.raise(theme.background.surface.offset)).toBe(resolved().hue.neutral[400])
expect(theme.increase(theme.background.raised.base, 1)).toBe(resolved().hue.neutral[400])
expect(theme.raise(theme.background.raised.base)).toBe(resolved().hue.neutral[400])
expect(theme.decrease(theme.hue.red[300], 2)).toBe(resolved().hue.red[100])
expect(theme.increase(theme.hue.red[900], 3)).toBe(resolved().hue.red[900])
expect(theme.decrease(theme.hue.red[100], 3)).toBe(resolved().hue.red[100])
expect(theme.source(theme.background.surface.offset)).toEqual({ hue: "neutral", step: 300 })
expect(theme.source(theme.background.raised.base)).toEqual({ hue: "neutral", step: 300 })
const equivalent = RGBA.fromInts(...resolved().hue.green[500].toInts())
expect(theme.source(equivalent)).toBeUndefined()
expect(theme.increase(equivalent, 1)).toBe(equivalent)
@@ -46,8 +46,8 @@ test("provides reactive properties, states, contexts, and color operations", ()
expect(theme.background.formfield.selected).toBe(resolved().background.formfield.selected)
expect(theme.background.formfield.focused).toBe(resolved().background.formfield.focused)
expect(theme.background.formfield.disabled).toBe(resolved().background.formfield.disabled)
expect(theme.background.surface.offset).toBe(resolved().background.surface.offset)
expect(theme.background.surface.overlay).toBe(resolved().background.surface.overlay)
expect(theme.background.raised.base).toBe(resolved().background.raised.base)
expect(theme.background.raised.high).toBe(resolved().background.raised.high)
expect(theme.scrollbar.default).toBe(resolved().scrollbar.default)
expect(theme.diff.text.added).toBe(resolved().diff.text.added)
@@ -58,14 +58,14 @@ test("provides reactive properties, states, contexts, and color operations", ()
expect(current().background.action.primary.focused).toBe(
resolved().contextual.elevated.background.action.primary.focused,
)
expect(current().background.action.primary.hovered).toBe(resolved().background.surface.overlay)
expect(current().background.action.primary.hovered).toBe(resolved().background.raised.high)
expect(current().background.formfield.selected).toBe(resolved().contextual.elevated.background.formfield.selected)
setResolved(resolveTheme(selectTheme(DEFAULT_THEME, "dark")))
setMode("dark")
expect(current().text.default).toBe(resolved().contextual.elevated.text.default)
expect(current().decrease(current().background.surface.offset, 1)).toBe(resolved().hue.neutral[600])
expect(current().raise(current().background.surface.offset)).toBe(resolved().hue.neutral[600])
expect(current().decrease(current().background.raised.base, 1)).toBe(resolved().hue.neutral[600])
expect(current().raise(current().background.raised.base)).toBe(resolved().hue.neutral[600])
})
test("a stable component theme view follows presentation context changes", () => {
+6 -6
View File
@@ -71,21 +71,21 @@ test("resolves independent definitions and hue aliases", () => {
expect(lightTheme.categorical[0]).toBe(lightTheme.hue.blue)
expect(lightTheme.source(lightTheme.hue.blue[500])).toEqual({ hue: "blue", step: 500 })
expect(lightTheme.source(lightTheme.hue.neutral[200])).toEqual({ hue: "neutral", step: 200 })
expect(lightTheme.source(lightTheme.background.surface.offset)).toEqual({ hue: "neutral", step: 300 })
expect(lightTheme.source(lightTheme.background.raised.base)).toEqual({ hue: "neutral", step: 300 })
expect(lightTheme.increase(lightTheme.hue.red[100])).toBe(lightTheme.hue.red[200])
expect(lightTheme.decrease(lightTheme.hue.red[200])).toBe(lightTheme.hue.red[100])
expect(lightTheme.contextual.elevated.increase(lightTheme.hue.red[100])).toBe(lightTheme.hue.red[200])
expect(lightTheme.text.default).toBeInstanceOf(RGBA)
expect(darkTheme.background.default).toBeInstanceOf(RGBA)
expect(lightTheme.background.surface.offset).toBe(lightTheme.hue.neutral[300])
expect(lightTheme.background.surface.overlay).toBe(lightTheme.hue.neutral[400])
expect(lightTheme.background.raised.base).toBe(lightTheme.hue.neutral[300])
expect(lightTheme.background.raised.high).toBe(lightTheme.hue.neutral[400])
expect(lightTheme.syntax.keyword).toBeInstanceOf(RGBA)
expect(lightTheme.text.action.primary.default).toBe(lightTheme.hue.neutral[200])
expect(lightTheme.contextual.elevated.background.action.primary.default).toBe(lightTheme.hue.interactive[500])
expect(lightTheme.contextual.elevated.background.default).toBe(lightTheme.background.surface.offset)
expect(lightTheme.contextual.elevated.background.default).toBe(lightTheme.background.raised.base)
expect(lightTheme.contextual.elevated.text.action.primary.default).toBe(lightTheme.hue.neutral[100])
expect(lightTheme.contextual.overlay.background.action.primary.default).toBe(lightTheme.hue.interactive[500])
expect(lightTheme.contextual.overlay.background.default).toBe(lightTheme.background.surface.overlay)
expect(lightTheme.contextual.overlay.background.default).toBe(lightTheme.background.raised.high)
expect(lightTheme.contextual.overlay.text.action.primary.default).toBe(lightTheme.hue.neutral[100])
expect(darkTheme.contextual.elevated.background.action.primary.default).toBe(darkTheme.hue.interactive[400])
expect(darkTheme.contextual.elevated.text.action.primary.default).toBe(darkTheme.hue.neutral[200])
@@ -244,7 +244,7 @@ test("resolves elevated hover surfaces from direct colors", () => {
const theme = resolveSource(
{
version: 2,
light: { background: { surface: { offset: "#123456", overlay: "#234567" } } },
light: { background: { raised: { base: "#123456", high: "#234567" } } },
dark: {},
},
"light",
@@ -19,7 +19,7 @@ const text = {
const background = {
default: "$hue.neutral.100",
surface: { offset: "$hue.neutral.200", overlay: "$hue.neutral.300" },
raised: { base: "$hue.neutral.200", high: "$hue.neutral.300", max: "$hue.neutral.400" },
action: {
primary: {
default: "$hue.interactive.600",
@@ -24,17 +24,17 @@ test("migrates resolved V1 modes into V2 tokens", () => {
expect(migrated.light.text?.subdued).toBe("$hue.neutral.600")
expect(migrated.light.background?.action?.primary?.default).toBe("transparent")
expect(migrated.light.background?.default).toBe("$hue.neutral.200")
expect(migrated.light.background?.surface?.offset).toBe("$hue.neutral.300")
expect(migrated.light.background?.surface?.overlay).toBe("$hue.neutral.400")
expect(migrated.light.background?.raised?.base).toBe("$hue.neutral.300")
expect(migrated.light.background?.raised?.high).toBe("$hue.neutral.400")
expect(migrated.dark.background?.default).toBe("$hue.neutral.800")
expect(migrated.dark.background?.surface?.offset).toBe("$hue.neutral.700")
expect(migrated.dark.background?.surface?.overlay).toBe("$hue.neutral.600")
expect(migrated.dark.background?.raised?.base).toBe("$hue.neutral.700")
expect(migrated.dark.background?.raised?.high).toBe("$hue.neutral.600")
expect(migrated.light.text?.action?.primary?.default).toBe("$text.default")
expect(migrated.light.text?.action?.secondary?.default).toBe("$text.subdued")
expect(migrated.light.text?.action?.secondary?.$hovered).toBe("$text.default")
expect(migrated.light.background?.action?.primary?.$selected).toBe("transparent")
expect(resolved.background.surface.offset.toInts()).toEqual(legacy.backgroundPanel.toInts())
expect(resolved.background.surface.overlay.toInts()).toEqual(legacy.backgroundElement.toInts())
expect(resolved.background.raised.base.toInts()).toEqual(legacy.backgroundPanel.toInts())
expect(resolved.background.raised.high.toInts()).toEqual(legacy.backgroundElement.toInts())
expect(resolved.background.formfield.selected.toInts()).toEqual(legacy.background.toInts())
expect(resolved.background.formfield.focused.toInts()).toEqual(legacy.background.toInts())
expect(resolved.text.formfield.default.toInts()).toEqual(legacy.text.toInts())