-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy paththeme-color.mjs
144 lines (123 loc) · 4.66 KB
/
theme-color.mjs
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import hextohsl from './hex-to-hsl.mjs'
export default function themeColor({ config }) {
const { color = {}, theme = {} } = config
if (theme === false) {
return ''
}
const defaultAccent = '#0075db'
const defaultError = '#d60606'
const defaultLight = '#fefefe'
const defaultDark = '#222222'
const light = color.light || theme.back || defaultLight
const dark = color.dark || theme.fore || defaultDark
const lightParts = hextohsl(light)
const darkTheme = theme?.dark || {}
const lightAccent = theme?.accent || defaultAccent
const lightAccentParts = hextohsl(lightAccent)
const darkAccent = theme?.dark?.accent || theme?.accent || defaultAccent
const darkAccentParts = hextohsl(darkAccent)
// If no custom dark accent colour provided, modify default accent's lightness for dark mode
if (darkAccent === lightAccent || darkAccent === defaultAccent) {
darkAccentParts.l = 62
}
const lightError = theme?.error || defaultError
const lightErrorParts = hextohsl(lightError)
const darkError = theme?.dark?.error || theme?.error || defaultError
const darkErrorParts = hextohsl(darkError)
// If no custom dark error colour provided, modify default error's lightness for dark mode
if (darkError === lightError || darkError === defaultError) {
darkErrorParts.l = 62
}
const darkThemeColors = Object.keys(darkTheme).map(name => {
return `--${name}: ${darkTheme[name]};`
}).join('\n')
const themeColors = Object.keys(theme).map(name => {
if (name === 'accent' ||
name === 'error' ||
name === 'back' ||
name === 'fore' ||
(name === 'dark' && typeof theme[name] === 'object')) {
return
}
else {
return colorSteps(hextohsl(theme[name]), name)
}
}).join('\n')
const colors = Object.keys(color).length ? Object.keys(color).map(name => `--${name}: ${color[name]};`).join('\n ') : ''
const grayScale = colorSteps({ h: lightParts.h, s: 0, l: 50 }, 'gray')
function colorSteps(color, name) {
const hue = color.h
const saturation = color.s
const luminance = color.l
return `
--${name}-100: hsl(${hue}, ${saturation}%, ${Math.floor(luminance + 40)}%);
--${name}-200: hsl(${hue}, ${saturation}%, ${Math.floor(luminance + 30)}%);
--${name}-300: hsl(${hue}, ${saturation}%, ${Math.floor(luminance + 20)}%);
--${name}-400: hsl(${hue}, ${saturation}%, ${Math.floor(luminance + 10)}%);
--${name}-500: hsl(${hue}, ${saturation}%, ${luminance}%);
--${name}-600: hsl(${hue}, ${saturation}%, ${Math.floor(luminance - 10)}%);
--${name}-700: hsl(${hue}, ${saturation}%, ${Math.floor(luminance - 20)}%);
--${name}-800: hsl(${hue}, ${saturation}%, ${Math.floor(luminance - 30)}%);
--${name}-900: hsl(${hue}, ${saturation}%, ${Math.floor(luminance - 40)}%);`
}
const themeStyles = `
/*** Theme Colors ***/
:root {
--accent-h: ${lightAccentParts.h};
--accent-s: ${lightAccentParts.s}%;
--accent-l: ${lightAccentParts.l}%;
--accent: hsl(var(--accent-h), var(--accent-s), var(--accent-l));
--light: ${light};
--dark: ${dark};
--fore: var(--dark, currentColor);
--back: var(--light);
--error-h: ${lightErrorParts.h};
--error-s: ${lightErrorParts.s}%;
--error-l: ${lightErrorParts.l}%;
--error: hsl(var(--error-h), var(--error-s), var(--error-l));
${themeColors}
${grayScale}
--focus-l: 30%;
accent-color: var(--accent, royalblue);
color-scheme: light dark;
}
:is(a, button, input, textarea, summary):focus:not(:focus-visible) {
outline: none;
}
:is(a, button, input, textarea, summary):focus-visible {
outline: max(var(--focus-size, 1px), 1px) solid var(--accent, royalblue);
outline-offset: var(--focus-offset, 0);
box-shadow: 0 0 0 max(var(--focus-size, 3px), 3px) hsl(var(--accent-h), var(--accent-s), calc(var(--accent-l) + var(--focus-l)))
;
}
:is(a, button, input, textarea, summary):not(:focus):not(:placeholder-shown):invalid {
outline: max(var(--focus-size, 1px), 1px) solid var(--error, crimson);
outline-offset: var(--focus-offset, 0);
box-shadow: 0 0 0 3px hsl(var(--error-h), var(--error-s), calc(var(--error-l) + var(--focus-l)));
}
@media (prefers-color-scheme: dark) {
:root {
--accent-h: ${darkAccentParts.h};
--accent-s: ${darkAccentParts.s}%;
--accent-l: ${darkAccentParts.l}%;
--error-h: ${darkErrorParts.h};
--error-s: ${darkErrorParts.s}%;
--error-l: ${darkErrorParts.l}%;
--focus-l: -30%;
--fore: var(--light);
--back: var(--dark);
${darkThemeColors}
}
}
`.replace(/^\s*\n/gm, '') // remove empty newlines
const colorStyles = `
/*** Spot Colors ***/
:root {
${colors}
}
`
let result = ``
if (theme !== false) result += themeStyles
if (Object.keys(color).length) result += colorStyles
return result
}