mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 16:05:39 +00:00
177 lines
5.1 KiB
TypeScript
177 lines
5.1 KiB
TypeScript
// Home-screen widget palettes. These copy the webmail's standard theme as it
|
|
// renders (app/globals.css plus the Tailwind classes the list, calendar and
|
|
// sidebar use), not the in-app ThemePalette: in the webmail's dark mode the
|
|
// primary colour is white, while the native dark palette keeps a blue primary.
|
|
// Widgets are meant to look like the webmail, so they carry their own tokens.
|
|
|
|
import type { ColorProp } from 'react-native-android-widget';
|
|
|
|
export type WidgetScheme = 'light' | 'dark';
|
|
|
|
export interface WidgetPalette {
|
|
scheme: WidgetScheme;
|
|
/**
|
|
* Widget surface (webmail `--color-background`). The edge around it is drawn
|
|
* natively, as `widget_frame` in android/app/src/main/res/values/colors.xml.
|
|
*/
|
|
bg: ColorProp;
|
|
fg: ColorProp;
|
|
muted: ColorProp;
|
|
/** Search pill, segmented track, quota ring track (webmail `--color-muted`). */
|
|
mutedBg: ColorProp;
|
|
border: ColorProp;
|
|
primary: ColorProp;
|
|
primaryFg: ColorProp;
|
|
accent: ColorProp;
|
|
accentFg: ColorProp;
|
|
/** `bg-accent/30`, the tint of an unread message row. */
|
|
unreadRow: ColorProp;
|
|
unreadDot: ColorProp;
|
|
/** Agenda day-header band. */
|
|
dayHeader: ColorProp;
|
|
/** "Today" label in the agenda. */
|
|
today: ColorProp;
|
|
/** Read subject (`text-foreground/90`). */
|
|
subjectRead: ColorProp;
|
|
success: ColorProp;
|
|
successText: ColorProp;
|
|
successBg: ColorProp;
|
|
info: ColorProp;
|
|
infoBg: ColorProp;
|
|
destructive: ColorProp;
|
|
destructiveBg: ColorProp;
|
|
overdue: ColorProp;
|
|
dueToday: ColorProp;
|
|
star: ColorProp;
|
|
/** Outline button edge (webmail `--color-input`). */
|
|
input: ColorProp;
|
|
/** Scheduled-send label (`sky-500/10` pill with `sky-700/300` text). */
|
|
scheduledBg: ColorProp;
|
|
scheduledText: ColorProp;
|
|
warningBg: ColorProp;
|
|
warningText: ColorProp;
|
|
folder: {
|
|
inbox: ColorProp;
|
|
drafts: ColorProp;
|
|
sent: ColorProp;
|
|
scheduled: ColorProp;
|
|
archive: ColorProp;
|
|
junk: ColorProp;
|
|
};
|
|
}
|
|
|
|
export const LIGHT: WidgetPalette = {
|
|
scheme: 'light',
|
|
bg: '#ffffff',
|
|
fg: '#0f172a',
|
|
muted: '#64748b',
|
|
mutedBg: '#f1f5f9',
|
|
border: '#e2e8f0',
|
|
primary: '#3b82f6',
|
|
primaryFg: '#ffffff',
|
|
accent: '#dbeafe',
|
|
accentFg: '#1e40af',
|
|
unreadRow: '#f4f9ff',
|
|
unreadDot: '#3b82f6',
|
|
dayHeader: '#f4f7fa',
|
|
today: '#3b82f6',
|
|
subjectRead: '#283548',
|
|
success: '#22c55e',
|
|
successText: '#15803d',
|
|
successBg: 'rgba(34, 197, 94, 0.15)',
|
|
info: '#2563eb',
|
|
infoBg: 'rgba(59, 130, 246, 0.15)',
|
|
destructive: '#ef4444',
|
|
destructiveBg: 'rgba(239, 68, 68, 0.15)',
|
|
overdue: '#e7000b',
|
|
dueToday: '#155dfc',
|
|
star: '#fbbf24',
|
|
input: '#e2e8f0',
|
|
scheduledBg: 'rgba(0, 166, 244, 0.1)',
|
|
scheduledText: '#0069a8',
|
|
warningBg: 'rgba(254, 154, 0, 0.12)',
|
|
warningText: '#973c00',
|
|
folder: {
|
|
inbox: '#155dfccc',
|
|
drafts: '#7f22fecc',
|
|
sent: '#009966cc',
|
|
scheduled: '#0084d1',
|
|
archive: '#e17100cc',
|
|
junk: '#e7000bcc',
|
|
},
|
|
};
|
|
|
|
export const DARK: WidgetPalette = {
|
|
scheme: 'dark',
|
|
bg: '#0a0a0a',
|
|
fg: '#fafafa',
|
|
muted: '#a3a3a3',
|
|
mutedBg: '#262626',
|
|
border: '#2d2d2d',
|
|
primary: '#fafafa',
|
|
primaryFg: '#171717',
|
|
accent: '#1e3a8a',
|
|
accentFg: '#dbeafe',
|
|
unreadRow: '#0f1830',
|
|
unreadDot: '#60a5fa',
|
|
dayHeader: '#202020',
|
|
today: '#fafafa',
|
|
subjectRead: '#e1e1e1',
|
|
success: '#16a34a',
|
|
successText: '#22c55e',
|
|
successBg: 'rgba(22, 163, 74, 0.15)',
|
|
info: '#60a5fa',
|
|
infoBg: 'rgba(96, 165, 250, 0.15)',
|
|
destructive: '#ef4444',
|
|
destructiveBg: 'rgba(239, 68, 68, 0.15)',
|
|
overdue: '#ff6467',
|
|
dueToday: '#51a2ff',
|
|
star: '#fbbf24',
|
|
input: '#262626',
|
|
scheduledBg: 'rgba(0, 166, 244, 0.1)',
|
|
scheduledText: '#74d4ff',
|
|
warningBg: 'rgba(254, 154, 0, 0.12)',
|
|
warningText: '#ffd230',
|
|
folder: {
|
|
inbox: '#51a2ffcc',
|
|
drafts: '#a684ffcc',
|
|
sent: '#00d492cc',
|
|
scheduled: '#00bcff',
|
|
archive: '#ffb900cc',
|
|
junk: '#ff6467cc',
|
|
},
|
|
};
|
|
|
|
/** Tint a calendar colour the way event chips do (`${color}24`, ~14% alpha). */
|
|
export function chipBackground(color: string): ColorProp {
|
|
const hex = normalizeHex(color);
|
|
return `${hex}24` as ColorProp;
|
|
}
|
|
|
|
/** Accepts `#rgb`, `#rrggbb`, `hsl(h, s%, l%)`; anything else falls back to blue. */
|
|
export function normalizeHex(color: string | null | undefined): `#${string}` {
|
|
if (!color) return '#3b82f6';
|
|
const c = color.trim();
|
|
if (/^#[0-9a-f]{6}$/i.test(c)) return c.toLowerCase() as `#${string}`;
|
|
if (/^#[0-9a-f]{3}$/i.test(c)) {
|
|
const [r, g, b] = c.slice(1).split('');
|
|
return `#${r}${r}${g}${g}${b}${b}`.toLowerCase() as `#${string}`;
|
|
}
|
|
if (/^#[0-9a-f]{8}$/i.test(c)) return c.slice(0, 7).toLowerCase() as `#${string}`;
|
|
const hsl = /^hsla?\(\s*([\d.]+)\s*,\s*([\d.]+)%\s*,\s*([\d.]+)%/i.exec(c);
|
|
if (hsl) return hslToHex(Number(hsl[1]), Number(hsl[2]), Number(hsl[3]));
|
|
return '#3b82f6';
|
|
}
|
|
|
|
export function hslToHex(h: number, s: number, l: number): `#${string}` {
|
|
const sat = s / 100;
|
|
const light = l / 100;
|
|
const k = (n: number) => (n + h / 30) % 12;
|
|
const a = sat * Math.min(light, 1 - light);
|
|
const f = (n: number) => light - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1)));
|
|
const hex = [f(0), f(8), f(4)]
|
|
.map((x) => Math.round(x * 255).toString(16).padStart(2, '0'))
|
|
.join('');
|
|
return `#${hex}`;
|
|
}
|