base16.ts

  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}