Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 9 additions & 27 deletions packages/tui/src/routes/session/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function requestOptions(form: FormWithLocation) {

export function FormPrompt(props: { form: FormWithLocation }) {
const client = useClient()
const { themeV2 } = useTheme().contextual("elevated")
const { themeV2, mode: themeMode } = useTheme().contextual("elevated")
const renderer = useRenderer()
const dimensions = useTerminalDimensions()
const keymap = Keymap.use()
Expand Down Expand Up @@ -754,7 +754,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
<box
backgroundColor={themeV2.background()}
border={["left"]}
borderColor={themeV2.hue.accent(500)}
borderColor={themeV2.hue.interactive(themeMode() === "light" ? 800 : 200)}
customBorderChars={SplitBorder.customBorderChars}
>
<box gap={1} paddingLeft={1} paddingRight={3} paddingTop={1} paddingBottom={1}>
Expand Down Expand Up @@ -857,11 +857,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
{external().url}
</text>
<text
fg={
store.answers[external().key] === true
? themeV2.text.feedback.success()
: themeV2.text.subdued()
}
fg={store.answers[external().key] === true ? themeV2.text.feedback.success() : themeV2.text.subdued()}
>
{store.answers[external().key] === true
? "✓ Acknowledged"
Expand Down Expand Up @@ -925,25 +921,15 @@ export function FormPrompt(props: { form: FormWithLocation }) {
>
<box flexDirection="row">
<box
backgroundColor={
active() ? themeV2.background.formfield("focused") : themeV2.background()
}
backgroundColor={active() ? themeV2.background.formfield("focused") : themeV2.background()}
paddingRight={1}
>
<text fg={themeV2.text.formfield(active() ? "focused" : "default")}>
{`${i() + 1}.`}
</text>
<text fg={themeV2.text.formfield(active() ? "focused" : "default")}>{`${i() + 1}.`}</text>
</box>
<box
backgroundColor={
active() ? themeV2.background.formfield("focused") : themeV2.background()
}
backgroundColor={active() ? themeV2.background.formfield("focused") : themeV2.background()}
>
<text
fg={themeV2.text.formfield(
active() ? "focused" : picked() ? "selected" : "default",
)}
>
<text fg={themeV2.text.formfield(active() ? "focused" : picked() ? "selected" : "default")}>
{multi() ? `[${picked() ? "✓" : " "}] ${row.label}` : row.label}
</text>
</box>
Expand Down Expand Up @@ -978,9 +964,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
{`${rows().length + 1}.`}
</text>
</box>
<box
backgroundColor={other() ? themeV2.background.formfield("focused") : themeV2.background()}
>
<box backgroundColor={other() ? themeV2.background.formfield("focused") : themeV2.background()}>
<text
fg={
other()
Expand Down Expand Up @@ -1052,9 +1036,7 @@ export function FormPrompt(props: { form: FormWithLocation }) {
<span style={{ fg: themeV2.text.subdued() }}>{truncate(fieldLabel(item), 40)}:</span>{" "}
<span
style={{
fg: acknowledged()
? themeV2.text.feedback.success()
: themeV2.text.feedback.error(),
fg: acknowledged() ? themeV2.text.feedback.success() : themeV2.text.feedback.error(),
}}
>
{acknowledged() ? "Acknowledged" : "(acknowledgement required)"}
Expand Down
38 changes: 19 additions & 19 deletions packages/tui/src/theme/v2/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,18 @@ export const DEFAULT_THEME = {
neutral: "$hue.gray",
},
text: {
default: "$hue.neutral.900",
default: "$hue.neutral.800",
subdued: "$hue.neutral.600",
action: {
primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
primary: { default: "$hue.neutral.200", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.200", $disabled: "$hue.neutral.500" },
},
formfield: {
default: "$hue.neutral.900",
default: "$hue.neutral.800",
$focused: "$text.action.primary.default",
$pressed: "$hue.neutral.100",
$pressed: "$hue.neutral.200",
$disabled: "$hue.neutral.500",
$selected: "$hue.interactive.600",
$selected: "$hue.interactive.700",
},
feedback: {
error: { default: "$hue.red.700", subdued: "$hue.red.600" },
Expand All @@ -118,10 +118,10 @@ export const DEFAULT_THEME = {
},
},
background: {
default: "$hue.neutral.100",
default: "$hue.neutral.200",
surface: {
offset: "$hue.neutral.200",
overlay: "$hue.neutral.300",
offset: "$hue.neutral.300",
overlay: "$hue.neutral.400",
},
action: {
primary: {
Expand Down Expand Up @@ -309,16 +309,16 @@ export const DEFAULT_THEME = {
neutral: "$hue.gray",
},
text: {
default: "$hue.neutral.100",
default: "$hue.neutral.200",
subdued: "$hue.neutral.400",
action: {
primary: { default: "$hue.neutral.100", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.100", $disabled: "$hue.neutral.500" },
primary: { default: "$hue.neutral.200", $disabled: "$hue.neutral.500" },
destructive: { default: "$hue.red.200", $disabled: "$hue.neutral.500" },
},
formfield: {
default: "$hue.neutral.100",
default: "$hue.neutral.200",
$focused: "$text.action.primary.default",
$pressed: "$hue.neutral.100",
$pressed: "$hue.neutral.200",
$disabled: "$hue.neutral.500",
$selected: "$hue.interactive.500",
},
Expand All @@ -330,10 +330,10 @@ export const DEFAULT_THEME = {
},
},
background: {
default: "$hue.neutral.900",
default: "$hue.neutral.800",
surface: {
offset: "$hue.neutral.800",
overlay: "$hue.neutral.700",
offset: "$hue.neutral.700",
overlay: "$hue.neutral.600",
},
action: {
primary: {
Expand Down Expand Up @@ -412,14 +412,14 @@ export const DEFAULT_THEME = {
codeBlock: "$hue.neutral.100",
},
"@context:elevated": {
text: { action: { primary: { default: "$hue.neutral.100" } } },
text: { action: { primary: { default: "$hue.neutral.200" } } },
background: {
default: "$background.surface.offset",
action: { primary: { default: "$hue.interactive.400", $hovered: "$background.surface.overlay" } },
},
},
"@context:overlay": {
text: { action: { primary: { default: "$hue.neutral.900" } } },
text: { action: { primary: { default: "$hue.neutral.200" } } },
background: {
default: "$background.surface.overlay",
action: { primary: { default: "$hue.interactive.400" } },
Expand Down
47 changes: 29 additions & 18 deletions packages/tui/src/theme/v2/v1-migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ function migrateMode(theme: Theme, mode: "light" | "dark"): ThemeFile["light"] {
const selected = hex(selectedForeground(theme, theme.primary))
const destructive = hex(selectedForeground(theme, theme.error))
const hues = inferHues(theme, mode)
const text = mode === "light" ? "$hue.neutral.900" : "$hue.neutral.100"
const textMuted = mode === "light" ? "$hue.neutral.700" : "$hue.neutral.300"
const primary = mode === "light" ? "$hue.interactive.900" : "$hue.interactive.100"
const background = mode === "light" ? "$hue.neutral.100" : "$hue.neutral.900"
const backgroundPanel = mode === "light" ? "$hue.neutral.200" : "$hue.neutral.800"
const backgroundMenu = mode === "light" ? "$hue.neutral.300" : "$hue.neutral.700"
const text = mode === "light" ? "$hue.neutral.800" : "$hue.neutral.200"
const textMuted = mode === "light" ? "$hue.neutral.600" : "$hue.neutral.400"
const primary = mode === "light" ? "$hue.interactive.800" : "$hue.interactive.200"
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"

return {
hue: {
Expand Down Expand Up @@ -233,13 +233,13 @@ function selectedForeground(theme: Theme, background: RGBA) {

function hueScale(color: RGBA, mode: "light" | "dark") {
const value = toOklch(color)
const anchor = mode === "light" ? 900 : 100
const anchor = mode === "light" ? 800 : 200
const endpoint = mode === "light" ? Math.max(0.97, value.l) : Math.min(0.18, value.l)
const alpha = color.toInts()[3]
return Object.fromEntries(
HueStep.literals.map((step) => {
if (step === anchor) return [step, hex(color)]
const progress = mode === "light" ? (900 - step) / 800 : (step - 100) / 800
const progress = mode === "light" ? (anchor - step) / (anchor - 100) : (step - anchor) / (900 - anchor)
const generated = oklchToHex({
l: value.l + (endpoint - value.l) * progress,
c: value.c * (1 - progress * 0.5),
Expand All @@ -256,20 +256,26 @@ function neutralScale(theme: Theme, mode: "light" | "dark") {
HueStep.literals.map((step) => {
const exact = anchors.find((anchor) => anchor.step === step)
if (exact) return [step, hex(exact.color)]
const lower = anchors.filter((anchor) => anchor.step < step).at(-1)!
const upper = anchors.find((anchor) => anchor.step > step)!
const first = anchors[0]!
const last = anchors.at(-1)!
const [lower, upper] =
step < first.step
? [first, anchors[1]!]
: step > last.step
? [anchors.at(-2)!, last]
: [anchors.filter((anchor) => anchor.step < step).at(-1)!, anchors.find((anchor) => anchor.step > step)!]
return [step, interpolate(lower.color, upper.color, (step - lower.step) / (upper.step - lower.step))]
}),
) as Record<HueStep, string>
}

function neutralAnchors(theme: Theme, mode: "light" | "dark") {
const light: { step: HueStep; color: RGBA }[] = [
{ step: 100, color: theme.background },
{ step: 200, color: theme.backgroundPanel },
{ step: 300, color: theme.backgroundElement || theme.backgroundMenu },
{ step: 700, color: theme.textMuted },
{ step: 900, color: theme.text },
{ step: 200, color: theme.background },
{ step: 300, color: theme.backgroundPanel },
{ step: 400, color: theme.backgroundElement || theme.backgroundMenu },
{ step: 600, color: theme.textMuted },
{ step: 800, color: theme.text },
]
if (mode === "light") return light
return light.toReversed().map((source) => ({ ...source, step: (1000 - source.step) as HueStep }))
Expand All @@ -278,13 +284,18 @@ function neutralAnchors(theme: Theme, mode: "light" | "dark") {
function interpolate(first: RGBA, second: RGBA, amount: number) {
const start = toOklch(first)
const end = toOklch(second)
const hue = ((((end.h - start.h) % 360) + 540) % 360) - 180
const startHue = Number.isFinite(start.h) ? start.h : Number.isFinite(end.h) ? end.h : 0
const endHue = Number.isFinite(end.h) ? end.h : startHue
const hue = ((((endHue - startHue) % 360) + 540) % 360) - 180
const generated = oklchToHex({
l: start.l + (end.l - start.l) * amount,
c: start.c + (end.c - start.c) * amount,
h: start.h + hue * amount,
h: startHue + hue * amount,
})
const alpha = Math.round(first.toInts()[3] + (second.toInts()[3] - first.toInts()[3]) * amount)
const alpha = Math.max(
0,
Math.min(255, Math.round(first.toInts()[3] + (second.toInts()[3] - first.toInts()[3]) * amount)),
)
return alpha === 255 ? generated : `${generated}${byte(alpha)}`
}

Expand Down
Loading
Loading