feat(appearance): add display preference models
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import type { ThemeScheme } from "./themes"
|
||||
|
||||
/**
|
||||
* Color-vision assistance modes.
|
||||
*
|
||||
* These modes select palettes that are easier to distinguish. They are not
|
||||
* simulations of how another person sees the interface and intentionally do
|
||||
* not apply a filter to images or the complete document.
|
||||
*/
|
||||
export type ColorVisionMode = "standard" | "protan" | "deutan" | "tritan"
|
||||
|
||||
export const colorVisionModes = [
|
||||
"standard",
|
||||
"protan",
|
||||
"deutan",
|
||||
"tritan",
|
||||
] as const satisfies readonly ColorVisionMode[]
|
||||
|
||||
export const COLOR_VISION_ATTRIBUTE = "data-ui-color-vision"
|
||||
|
||||
type ColorVisionVariables = Readonly<
|
||||
Record<
|
||||
"destructive" | "chart-1" | "chart-2" | "chart-3" | "chart-4" | "chart-5",
|
||||
string
|
||||
>
|
||||
>
|
||||
|
||||
/**
|
||||
* Palettes for semantic data colors that commonly need to be distinguished.
|
||||
* Components must still pair color with text, icons, shapes, or patterns.
|
||||
*/
|
||||
export const colorVisionPalettes: Readonly<
|
||||
Record<
|
||||
Exclude<ColorVisionMode, "standard">,
|
||||
Record<ThemeScheme, ColorVisionVariables>
|
||||
>
|
||||
> = {
|
||||
protan: {
|
||||
light: {
|
||||
destructive: "#b54a00",
|
||||
"chart-1": "#0072b2",
|
||||
"chart-2": "#e69f00",
|
||||
"chart-3": "#009e73",
|
||||
"chart-4": "#cc79a7",
|
||||
"chart-5": "#6b5ca5",
|
||||
},
|
||||
dark: {
|
||||
destructive: "#ff9d66",
|
||||
"chart-1": "#56b4e9",
|
||||
"chart-2": "#f0e442",
|
||||
"chart-3": "#65d5b3",
|
||||
"chart-4": "#e8a4ce",
|
||||
"chart-5": "#afa4e8",
|
||||
},
|
||||
},
|
||||
deutan: {
|
||||
light: {
|
||||
destructive: "#a64b00",
|
||||
"chart-1": "#005f9e",
|
||||
"chart-2": "#d88c00",
|
||||
"chart-3": "#8a6f00",
|
||||
"chart-4": "#bd63a0",
|
||||
"chart-5": "#168c9e",
|
||||
},
|
||||
dark: {
|
||||
destructive: "#ff9d66",
|
||||
"chart-1": "#65bff0",
|
||||
"chart-2": "#f3c969",
|
||||
"chart-3": "#d7c85d",
|
||||
"chart-4": "#e5a0cf",
|
||||
"chart-5": "#63c7d5",
|
||||
},
|
||||
},
|
||||
tritan: {
|
||||
light: {
|
||||
destructive: "#b83261",
|
||||
"chart-1": "#b83280",
|
||||
"chart-2": "#008f72",
|
||||
"chart-3": "#d55e00",
|
||||
"chart-4": "#6b5ca5",
|
||||
"chart-5": "#53606d",
|
||||
},
|
||||
dark: {
|
||||
destructive: "#f27ca5",
|
||||
"chart-1": "#ed8bc9",
|
||||
"chart-2": "#62d1b3",
|
||||
"chart-3": "#ff9d66",
|
||||
"chart-4": "#afa4e8",
|
||||
"chart-5": "#b8c2cc",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export function isColorVisionMode(value: unknown): value is ColorVisionMode {
|
||||
return colorVisionModes.some((mode) => mode === value)
|
||||
}
|
||||
|
||||
export function getColorVisionCssVariables(
|
||||
mode: ColorVisionMode,
|
||||
scheme: ThemeScheme
|
||||
): ColorVisionVariables | undefined {
|
||||
return mode === "standard" ? undefined : colorVisionPalettes[mode][scheme]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { ThemeScheme } from "./themes"
|
||||
|
||||
export type Brightness = "dim" | "normal" | "bright"
|
||||
export type MotionMode = "system" | "reduce" | "full"
|
||||
export type ResolvedMotionMode = Exclude<MotionMode, "system">
|
||||
export type TextScale = "100" | "125" | "150" | "200"
|
||||
|
||||
export const brightnessModes = [
|
||||
"dim",
|
||||
"normal",
|
||||
"bright",
|
||||
] as const satisfies readonly Brightness[]
|
||||
export const motionModes = [
|
||||
"system",
|
||||
"reduce",
|
||||
"full",
|
||||
] as const satisfies readonly MotionMode[]
|
||||
export const textScales = [
|
||||
"100",
|
||||
"125",
|
||||
"150",
|
||||
"200",
|
||||
] as const satisfies readonly TextScale[]
|
||||
|
||||
export const BRIGHTNESS_ATTRIBUTE = "data-ui-brightness"
|
||||
export const MOTION_ATTRIBUTE = "data-ui-motion"
|
||||
export const TEXT_SCALE_ATTRIBUTE = "data-ui-text-scale"
|
||||
|
||||
const surfaceVariableNames = [
|
||||
"background",
|
||||
"card",
|
||||
"popover",
|
||||
"secondary",
|
||||
"muted",
|
||||
"highlight",
|
||||
"sidebar",
|
||||
] as const
|
||||
|
||||
export function isBrightness(value: unknown): value is Brightness {
|
||||
return brightnessModes.some((mode) => mode === value)
|
||||
}
|
||||
|
||||
export function isMotionMode(value: unknown): value is MotionMode {
|
||||
return motionModes.some((mode) => mode === value)
|
||||
}
|
||||
|
||||
export function isTextScale(value: unknown): value is TextScale {
|
||||
return textScales.some((scale) => scale === value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts interface surfaces without filtering media, illustrations, or brand
|
||||
* colors. Foreground colors are preserved so dimming does not reduce contrast.
|
||||
*/
|
||||
export function applyBrightnessToTheme(
|
||||
variables: Record<string, string>,
|
||||
brightness: Brightness,
|
||||
scheme: ThemeScheme
|
||||
): Record<string, string> {
|
||||
if (brightness === "normal") return variables
|
||||
|
||||
const target = brightness === "dim" ? "black" : "white"
|
||||
const amount = {
|
||||
bright: scheme === "light" ? 12 : 8,
|
||||
dim: scheme === "light" ? 5 : 12,
|
||||
}[brightness]
|
||||
const adjusted = { ...variables }
|
||||
|
||||
for (const name of surfaceVariableNames) {
|
||||
const value = variables[name]
|
||||
if (value) {
|
||||
adjusted[name] = `color-mix(in oklch,${value},${target} ${amount}%)`
|
||||
}
|
||||
}
|
||||
|
||||
return adjusted
|
||||
}
|
||||
Reference in New Issue
Block a user