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 };
111
112 const player = {
113 1: buildPlayer(rampColor(ramps.blue, 0.5)),
114 2: buildPlayer(rampColor(ramps.green, 0.5)),
115 3: buildPlayer(rampColor(ramps.magenta, 0.5)),
116 4: buildPlayer(rampColor(ramps.orange, 0.5)),
117 5: buildPlayer(rampColor(ramps.violet, 0.5)),
118 6: buildPlayer(rampColor(ramps.cyan, 0.5)),
119 7: buildPlayer(rampColor(ramps.red, 0.5)),
120 8: buildPlayer(rampColor(ramps.yellow, 0.5)),
121 };
122
123 const editor = {
124 background: backgroundColor[500].base,
125 indent_guide: borderColor.muted,
126 indent_guide_active: borderColor.secondary,
127 line: {
128 active: rampColor(ramps.neutral, 1),
129 highlighted: rampColor(ramps.neutral, 1.25), // TODO: Where is this used?
130 },
131 highlight: {
132 selection: player[1].selectionColor,
133 occurrence: withOpacity(rampColor(ramps.neutral, 2), blend),
134 activeOccurrence: withOpacity(rampColor(ramps.neutral, 2), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
135 matchingBracket: backgroundColor[500].active, // TODO: Not hooked up
136 match: rampColor(ramps.violet, 0.15),
137 activeMatch: withOpacity(rampColor(ramps.violet, 0.4), blend * 2), // TODO: Not hooked up - https://github.com/zed-industries/zed/issues/751
138 related: backgroundColor[500].hovered,
139 },
140 gutter: {
141 primary: textColor.placeholder,
142 active: textColor.active,
143 },
144 };
145
146 const syntax: Syntax = {
147 primary: {
148 color: rampColor(ramps.neutral, 7),
149 weight: fontWeights.normal,
150 },
151 comment: {
152 color: rampColor(ramps.neutral, 5),
153 weight: fontWeights.normal,
154 },
155 punctuation: {
156 color: rampColor(ramps.neutral, 6),
157 weight: fontWeights.normal,
158 },
159 constant: {
160 color: rampColor(ramps.neutral, 4),
161 weight: fontWeights.normal,
162 },
163 keyword: {
164 color: rampColor(ramps.blue, 0.5),
165 weight: fontWeights.normal,
166 },
167 function: {
168 color: rampColor(ramps.yellow, 0.5),
169 weight: fontWeights.normal,
170 },
171 type: {
172 color: rampColor(ramps.cyan, 0.5),
173 weight: fontWeights.normal,
174 },
175 variant: {
176 color: rampColor(ramps.blue, 0.5),
177 weight: fontWeights.normal,
178 },
179 property: {
180 color: rampColor(ramps.blue, 0.5),
181 weight: fontWeights.normal,
182 },
183 enum: {
184 color: rampColor(ramps.orange, 0.5),
185 weight: fontWeights.normal,
186 },
187 operator: {
188 color: rampColor(ramps.orange, 0.5),
189 weight: fontWeights.normal,
190 },
191 string: {
192 color: rampColor(ramps.orange, 0.5),
193 weight: fontWeights.normal,
194 },
195 number: {
196 color: rampColor(ramps.green, 0.5),
197 weight: fontWeights.normal,
198 },
199 boolean: {
200 color: rampColor(ramps.green, 0.5),
201 weight: fontWeights.normal,
202 },
203 predictive: {
204 color: textColor.muted,
205 weight: fontWeights.normal,
206 },
207 title: {
208 color: rampColor(ramps.yellow, 0.5),
209 weight: fontWeights.bold,
210 },
211 emphasis: {
212 color: textColor.feature,
213 weight: fontWeights.normal,
214 },
215 "emphasis.strong": {
216 color: textColor.feature,
217 weight: fontWeights.bold,
218 },
219 linkUri: {
220 color: rampColor(ramps.green, 0.5),
221 weight: fontWeights.normal,
222 underline: true,
223 },
224 linkText: {
225 color: rampColor(ramps.orange, 0.5),
226 weight: fontWeights.normal,
227 italic: true,
228 },
229 };
230
231 const shadowAlpha: NumberToken = {
232 value: blend,
233 type: "number",
234 };
235
236 return {
237 name,
238 backgroundColor,
239 borderColor,
240 textColor,
241 iconColor: textColor,
242 editor,
243 syntax,
244 player,
245 shadowAlpha,
246 };
247}