| author | |
| committer | |
| log | e0649ed7d8f8b5f21d114ff2b09ef803184f1d05 |
| tree | d7fd12ae6e7ef0c4b3610a05675f8fc89c1f4358 |
| parent | 4d36142c022e0dc797034933d01e200818b4083d |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 112 insertions(+), 102 deletions(-)
apps/mobile/src/lib/materialYouTheme.android.ts created+108| ... | ... | @@ -0,0 +1,108 @@ |
| 1 | import { Hct, argbFromHex, hexFromArgb } from "@material/material-color-utilities"; | |
| 2 | ||
| 3 | import type { MobileThemeAppearance, MobileThemeVariables } from "./mobileTheme"; | |
| 4 | import type { MaterialYouPalette } from "./materialYouPalette"; | |
| 5 | ||
| 6 | function withAlpha(color: string, alpha: number): string { | |
| 7 | const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color); | |
| 8 | if (!match?.[1]) return color; | |
| 9 | const alphaByte = Math.round(Math.min(1, Math.max(0, alpha)) * 255) | |
| 10 | .toString(16) | |
| 11 | .padStart(2, "0") | |
| 12 | .toUpperCase(); | |
| 13 | return `#${match[1]}${alphaByte}`; | |
| 14 | } | |
| 15 | ||
| 16 | function materialTone(color: string, tone: number): string { | |
| 17 | const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color); | |
| 18 | if (!match?.[1]) return color; | |
| 19 | ||
| 20 | const source = Hct.fromInt(argbFromHex(match[1])); | |
| 21 | return `${hexFromArgb(Hct.from(source.hue, source.chroma, tone).toInt()).toUpperCase()}FF`; | |
| 22 | } | |
| 23 | ||
| 24 | export function materialYouPaletteToMobileThemeVariables( | |
| 25 | palette: MaterialYouPalette, | |
| 26 | appearance: MobileThemeAppearance, | |
| 27 | base: MobileThemeVariables, | |
| 28 | ): MobileThemeVariables { | |
| 29 | const dark = appearance === "dark"; | |
| 30 | const userBubble = dark ? materialTone(palette.inversePrimary, 30) : palette.primary; | |
| 31 | const userBubbleForeground = dark ? materialTone(palette.inversePrimary, 90) : palette.onPrimary; | |
| 32 | ||
| 33 | return { | |
| 34 | ...base, | |
| 35 | "--color-screen": palette.surface, | |
| 36 | "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98), | |
| 37 | "--color-sheet-solid": palette.surfaceContainerLow, | |
| 38 | "--color-card": palette.surfaceContainer, | |
| 39 | "--color-card-alt": palette.surfaceContainerHigh, | |
| 40 | "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8), | |
| 41 | "--color-thread-canvas": palette.surfaceContainerLow, | |
| 42 | "--color-thread-selected": palette.surfaceContainer, | |
| 43 | "--color-thread-selected-foreground": palette.onSurface, | |
| 44 | "--color-thread-selected-foreground-muted": palette.onSurfaceVariant, | |
| 45 | "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88), | |
| 46 | "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94), | |
| 47 | "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46), | |
| 48 | "--color-foreground": palette.onSurface, | |
| 49 | "--color-foreground-secondary": palette.onSurfaceVariant, | |
| 50 | "--color-foreground-muted": palette.onSurfaceVariant, | |
| 51 | "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58), | |
| 52 | "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65), | |
| 53 | "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46), | |
| 54 | "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34), | |
| 55 | "--color-subtle": withAlpha(palette.onSurface, 0.05), | |
| 56 | "--color-subtle-strong": withAlpha(palette.onSurface, 0.1), | |
| 57 | "--color-inline-skill-background": withAlpha(palette.tertiaryContainer, 0.52), | |
| 58 | "--color-inline-skill-border": withAlpha(palette.tertiary, 0.38), | |
| 59 | "--color-inline-skill-foreground": palette.onTertiaryContainer, | |
| 60 | "--color-primary": palette.primary, | |
| 61 | "--color-primary-foreground": palette.onPrimary, | |
| 62 | "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18), | |
| 63 | "--color-secondary": palette.secondaryContainer, | |
| 64 | "--color-secondary-foreground": palette.onSecondaryContainer, | |
| 65 | "--color-secondary-border": withAlpha(palette.outlineVariant, dark ? 0.42 : 0.56), | |
| 66 | "--color-switch-active-track": palette.primary, | |
| 67 | "--color-switch-active-thumb": palette.onPrimary, | |
| 68 | "--color-switch-inactive-track": palette.surfaceContainerHighest, | |
| 69 | "--color-switch-inactive-thumb": palette.outline, | |
| 70 | "--color-danger": palette.errorContainer, | |
| 71 | "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5), | |
| 72 | "--color-danger-foreground": palette.onErrorContainer, | |
| 73 | "--color-input": palette.surfaceContainerLowest, | |
| 74 | "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5), | |
| 75 | "--color-sidebar-search": palette.surfaceContainerLowest, | |
| 76 | "--color-placeholder": withAlpha(palette.onSurface, 0.62), | |
| 77 | "--color-icon": palette.onSurface, | |
| 78 | "--color-icon-muted": palette.onSurfaceVariant, | |
| 79 | "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68), | |
| 80 | "--color-header": palette.surfaceContainerHigh, | |
| 81 | "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48), | |
| 82 | "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72), | |
| 83 | "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18), | |
| 84 | "--color-status-bar": palette.surfaceContainerLow, | |
| 85 | "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10), | |
| 86 | "--color-md-strong": materialTone(palette.onSurfaceVariant, dark ? 90 : 10), | |
| 87 | "--color-md-link": palette.primary, | |
| 88 | "--color-md-blockquote-border": withAlpha(palette.primary, dark ? 0.34 : 0.26), | |
| 89 | "--color-md-blockquote-bg": withAlpha(palette.primaryContainer, dark ? 0.14 : 0.2), | |
| 90 | "--color-md-code-bg": withAlpha(palette.onSurface, dark ? 0.08 : 0.06), | |
| 91 | "--color-md-code-text": palette.onSurface, | |
| 92 | "--color-md-user-code-bg": withAlpha(userBubbleForeground, dark ? 0.18 : 0.22), | |
| 93 | "--color-md-user-code-text": userBubbleForeground, | |
| 94 | "--color-md-user-fence-bg": withAlpha(userBubbleForeground, dark ? 0.14 : 0.18), | |
| 95 | "--color-md-user-fence-text": userBubbleForeground, | |
| 96 | "--color-md-hr": withAlpha(palette.outlineVariant, dark ? 0.48 : 0.6), | |
| 97 | "--color-user-bubble": userBubble, | |
| 98 | "--color-user-bubble-foreground": userBubbleForeground, | |
| 99 | "--color-user-bubble-foreground-muted": withAlpha(userBubbleForeground, 0.78), | |
| 100 | "--color-user-bubble-skill-foreground": userBubbleForeground, | |
| 101 | "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22), | |
| 102 | "--color-drawer": palette.surface, | |
| 103 | "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12), | |
| 104 | "--color-dot-separator": withAlpha(palette.onSurface, 0.2), | |
| 105 | "--color-wordmark": palette.onSurface, | |
| 106 | "--color-chevron": withAlpha(palette.onSurface, 0.2), | |
| 107 | }; | |
| 108 | } |
apps/mobile/src/lib/materialYouTheme.test.ts+1-1| ... | ... | @@ -2,7 +2,7 @@ import { describe, expect, it } from "vite-plus/test"; |
| 2 | 2 | |
| 3 | 3 | import { getMobileThemeRuntimeVariables } from "./mobileThemeVariables"; |
| 4 | 4 | import type { MaterialYouPalette } from "./materialYouPalette"; |
| 5 | import { materialYouPaletteToMobileThemeVariables } from "./materialYouTheme"; | |
| 5 | import { materialYouPaletteToMobileThemeVariables } from "./materialYouTheme.android"; | |
| 6 | 6 | |
| 7 | 7 | const palette: MaterialYouPalette = { |
| 8 | 8 | primary: "#6750A4FF", |
apps/mobile/src/lib/materialYouTheme.ts+3-101| ... | ... | @@ -1,108 +1,10 @@ |
| 1 | import { Hct, argbFromHex, hexFromArgb } from "@material/material-color-utilities"; | |
| 2 | ||
| 3 | 1 | import type { MobileThemeAppearance, MobileThemeVariables } from "./mobileTheme"; |
| 4 | 2 | import type { MaterialYouPalette } from "./materialYouPalette"; |
| 5 | 3 | |
| 6 | function withAlpha(color: string, alpha: number): string { | |
| 7 | const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color); | |
| 8 | if (!match?.[1]) return color; | |
| 9 | const alphaByte = Math.round(Math.min(1, Math.max(0, alpha)) * 255) | |
| 10 | .toString(16) | |
| 11 | .padStart(2, "0") | |
| 12 | .toUpperCase(); | |
| 13 | return `#${match[1]}${alphaByte}`; | |
| 14 | } | |
| 15 | ||
| 16 | function materialTone(color: string, tone: number): string { | |
| 17 | const match = /^#([\da-f]{6})(?:[\da-f]{2})?$/iu.exec(color); | |
| 18 | if (!match?.[1]) return color; | |
| 19 | ||
| 20 | const source = Hct.fromInt(argbFromHex(match[1])); | |
| 21 | return `${hexFromArgb(Hct.from(source.hue, source.chroma, tone).toInt()).toUpperCase()}FF`; | |
| 22 | } | |
| 23 | ||
| 24 | 4 | export function materialYouPaletteToMobileThemeVariables( |
| 25 | palette: MaterialYouPalette, | |
| 26 | appearance: MobileThemeAppearance, | |
| 5 | _palette: MaterialYouPalette, | |
| 6 | _appearance: MobileThemeAppearance, | |
| 27 | 7 | base: MobileThemeVariables, |
| 28 | 8 | ): MobileThemeVariables { |
| 29 | const dark = appearance === "dark"; | |
| 30 | const userBubble = dark ? materialTone(palette.inversePrimary, 30) : palette.primary; | |
| 31 | const userBubbleForeground = dark ? materialTone(palette.inversePrimary, 90) : palette.onPrimary; | |
| 32 | ||
| 33 | return { | |
| 34 | ...base, | |
| 35 | "--color-screen": palette.surface, | |
| 36 | "--color-sheet": withAlpha(palette.surfaceContainerLow, 0.98), | |
| 37 | "--color-sheet-solid": palette.surfaceContainerLow, | |
| 38 | "--color-card": palette.surfaceContainer, | |
| 39 | "--color-card-alt": palette.surfaceContainerHigh, | |
| 40 | "--color-card-translucent": withAlpha(palette.surfaceContainer, 0.8), | |
| 41 | "--color-thread-canvas": palette.surfaceContainerLow, | |
| 42 | "--color-thread-selected": palette.surfaceContainer, | |
| 43 | "--color-thread-selected-foreground": palette.onSurface, | |
| 44 | "--color-thread-selected-foreground-muted": palette.onSurfaceVariant, | |
| 45 | "--color-composer-panel": withAlpha(palette.surfaceContainerLow, dark ? 0.92 : 0.88), | |
| 46 | "--color-composer-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.9 : 0.94), | |
| 47 | "--color-composer-border": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46), | |
| 48 | "--color-foreground": palette.onSurface, | |
| 49 | "--color-foreground-secondary": palette.onSurfaceVariant, | |
| 50 | "--color-foreground-muted": palette.onSurfaceVariant, | |
| 51 | "--color-foreground-tertiary": withAlpha(palette.onSurfaceVariant, 0.58), | |
| 52 | "--color-border": withAlpha(palette.outlineVariant, dark ? 0.5 : 0.65), | |
| 53 | "--color-border-subtle": withAlpha(palette.outlineVariant, dark ? 0.34 : 0.46), | |
| 54 | "--color-separator": withAlpha(palette.outlineVariant, dark ? 0.24 : 0.34), | |
| 55 | "--color-subtle": withAlpha(palette.onSurface, 0.05), | |
| 56 | "--color-subtle-strong": withAlpha(palette.onSurface, 0.1), | |
| 57 | "--color-inline-skill-background": withAlpha(palette.tertiaryContainer, 0.52), | |
| 58 | "--color-inline-skill-border": withAlpha(palette.tertiary, 0.38), | |
| 59 | "--color-inline-skill-foreground": palette.onTertiaryContainer, | |
| 60 | "--color-primary": palette.primary, | |
| 61 | "--color-primary-foreground": palette.onPrimary, | |
| 62 | "--color-primary-shadow": withAlpha(palette.scrim, dark ? 0.22 : 0.18), | |
| 63 | "--color-secondary": palette.secondaryContainer, | |
| 64 | "--color-secondary-foreground": palette.onSecondaryContainer, | |
| 65 | "--color-secondary-border": withAlpha(palette.outlineVariant, dark ? 0.42 : 0.56), | |
| 66 | "--color-switch-active-track": palette.primary, | |
| 67 | "--color-switch-active-thumb": palette.onPrimary, | |
| 68 | "--color-switch-inactive-track": palette.surfaceContainerHighest, | |
| 69 | "--color-switch-inactive-thumb": palette.outline, | |
| 70 | "--color-danger": palette.errorContainer, | |
| 71 | "--color-danger-border": withAlpha(palette.onErrorContainer, dark ? 0.42 : 0.5), | |
| 72 | "--color-danger-foreground": palette.onErrorContainer, | |
| 73 | "--color-input": palette.surfaceContainerLowest, | |
| 74 | "--color-input-border": withAlpha(palette.outline, dark ? 0.42 : 0.5), | |
| 75 | "--color-sidebar-search": palette.surfaceContainerLowest, | |
| 76 | "--color-placeholder": withAlpha(palette.onSurface, 0.62), | |
| 77 | "--color-icon": palette.onSurface, | |
| 78 | "--color-icon-muted": palette.onSurfaceVariant, | |
| 79 | "--color-icon-subtle": withAlpha(palette.onSurfaceVariant, 0.68), | |
| 80 | "--color-header": palette.surfaceContainerHigh, | |
| 81 | "--color-header-border": withAlpha(palette.outlineVariant, dark ? 0.36 : 0.48), | |
| 82 | "--color-glass-surface": withAlpha(palette.surfaceContainerHigh, dark ? 0.78 : 0.72), | |
| 83 | "--color-glass-tint": withAlpha(palette.primaryContainer, dark ? 0.24 : 0.18), | |
| 84 | "--color-status-bar": palette.surfaceContainerLow, | |
| 85 | "--color-md-body": materialTone(palette.onSurfaceVariant, dark ? 90 : 10), | |
| 86 | "--color-md-strong": materialTone(palette.onSurfaceVariant, dark ? 90 : 10), | |
| 87 | "--color-md-link": palette.primary, | |
| 88 | "--color-md-blockquote-border": withAlpha(palette.primary, dark ? 0.34 : 0.26), | |
| 89 | "--color-md-blockquote-bg": withAlpha(palette.primaryContainer, dark ? 0.14 : 0.2), | |
| 90 | "--color-md-code-bg": withAlpha(palette.onSurface, dark ? 0.08 : 0.06), | |
| 91 | "--color-md-code-text": palette.onSurface, | |
| 92 | "--color-md-user-code-bg": withAlpha(userBubbleForeground, dark ? 0.18 : 0.22), | |
| 93 | "--color-md-user-code-text": userBubbleForeground, | |
| 94 | "--color-md-user-fence-bg": withAlpha(userBubbleForeground, dark ? 0.14 : 0.18), | |
| 95 | "--color-md-user-fence-text": userBubbleForeground, | |
| 96 | "--color-md-hr": withAlpha(palette.outlineVariant, dark ? 0.48 : 0.6), | |
| 97 | "--color-user-bubble": userBubble, | |
| 98 | "--color-user-bubble-foreground": userBubbleForeground, | |
| 99 | "--color-user-bubble-foreground-muted": withAlpha(userBubbleForeground, 0.78), | |
| 100 | "--color-user-bubble-skill-foreground": userBubbleForeground, | |
| 101 | "--color-backdrop": withAlpha(palette.scrim, dark ? 0.48 : 0.22), | |
| 102 | "--color-drawer": palette.surface, | |
| 103 | "--color-drawer-shadow": withAlpha(palette.scrim, dark ? 0.32 : 0.12), | |
| 104 | "--color-dot-separator": withAlpha(palette.onSurface, 0.2), | |
| 105 | "--color-wordmark": palette.onSurface, | |
| 106 | "--color-chevron": withAlpha(palette.onSurface, 0.2), | |
| 107 | }; | |
| 9 | return base; | |
| 108 | 10 | } |