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