1import chroma, { Color, Scale } from "chroma-js";
2import { color, ColorToken, fontWeights, NumberToken } from "../../tokens";
3import { withOpacity } from "../../utils/color";
4import Theme, { buildPlayer, Syntax } from "./theme";
5
6export function colorRamp(color: Color): Scale {
7 let hue = color.hsl()[0];
8 let endColor = chroma.hsl(hue, 0.88, 0.96);
9 let startColor = chroma.hsl(hue, 0.68, 0.12);
10 return chroma.scale([startColor, color, endColor]).mode("hsl");
11}
12
13export function createTheme(
14 name: string,
15 isLight: boolean,
16 ramps: { [rampName: string]: Scale },
17 blend?: number
18): Theme {
19 if (isLight) {
20 for (var rampName in ramps) {
21 ramps[rampName] = ramps[rampName].domain([1, 0]);
22 }
23 ramps.neutral = ramps.neutral.domain([7, 0]);
24 } else {
25 ramps.neutral = ramps.neutral.domain([0, 7]);
26 }
27
28 if (blend === undefined) {
29 blend = isLight ? 0.12 : 0.24;
30 }
31
32 function rampColor(ramp: Scale, index: number): ColorToken {
33 return color(ramp(index).hex());
34 }
35
36 const backgroundColor = {
37 // Title bar
38 100: {
39 base: rampColor(ramps.neutral, 1.25),
40 hovered: rampColor(ramps.neutral, 1.5),
41 active: rampColor(ramps.neutral, 1.75),
42 },
43 // Midground (panels, etc)
44 300: {
45 base: rampColor(ramps.neutral, 1),
46 hovered: rampColor(ramps.neutral, 1.25),
47 active: rampColor(ramps.neutral, 1.5),
48 },
49 // Editor
50 500: {
51 base: rampColor(ramps.neutral, 0),
52 hovered: rampColor(ramps.neutral, 0.25),
53 active: rampColor(ramps.neutral, 0.5),
54 },
55 on300: {
56 base: rampColor(ramps.neutral, 0),
57 hovered: rampColor(ramps.neutral, 0.25),
58 active: rampColor(ramps.neutral, 0.5),
59 },
60 on500: {
61 base: rampColor(ramps.neutral, 1.25),
62 hovered: rampColor(ramps.neutral, 1.5),
63 active: rampColor(ramps.neutral, 1.75),
64 },
65 ok: {
66 base: withOpacity(rampColor(ramps.green, 0.5), 0.15),
67 hovered: withOpacity(rampColor(ramps.green, 0.5), 0.2),
68 active: withOpacity(rampColor(ramps.green, 0.5), 0.25),
69 },
70 error: {
71 base: withOpacity(rampColor(ramps.red, 0.5), 0.15),
72 hovered: withOpacity(rampColor(ramps.red, 0.5), 0.2),
73 active: withOpacity(rampColor(ramps.red, 0.5), 0.25),
74 },
75 warning: {
76 base: withOpacity(rampColor(ramps.yellow, 0.5), 0.15),
77 hovered: withOpacity(rampColor(ramps.yellow, 0.5), 0.2),
78 active: withOpacity(rampColor(ramps.yellow, 0.5), 0.25),
79 },
80 info: {
81 base: withOpacity(rampColor(ramps.blue, 0.5), 0.15),
82 hovered: withOpacity(rampColor(ramps.blue, 0.5), 0.2),
83 active: withOpacity(rampColor(ramps.blue, 0.5), 0.25),
84 },
85 };
86
87 const borderColor = {
88 primary: rampColor(ramps.neutral, isLight ? 1.5 : 0),
89 secondary: rampColor(ramps.neutral, isLight ? 1.25 : 1),
90 muted: rampColor(ramps.neutral, isLight ? 1 : 3),
91 active: rampColor(ramps.neutral, isLight ? 4 : 3),
92 onMedia: withOpacity(rampColor(ramps.neutral, 0), 0.1),
93 ok: withOpacity(rampColor(ramps.green, 0.5), 0.15),
94 error: withOpacity(rampColor(ramps.red, 0.5), 0.15),
95 warning: withOpacity(rampColor(ramps.yellow, 0.5), 0.15),
96 info: withOpacity(rampColor(ramps.blue, 0.5), 0.15),
97 };
98
99 const textColor = {
100 primary: rampColor(ramps.neutral, 6),
101 secondary: rampColor(ramps.neutral, 5),
102 muted: rampColor(ramps.neutral, 5),
103 placeholder: rampColor(ramps.neutral, 4),
104 active: rampColor(ramps.neutral, 7),
105 feature: rampColor(ramps.blue, 0.5),
106 ok: rampColor(ramps.green, 0.5),
107 error: rampColor(ramps.red, 0.5),
108 warning: rampColor(ramps.yellow, 0.5),
109 info: rampColor(ramps.blue, 0.5),
110 onMedia: rampColor(ramps.neutral, isLight ? 0 : 7),
111 };
112
113 const player = {
114 1: buildPlayer(rampColor(ramps.blue, 0.5)),
115 2: buildPlayer(rampColor(ramps.green, 0.5)),
116 3: buildPlayer(rampColor(ramps.magenta, 0.5)),
117 4: buildPlayer(rampColor(ramps.orange, 0.5)),
118 5: buildPlayer(rampColor(ramps.violet, 0.5)),
119 6: buildPlayer(rampColor(ramps.cyan, 0.5)),
120 7: buildPlayer(rampColor(ramps.red, 0.5)),
121 8: buildPlayer(rampColor(ramps.yellow, 0.5)),
122 };
123
124 const editor = {
125 background: backgroundColor[500].base,
126 indent_guide: borderColor.muted,
127 indent_guide_active: borderColor.secondary,
128 line: {
129 active: rampColor(ramps.neutral, 1),
130 highlighted: rampColor(ramps.neutral, 1.25), // TODO: Where is this used?
131 },
132 highlight: {
133 selection: player[1].selectionColor,
134 occurrence: withOpacity(rampColor(ramps.neutral, 3.5), blend),
135 activeOccurrence: withOpacity(rampColor(ramps.neutral, 3.5), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
136 matchingBracket: backgroundColor[500].active, // TODO: Not hooked up
137 match: rampColor(ramps.violet, 0.15),
138 activeMatch: withOpacity(rampColor(ramps.violet, 0.4), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
139 related: backgroundColor[500].hovered,
140 },
141 gutter: {
142 primary: textColor.placeholder,
143 active: textColor.active,
144 },
145 };
146
147 const syntax: Syntax = {
148 primary: {
149 color: rampColor(ramps.neutral, 7),
150 weight: fontWeights.normal,
151 },
152 comment: {
153 color: rampColor(ramps.neutral, 5),
154 weight: fontWeights.normal,
155 },
156 punctuation: {
157 color: rampColor(ramps.neutral, 6),
158 weight: fontWeights.normal,
159 },
160 constant: {
161 color: rampColor(ramps.neutral, 4),
162 weight: fontWeights.normal,
163 },
164 keyword: {
165 color: rampColor(ramps.blue, 0.5),
166 weight: fontWeights.normal,
167 },
168 function: {
169 color: rampColor(ramps.yellow, 0.5),
170 weight: fontWeights.normal,
171 },
172 type: {
173 color: rampColor(ramps.cyan, 0.5),
174 weight: fontWeights.normal,
175 },
176 variant: {
177 color: rampColor(ramps.blue, 0.5),
178 weight: fontWeights.normal,
179 },
180 property: {
181 color: rampColor(ramps.blue, 0.5),
182 weight: fontWeights.normal,
183 },
184 enum: {
185 color: rampColor(ramps.orange, 0.5),
186 weight: fontWeights.normal,
187 },
188 operator: {
189 color: rampColor(ramps.orange, 0.5),
190 weight: fontWeights.normal,
191 },
192 string: {
193 color: rampColor(ramps.orange, 0.5),
194 weight: fontWeights.normal,
195 },
196 number: {
197 color: rampColor(ramps.green, 0.5),
198 weight: fontWeights.normal,
199 },
200 boolean: {
201 color: rampColor(ramps.green, 0.5),
202 weight: fontWeights.normal,
203 },
204 predictive: {
205 color: textColor.muted,
206 weight: fontWeights.normal,
207 },
208 title: {
209 color: rampColor(ramps.yellow, 0.5),
210 weight: fontWeights.bold,
211 },
212 emphasis: {
213 color: textColor.feature,
214 weight: fontWeights.normal,
215 },
216 "emphasis.strong": {
217 color: textColor.feature,
218 weight: fontWeights.bold,
219 },
220 linkUri: {
221 color: rampColor(ramps.green, 0.5),
222 weight: fontWeights.normal,
223 underline: true,
224 },
225 linkText: {
226 color: rampColor(ramps.orange, 0.5),
227 weight: fontWeights.normal,
228 italic: true,
229 },
230 };
231
232 const shadowAlpha: NumberToken = {
233 value: blend,
234 type: "number",
235 };
236
237 return {
238 name,
239 backgroundColor,
240 borderColor,
241 textColor,
242 iconColor: textColor,
243 editor,
244 syntax,
245 player,
246 shadowAlpha,
247 };
248}