1import defaultThemeVariables from "../../generated-uniwind-default-theme-variables.json";
2import {
3 DEFAULT_MOBILE_THEME_ID,
4 flattenThemeColor,
5 getMobileThemeVariables,
6 themeColorWithAlpha,
7 themeColorWithContrast,
8 type MobileThemeAppearance,
9 type MobileThemeId,
10 type MobileThemeVariables,
11} from "./mobileTheme";
12
13const defaults = defaultThemeVariables as Readonly<
14 Record<MobileThemeAppearance, MobileThemeVariables>
15>;
16
17const CARD_TEXT_ROLES = [
18 "--color-foreground-secondary",
19 "--color-foreground-muted",
20 "--color-placeholder",
21 "--color-primary-text",
22] as const;
23
24// A selected row in a sidebar sits on the drawer, so it must stay visible there with its labels.
25function withVisibleSelection(variables: MobileThemeVariables): MobileThemeVariables {
26 const selected = themeColorWithContrast(
27 flattenThemeColor(variables["--color-thread-selected"], variables["--color-drawer"]),
28 flattenThemeColor(variables["--color-drawer"], variables["--color-screen"]),
29 1.2,
30 );
31 return {
32 ...variables,
33 "--color-thread-selected": selected,
34 "--color-thread-selected-foreground": themeColorWithContrast(
35 variables["--color-thread-selected-foreground"],
36 selected,
37 ),
38 "--color-thread-selected-foreground-muted": themeColorWithContrast(
39 variables["--color-thread-selected-foreground-muted"],
40 selected,
41 ),
42 };
43}
44
45export function getMobileThemeRuntimeVariables(
46 themeId: MobileThemeId,
47 appearance: MobileThemeAppearance,
48 platform: string,
49): MobileThemeVariables {
50 return withVisibleSelection(platformThemeVariables(themeId, appearance, platform));
51}
52
53function platformThemeVariables(
54 themeId: MobileThemeId,
55 appearance: MobileThemeAppearance,
56 platform: string,
57): MobileThemeVariables {
58 const usesDefaultPalette = themeId === DEFAULT_MOBILE_THEME_ID || themeId === "material-you";
59 const variables = usesDefaultPalette
60 ? defaults[appearance]
61 : getMobileThemeVariables(themeId, appearance);
62 const frame = themeColorWithAlpha(
63 variables[usesDefaultPalette ? "--color-row-hover" : "--color-drawer"],
64 1,
65 );
66 if (platform === "ios" && usesDefaultPalette && appearance === "light") {
67 return {
68 ...variables,
69 "--color-header": frame,
70 "--color-header-foreground": variables["--color-drawer-foreground"],
71 "--color-drawer": frame,
72 "--color-drawer-foreground-muted": variables["--color-foreground-muted"],
73 };
74 }
75 if (platform !== "android") return variables;
76
77 // Android paints the drawer as the page inside a lighter frame, so dark cards lift off both.
78 const screen = variables["--color-screen"];
79 const groupedCard =
80 appearance === "dark"
81 ? themeColorWithContrast(
82 variables["--color-grouped-card"],
83 [screen, flattenThemeColor(frame, screen)],
84 1.2,
85 )
86 : variables["--color-grouped-card"];
87 const readableOnCard = (role: (typeof CARD_TEXT_ROLES)[number]) =>
88 [role, themeColorWithContrast(variables[role], groupedCard)] as const;
89 return {
90 ...variables,
91 ...Object.fromEntries(CARD_TEXT_ROLES.map(readableOnCard)),
92 "--color-header": frame,
93 "--color-header-foreground": variables["--color-drawer-foreground"],
94 "--color-drawer": screen,
95 "--color-grouped-card": groupedCard,
96 };
97}