base16.ts

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