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  };
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}