Skip to content

Commit fbd6df3

Browse files
authored
feat(webapp): Themes + contrast settings update (#4206)
Adds System Preferences, Dark and Light themes, gated by the `hasThemeSwitcher` feature flag (off by default — dark stays the default theme for everyone). Old theme is now "Classic"and set as default. "System preferences" theme has both Light and Dark modes and uses your laptop settings to use a correct one. It has less color accents (specifically less colored text), and they are the same for both modes, only grayscale values change between them. And Light/Dark themes can be used separately. New Contrast setting is available for System Preferences, Dark and Light themes - it changes the contrast for the whole app. All new visual Settings live in Account.
1 parent 57254b5 commit fbd6df3

107 files changed

Lines changed: 7792 additions & 5314 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.server-changes/light-theme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: feature
4+
---
5+
6+
The dashboard gets a refreshed Dark theme as its new default look. On the account page you can switch to Light, System preference (follows your OS), or the original Classic look, and fine-tune the new themes with a contrast slider.

apps/webapp/app/assets/icons/MachineIcon.tsx

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,33 @@ export function MachineDefaultIcon({ className }: { className?: string }) {
4848

4949
function MachineIconNoMachine({ className }: { className?: string }) {
5050
return (
51-
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
51+
<svg
52+
className={cn("text-[#D7D9DD] light:text-charcoal-500", className)}
53+
viewBox="0 0 20 20"
54+
fill="none"
55+
xmlns="http://www.w3.org/2000/svg"
56+
>
5257
<path
5358
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H6.18945C6.25882 16.9453 6.32663 16.8872 6.39062 16.8232L10.2139 13H16.1875Z"
54-
fill="#D7D9DD"
59+
fill="currentColor"
5560
fillOpacity="0.4"
5661
/>
5762
<path
5863
d="M4.84961 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H8.84961L4.84961 12ZM16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H11.2139L15.2139 8H16.1875Z"
59-
fill="#D7D9DD"
64+
fill="currentColor"
6065
fillOpacity="0.4"
6166
/>
6267
<path
6368
d="M9.85059 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H13.8506L9.85059 7Z"
64-
fill="#D7D9DD"
69+
fill="currentColor"
6570
fillOpacity="0.4"
6671
/>
6772
<line
6873
x1="3"
6974
y1="16.9998"
7075
x2="17.0018"
7176
y2="2.99805"
72-
stroke="#D7D9DD"
77+
stroke="currentColor"
7378
strokeOpacity="0.4"
7479
strokeWidth="2"
7580
strokeLinecap="round"
@@ -83,17 +88,17 @@ function MachineIconMicro({ className }: { className?: string }) {
8388
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
8489
<path
8590
d="M16.1875 13C16.6362 13 17 13.3638 17 13.8125V16.1875C17 16.6362 16.6362 17 16.1875 17H3.8125C3.36377 17 3 16.6362 3 16.1875V13.8125C3 13.3638 3.36377 13 3.8125 13H16.1875ZM5 14C4.44772 14 4 14.4477 4 15C4 15.5523 4.44772 16 5 16C5.55228 16 6 15.5523 6 15C6 14.4477 5.55228 14 5 14Z"
86-
fill="#D7D9DD"
91+
fill="currentColor"
8792
fillOpacity="0.4"
8893
/>
8994
<path
9095
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
91-
fill="#D7D9DD"
96+
fill="currentColor"
9297
fillOpacity="0.4"
9398
/>
9499
<path
95100
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
96-
fill="#D7D9DD"
101+
fill="currentColor"
97102
fillOpacity="0.4"
98103
/>
99104
</svg>
@@ -109,12 +114,12 @@ function MachineIconSmall1x({ className }: { className?: string }) {
109114
/>
110115
<path
111116
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
112-
fill="#D7D9DD"
117+
fill="currentColor"
113118
fillOpacity="0.4"
114119
/>
115120
<path
116121
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
117-
fill="#D7D9DD"
122+
fill="currentColor"
118123
fillOpacity="0.4"
119124
/>
120125
</svg>
@@ -130,12 +135,12 @@ function MachineIconSmall2x({ className }: { className?: string }) {
130135
/>
131136
<path
132137
d="M16.1875 8C16.6362 8 17 8.36377 17 8.8125V11.1875C17 11.6362 16.6362 12 16.1875 12H3.8125C3.36377 12 3 11.6362 3 11.1875V8.8125C3 8.36377 3.36377 8 3.8125 8H16.1875ZM5 9C4.44772 9 4 9.44772 4 10C4 10.5523 4.44772 11 5 11C5.55228 11 6 10.5523 6 10C6 9.44772 5.55228 9 5 9Z"
133-
fill="#D7D9DD"
138+
fill="currentColor"
134139
fillOpacity="0.4"
135140
/>
136141
<path
137142
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
138-
fill="#D7D9DD"
143+
fill="currentColor"
139144
fillOpacity="0.4"
140145
/>
141146
</svg>
@@ -155,7 +160,7 @@ function MachineIconMedium1x({ className }: { className?: string }) {
155160
/>
156161
<path
157162
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
158-
fill="#D7D9DD"
163+
fill="currentColor"
159164
fillOpacity="0.4"
160165
/>
161166
</svg>
@@ -175,7 +180,7 @@ function MachineIconMedium2x({ className }: { className?: string }) {
175180
/>
176181
<path
177182
d="M16.1875 3C16.6362 3 17 3.36377 17 3.8125V6.1875C17 6.63623 16.6362 7 16.1875 7H3.8125C3.36377 7 3 6.63623 3 6.1875V3.8125C3 3.36377 3.36377 3 3.8125 3H16.1875ZM5 4C4.44772 4 4 4.44772 4 5C4 5.55228 4.44772 6 5 6C5.55228 6 6 5.55228 6 5C6 4.44772 5.55228 4 5 4Z"
178-
fill="#D7D9DD"
183+
fill="currentColor"
179184
fillOpacity="0.4"
180185
/>
181186
</svg>
790 Bytes
Loading
3.5 KB
Loading
17.6 KB
Loading

apps/webapp/app/components/AskAI.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ function ChatMessages({
458458
<div className="flex flex-col">
459459
<Callout variant="error" className="mb-4">
460460
<Paragraph className="font-semibold text-error">Error generating answer:</Paragraph>
461-
<Paragraph className="text-rose-300">
461+
<Paragraph className="text-rose-500 dark:text-rose-300">
462462
{error} If the problem persists after retrying, please contact support.
463463
</Paragraph>
464464
</Callout>

apps/webapp/app/components/BackgroundWrapper.tsx

Lines changed: 61 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,74 @@
11
import { type ReactNode } from "react";
22
import blurredDashboardBackgroundMenuTop from "~/assets/images/blurred-dashboard-background-menu-top.jpg";
3+
import blurredDashboardBackgroundMenuTopLight from "~/assets/images/blurred-dashboard-background-menu-top-light.jpg";
34
import blurredDashboardBackgroundMenuBottom from "~/assets/images/blurred-dashboard-background-menu-bottom.jpg";
5+
import blurredDashboardBackgroundMenuBottomLight from "~/assets/images/blurred-dashboard-background-menu-bottom-light.jpg";
46
import blurredDashboardBackgroundTable from "~/assets/images/blurred-dashboard-background-table.jpg";
7+
import blurredDashboardBackgroundTableLight from "~/assets/images/blurred-dashboard-background-table-light.jpg";
8+
9+
/* Blurred dashboard screenshots; the -light set is the same artwork with the
10+
lightness inverted for the light theme. */
11+
const BACKDROPS = [
12+
{
13+
images: {
14+
menuTop: blurredDashboardBackgroundMenuTop,
15+
menuBottom: blurredDashboardBackgroundMenuBottom,
16+
table: blurredDashboardBackgroundTable,
17+
},
18+
tableFill: "#101214",
19+
/* `light` is a zero-specificity variant, so `light:lg:hidden` ties with
20+
`lg:block` - the `!` guarantees the dark backdrop loses on Light. */
21+
className: "hidden lg:block light:lg:hidden!",
22+
},
23+
{
24+
images: {
25+
menuTop: blurredDashboardBackgroundMenuTopLight,
26+
menuBottom: blurredDashboardBackgroundMenuBottomLight,
27+
table: blurredDashboardBackgroundTableLight,
28+
},
29+
tableFill: "#f4f5f7",
30+
className: "hidden light:lg:block",
31+
},
32+
];
533

634
export function BackgroundWrapper({ children }: { children: ReactNode }) {
735
return (
836
<div className="relative h-full w-full overflow-hidden bg-background-dimmed lg:bg-transparent">
9-
<div
10-
className="absolute left-0 top-0 hidden w-[260px] bg-contain bg-top-left bg-no-repeat lg:block"
11-
style={{
12-
backgroundImage: `url(${blurredDashboardBackgroundMenuTop})`,
13-
aspectRatio: "auto",
14-
height: "100vh",
15-
backgroundSize: "260px auto",
16-
}}
17-
/>
37+
{BACKDROPS.map(({ images, tableFill, className }) => (
38+
<div key={tableFill} className={className}>
39+
<div
40+
className="absolute left-0 top-0 w-[260px] bg-contain bg-top-left bg-no-repeat"
41+
style={{
42+
backgroundImage: `url(${images.menuTop})`,
43+
aspectRatio: "auto",
44+
height: "100vh",
45+
backgroundSize: "260px auto",
46+
}}
47+
/>
1848

19-
<div
20-
className="absolute bottom-0 left-0 hidden w-[260px] bg-contain bg-bottom-left bg-no-repeat lg:block"
21-
style={{
22-
backgroundImage: `url(${blurredDashboardBackgroundMenuBottom})`,
23-
aspectRatio: "auto",
24-
height: "100vh",
25-
backgroundSize: "260px auto",
26-
}}
27-
/>
49+
<div
50+
className="absolute bottom-0 left-0 w-[260px] bg-contain bg-bottom-left bg-no-repeat"
51+
style={{
52+
backgroundImage: `url(${images.menuBottom})`,
53+
aspectRatio: "auto",
54+
height: "100vh",
55+
backgroundSize: "260px auto",
56+
}}
57+
/>
2858

29-
<div
30-
className="absolute top-0 hidden bg-top-left bg-no-repeat lg:block"
31-
style={{
32-
left: "260px",
33-
backgroundImage: `url(${blurredDashboardBackgroundTable})`,
34-
width: "100%",
35-
height: "100vh",
36-
backgroundSize: "1200px auto",
37-
backgroundColor: "#101214",
38-
}}
39-
/>
59+
<div
60+
className="absolute top-0 bg-top-left bg-no-repeat"
61+
style={{
62+
left: "260px",
63+
backgroundImage: `url(${images.table})`,
64+
width: "100%",
65+
height: "100vh",
66+
backgroundSize: "1200px auto",
67+
backgroundColor: tableFill,
68+
}}
69+
/>
70+
</div>
71+
))}
4072

4173
<div className="relative z-10 h-full w-full">{children}</div>
4274
</div>

apps/webapp/app/components/BlankStatePanels.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "@heroicons/react/20/solid";
1313
import { useLocation } from "react-use";
1414
import { AIChatIcon } from "~/assets/icons/AIChatIcon";
15+
import { AIPenIcon } from "~/assets/icons/AIPenIcon";
1516
import { BranchEnvironmentIconSmall } from "~/assets/icons/EnvironmentIcons";
1617
import { WaitpointTokenIcon } from "~/assets/icons/WaitpointTokenIcon";
1718
import openBulkActionsPanel from "~/assets/images/open-bulk-actions-panel.png";
@@ -742,7 +743,7 @@ export function PromptsNone() {
742743
return (
743744
<InfoPanel
744745
title="Define your first prompt"
745-
icon={AIChatIcon}
746+
icon={AIPenIcon}
746747
iconClassName="text-aiPrompts"
747748
panelClassName="max-w-lg"
748749
accessory={

apps/webapp/app/components/BulkActionFilterSummary.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Paragraph } from "./primitives/Paragraph";
99
import simplur from "simplur";
1010
import { appliedSummary, dateFromString, timeFilterRenderValues } from "./runs/v3/SharedFilters";
1111
import { formatNumber } from "~/utils/numberFormatter";
12-
import { SpinnerWhite } from "./primitives/Spinner";
12+
import { Spinner } from "./primitives/Spinner";
1313
import { ArrowPathIcon, CheckIcon, XCircleIcon } from "@heroicons/react/20/solid";
1414
import { XCircleIcon as XCircleIconOutline } from "@heroicons/react/24/outline";
1515
import assertNever from "assert-never";
@@ -306,5 +306,5 @@ export function EstimatedCount({ count }: { count?: number }) {
306306
return <>~{formatNumber(count)}</>;
307307
}
308308

309-
return <SpinnerWhite className="mx-0.5 -mt-0.5 inline size-3" />;
309+
return <Spinner color="blue" className="mx-0.5 -mt-0.5 inline size-3" />;
310310
}

apps/webapp/app/components/DevPresence.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import disconnectedImage from "../assets/images/cli-disconnected.png";
1515
import { InlineCode } from "./code/InlineCode";
1616
import { Button } from "./primitives/Buttons";
1717
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "./primitives/Dialog";
18+
import { cn } from "~/utils/cn";
1819
import { Paragraph } from "./primitives/Paragraph";
1920
import { TextLink } from "./primitives/TextLink";
2021
import { PackageManagerProvider, TriggerDevStepV3 } from "./SetupCommands";
@@ -165,7 +166,10 @@ export function DevPresencePanel({ isConnected }: { isConnected: boolean | undef
165166
width={282}
166167
height={45}
167168
/>
168-
<Paragraph variant="small" className={isConnected ? "text-success" : "text-error"}>
169+
<Paragraph
170+
variant="small"
171+
className={cn("system-mono-label", isConnected ? "text-success" : "text-error")}
172+
>
169173
{isConnected === undefined
170174
? "Checking connection..."
171175
: isConnected

0 commit comments

Comments
 (0)