1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
import Color from "color";
import { BehaviorSubject, Observable } from "rxjs";
function lightenBy(color: Color, ratio: number): Color {
const lightness = color.lightness();
return color.lightness(lightness + (100 - lightness) * ratio);
}
function darkenBy(color: Color, ratio: number): Color {
const lightness = color.lightness();
return color.lightness(lightness - lightness * ratio);
}
export interface PaletteColor {
color: string;
inactive: string;
lighter: string;
darker: string;
[key: string]: string;
}
export interface Palette {
primary: PaletteColor;
primaryEnhance: PaletteColor;
secondary: PaletteColor;
textPrimary: PaletteColor;
textOnPrimary: PaletteColor;
danger: PaletteColor;
success: PaletteColor;
[key: string]: PaletteColor;
}
export type PaletteColorType = keyof Palette;
export function generatePaletteColor(color: string): PaletteColor {
const c = Color(color);
return {
color: c.toString(),
inactive: (c.lightness() > 60
? darkenBy(c, 0.1)
: lightenBy(c, 0.2)
).toString(),
lighter: lightenBy(c, 0.1).fade(0.1).toString(),
darker: darkenBy(c, 0.1).toString(),
};
}
export function generatePalette(options: {
primary: string;
primaryEnhance?: string;
secondary?: string;
}): Palette {
const { primary, primaryEnhance, secondary } = options;
const p = Color(primary);
const pe =
primaryEnhance == null
? lightenBy(p, 0.3).saturate(0.3)
: Color(primaryEnhance);
const s = secondary == null ? p.rotate(90) : Color(secondary);
return {
primary: generatePaletteColor(p.toString()),
primaryEnhance: generatePaletteColor(pe.toString()),
secondary: generatePaletteColor(s.toString()),
textPrimary: generatePaletteColor("#111111"),
textOnPrimary: generatePaletteColor(p.lightness() > 60 ? "black" : "white"),
danger: generatePaletteColor("red"),
success: generatePaletteColor("green"),
};
}
export function generatePaletteCSS(palette: Palette): string {
function toSnakeCase(s: string): string {
return s.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
}
const colors: [string, string][] = [];
for (const paletteColorName in palette) {
const paletteColor = palette[paletteColorName];
for (const variant in paletteColor) {
let key = `--tl-${toSnakeCase(paletteColorName)}`;
if (variant !== "color") key += `-${toSnakeCase(variant)}`;
key += "-color";
colors.push([key, paletteColor[variant]]);
}
}
return `:root {${colors
.map(([key, color]) => `${key} : ${color};`)
.join("")}}`;
}
const paletteSubject: BehaviorSubject<Palette> = new BehaviorSubject<Palette>(
generatePalette({ primary: "#007bff" })
);
export const palette$: Observable<Palette> = paletteSubject.asObservable();
palette$.subscribe((palette) => {
const styleTagId = "timeline-palette-css";
let styleTag = document.getElementById(styleTagId);
if (styleTag == null) {
styleTag = document.createElement("style");
styleTag.id = styleTagId;
document.head.append(styleTag);
}
styleTag.innerHTML = generatePaletteCSS(palette);
});
export function setPalette(palette: Palette): () => void {
const old = paletteSubject.value;
paletteSubject.next(palette);
return () => {
paletteSubject.next(old);
};
}
|