Express workspace in terms of new components

Nathan Sobo and Nate Butler created

Co-Authored-By: Nate Butler <1714999+iamnbutler@users.noreply.github.com>

Change summary

styles/app.ts            | 155 ++---------------------------------------
styles/components.ts     |  23 +++++
styles/core.color.ts     |  93 -------------------------
styles/core.ts           |  26 +++++-
styles/lib.ts            |  44 +++++++++++
styles/package-lock.json |  11 ++
styles/package.json      |   1 
styles/selector-modal.ts |   2 
styles/theme.ts          |  46 ++++++++++++
styles/workspace.ts      | 133 ++++++++++++++++++++++++++++++++++++
10 files changed, 288 insertions(+), 246 deletions(-)

Detailed changes

styles/app.ts 🔗

@@ -1,147 +1,12 @@
-import { selectorModal } from "./selector-modal";
+import { backgroundColor } from "./components";
+import selectorModal from "./selector-modal";
+import workspace from "./workspace";
 import Theme from "./theme";
 
 export default function app(theme: Theme): Object {
   return {
     selector: selectorModal(theme),
-    workspace: {
-      background: "$surface.500",
-      leaderBorderOpacity: 0.7,
-      leader_border_width: 2.0,
-      active_tab: {
-        background: "$surface.300",
-        extends: "$workspace.tab",
-        text: "$text.primary",
-        border: {
-          bottom: false,
-        },
-      },
-      left_sidebar: {
-        extends: "$workspace.sidebar",
-        border: {
-          color: "$border.primary",
-          right: true,
-          width: 1,
-        },
-      },
-      pane_divider: {
-        color: "$border.primary",
-        width: 1,
-      },
-      right_sidebar: {
-        extends: "$workspace.sidebar",
-        border: {
-          color: "$border.primary",
-          left: true,
-          width: 1,
-        },
-      },
-      sidebar: {
-        width: 30,
-        active_item: {
-          extends: "$workspace.sidebar.item",
-          icon_color: "$text.primary.color",
-        },
-        border: {
-          color: "$border.primary",
-          right: true,
-          width: 1,
-        },
-        item: {
-          height: "$workspace.tab.height",
-          icon_color: "$text.muted.color",
-          icon_size: 18,
-        },
-        resize_handle: {
-          background: "$border.primary",
-          padding: {
-            left: 1,
-          },
-        },
-      },
-      status_bar: {
-        cursor_position: "$text.muted",
-        diagnostic_message: "$text.muted",
-        height: 24,
-        item_spacing: 8,
-        lsp_message: "$text.muted",
-        padding: {
-          left: 6,
-          right: 6,
-        },
-      },
-      tab: {
-        height: 34,
-        icon_close: "$text.muted.color",
-        icon_close_active: "$text.primary.color",
-        icon_conflict: "$status.warn",
-        icon_dirty: "$status.info",
-        icon_width: 8,
-        spacing: 10,
-        text: "$text.muted",
-        border: {
-          bottom: true,
-          color: "$border.primary",
-          left: true,
-          overlay: true,
-          width: 1,
-        },
-        padding: {
-          left: 12,
-          right: 12,
-        },
-      },
-      titlebar: {
-        avatar_width: 18,
-        height: 32,
-        share_icon_active_color: "$text.primary.color",
-        share_icon_color: "$text.muted.color",
-        title: "$text.primary",
-        avatar: {
-          corner_radius: 10,
-          border: {
-            color: "#00000088",
-            width: 1,
-          },
-        },
-        avatar_ribbon: {
-          background: "#ff0000",
-          height: 3,
-          width: 12,
-        },
-        border: {
-          bottom: true,
-          color: "$border.primary",
-          width: 1,
-        },
-        hovered_sign_in_prompt: {
-          color: "$text.secondary.color",
-          extends: "$workspace.titlebar.sign_in_prompt",
-        },
-        offline_icon: {
-          color: "$text.muted.color",
-          width: 16,
-          padding: {
-            right: 4,
-          },
-        },
-        outdated_warning: {
-          extends: "$text.muted",
-          size: 13,
-        },
-        sign_in_prompt: {
-          extends: "$text.muted",
-          size: 13,
-          underline: true,
-          padding: {
-            right: 8,
-          },
-        },
-      },
-      toolbar: {
-        height: 44,
-      },
-    },
+    workspace: workspace(theme),
     chat_panel: {
       extends: "$panel",
       channel_name: {
@@ -205,7 +70,7 @@ export default function app(theme: Theme): Object {
         extends: "$chat_panel.sign_in_prompt",
       },
       input_editor: {
-        background: "$surface.300",
+        background: backgroundColor(theme, 300),
         corner_radius: 6,
         placeholder_text: "$text.muted",
         selection: "$selection.host",
@@ -306,7 +171,7 @@ export default function app(theme: Theme): Object {
     },
     editor: {
       active_line_background: "$state.active_line",
-      background: "$surface.300",
+      background: backgroundColor(theme, 300),
       code_actions_indicator: "$text.muted.color",
       diff_background_deleted: "$state.deleted_line",
       diff_background_inserted: "$state.inserted_line",
@@ -314,7 +179,7 @@ export default function app(theme: Theme): Object {
       document_highlight_write_background: "#99999916",
       error_color: "$status.bad",
       guest_selections: "$selection.guests",
-      gutter_background: "$surface.300",
+      gutter_background: backgroundColor(theme, 300),
       gutter_padding_factor: 2.5,
       highlighted_line_background: "$state.highlighted_line",
       line_number: "$text.muted.color",
@@ -502,7 +367,7 @@ export default function app(theme: Theme): Object {
       },
     },
     project_diagnostics: {
-      background: "$surface.300",
+      background: backgroundColor(theme, 300),
       tab_icon_spacing: 4,
       tab_icon_width: 13,
       tab_summary_spacing: 10,
@@ -547,7 +412,7 @@ export default function app(theme: Theme): Object {
       },
     },
     search: {
-      background: "$surface.300",
+      background: backgroundColor(theme, 300),
       match_background: "$state.highlighted_line",
       tab_icon_spacing: 4,
       tab_icon_width: 14,
@@ -599,7 +464,7 @@ export default function app(theme: Theme): Object {
         padding: 6,
       },
       option_button: {
-        background: "$surface.300",
+        background: backgroundColor(theme, 300),
         corner_radius: 6,
         extends: "$text.secondary",
         border: {

styles/components.ts 🔗

@@ -1,5 +1,6 @@
 import chroma from "chroma-js";
-import core, { Color } from "./core";
+import core from "./core";
+import { Color } from "./lib";
 import Theme, { BackgroundColor, Weight } from "./theme";
 
 export function text(
@@ -19,13 +20,31 @@ export function text(
   };
 }
 
-export function border(theme: Theme, color: keyof Theme["borderColor"]) {
+export interface BorderOptions {
+  width?: number;
+  top?: boolean;
+  bottom?: boolean;
+  left?: boolean;
+  right?: boolean;
+  overlay?: boolean;
+}
+
+export function border(
+  theme: Theme,
+  color: keyof Theme["borderColor"],
+  options?: BorderOptions
+) {
   return {
     color: theme.borderColor[color].value,
     width: 1,
+    ...options,
   };
 }
 
+export function iconColor(theme: Theme, color: keyof Theme["iconColor"]) {
+  return theme.iconColor[color].value;
+}
+
 export interface Player {
   selection: {
     cursor: Color;

styles/core.color.ts 🔗

@@ -1,93 +0,0 @@
-import chroma from "chroma-js";
-
-export type Color = string;
-
-function returnTokens(
-  colorName: string,
-  ramp: Array<object>, // help, have no clue on type here
-) {
-  let tokens = {};
-  let token = {};
-  let colorNumber = 0;
-  let increment = 0;
-
-  for (let i = 0; i < ramp.len; i++) {
-    if (i > 11 ) {
-      increment = 50;
-    } else {
-      increment = 100;
-    }
-
-    if (i !== 0) {
-      colorNumber = i * increment;
-    }
-
-    token = {
-      [`${colorName}_${colorNumber}`]: {
-        value: ramp[i].value,
-        step: i,
-        type: "color",
-      },
-    };
-
-    Object.assign(token, tokens);
-  }
-  return tokens;
-}
-
-function oneColorRamp(
-  colorName: string, 
-  baseColor: string, 
-  steps: number = 10
-) {
-  let hsl = chroma(baseColor).hsl();
-  let h = Math.round(hsl[0]);
-  let lightColor = chroma.hsl(h, 0.88, 0.96).hex();
-  let darkColor = chroma.hsl(h, 0.68, 0.32).hex();
-
-  let ramp = chroma
-    .scale([lightColor, baseColor, darkColor])
-    .domain([0, 0.5, 1])
-    .mode("hsl")
-    .gamma(1)
-    .correctLightness(true)
-    .padding([0, 0.15])
-    .colors(steps)
-    .hex();
-
-  return returnTokens(colorName, ramp);
-}
-
-function colorRamp(
-  colorName: string, 
-  startColor: string, 
-  endColor: string, 
-  steps: number
-) {
-  let ramp = chroma.scale([startColor, endColor]).mode("hsl").colors(steps).hex();
-
-  return returnTokens(colorName, ramp);
-}
-
-export default {
-  color: {
-    neutral: colorRamp("neutral", "black", "white", 21), // colorName, startColor, endColor, steps
-    rose: oneColorRamp("rose", "#F43F5EFF"), // colorName, baseColor, steps(optional)
-    red: oneColorRamp("red", "#EF4444FF"),
-    orange: oneColorRamp("orange", "#F97316FF"),
-    amber: oneColorRamp("amber", "#F59E0BFF"),
-    yellow: oneColorRamp("yellow", "#EAB308FF"),
-    lime: oneColorRamp("lime", "#84CC16FF"),
-    green: oneColorRamp("green", "#22C55EFF"),
-    emerald: oneColorRamp("emerald", "#10B981FF"),
-    teal: oneColorRamp("teal", "#14B8A6FF"),
-    cyan: oneColorRamp("cyan", "#06BBD4FF"),
-    sky: oneColorRamp("sky", "#0EA5E9FF"),
-    blue: oneColorRamp("blue", "#3B82F6FF"),
-    indigo: oneColorRamp("indigo", "#6366F1FF"),
-    violet: oneColorRamp("violet", "#8B5CF6FF"),
-    purple: oneColorRamp("purple", "#A855F7FF"),
-    fuschia: oneColorRamp("fuschia", "#D946E4FF"),
-    pink: oneColorRamp("pink", "#EC4899FF"),
-  },
-};

styles/core.ts 🔗

@@ -1,10 +1,6 @@
-import color from "./core.color";
-
-export type Color = string;
+import { colorRamp } from "./lib";
 
 export default {
-  color: color,
-
   fontFamily: {
     sans: "Zed Sans",
     mono: "Zed Mono",
@@ -39,4 +35,24 @@ export default {
       type: "fontSizes",
     },
   },
+  color: {
+    neutral: colorRamp(["black", "white"], { steps: 21, increment: 50 }),
+    rose: colorRamp("#F43F5EFF"),
+    red: colorRamp("#EF4444FF"),
+    orange: colorRamp("#F97316FF"),
+    amber: colorRamp("#F59E0BFF"),
+    yellow: colorRamp("#EAB308FF"),
+    lime: colorRamp("#84CC16FF"),
+    green: colorRamp("#22C55EFF"),
+    emerald: colorRamp("#10B981FF"),
+    teal: colorRamp("#14B8A6FF"),
+    cyan: colorRamp("#06BBD4FF"),
+    sky: colorRamp("#0EA5E9FF"),
+    blue: colorRamp("#3B82F6FF"),
+    indigo: colorRamp("#6366F1FF"),
+    violet: colorRamp("#8B5CF6FF"),
+    purple: colorRamp("#A855F7FF"),
+    fuschia: colorRamp("#D946E4FF"),
+    pink: colorRamp("#EC4899FF"),
+  },
 };

styles/lib.ts 🔗

@@ -0,0 +1,44 @@
+import chroma, { Scale } from "chroma-js";
+
+export type Color = string;
+export type ColorRampStep = { value: Color; type: "color"; step: number };
+export type ColorRamp = {
+  [index: number]: ColorRampStep;
+};
+
+export function colorRamp(
+  color: Color | [Color, Color],
+  options?: { steps?: number; increment?: number }
+): ColorRamp {
+  let scale: Scale;
+  if (Array.isArray(color)) {
+    const [startColor, endColor] = color;
+    scale = chroma.scale([startColor, endColor]);
+  } else {
+    let hue = Math.round(chroma(color).hsl()[0]);
+    let startColor = chroma.hsl(hue, 0.88, 0.96);
+    let endColor = chroma.hsl(hue, 0.68, 0.32);
+    scale = chroma
+      .scale([startColor, color, endColor])
+      .domain([0, 0.5, 1])
+      .mode("hsl")
+      .gamma(1)
+      .correctLightness(true)
+      .padding([0, 0.15]);
+  }
+
+  const ramp: ColorRamp = {};
+  const steps = options?.steps || 10;
+  const increment = options?.increment || 100;
+
+  scale.colors(steps, "hex").forEach((color, ix) => {
+    const step = ix * increment;
+    ramp[step] = {
+      value: color,
+      step,
+      type: "color",
+    };
+  });
+
+  return ramp;
+}

styles/package-lock.json 🔗

@@ -9,9 +9,15 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
+        "@types/chroma-js": "^2.1.3",
         "chroma-js": "^2.4.2"
       }
     },
+    "node_modules/@types/chroma-js": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
+      "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
+    },
     "node_modules/chroma-js": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
@@ -19,6 +25,11 @@
     }
   },
   "dependencies": {
+    "@types/chroma-js": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.1.3.tgz",
+      "integrity": "sha512-1xGPhoSGY1CPmXLCBcjVZSQinFjL26vlR8ZqprsBWiFyED4JacJJ9zHhh5aaUXqbY9B37mKQ73nlydVAXmr1+g=="
+    },
     "chroma-js": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",

styles/package.json 🔗

@@ -9,6 +9,7 @@
   "author": "",
   "license": "ISC",
   "dependencies": {
+    "@types/chroma-js": "^2.1.3",
     "chroma-js": "^2.4.2"
   }
 }

styles/selector-modal.ts 🔗

@@ -1,7 +1,7 @@
 import { backgroundColor, border, player, shadow, text } from "./components";
 import Theme from "./theme";
 
-export function selectorModal(theme: Theme): Object {
+export default function selectorModal(theme: Theme): Object {
   const item = {
     padding: {
       bottom: 4,

styles/theme.ts 🔗

@@ -1,3 +1,5 @@
+import { colorRamp } from "./lib";
+
 export type Color = string;
 export type Weight =
   | "thin"
@@ -87,6 +89,50 @@ export default interface Theme {
     feature: {
       value: Color;
     };
+    ok: {
+      value: Color;
+    };
+    error: {
+      value: Color;
+    };
+    warning: {
+      value: Color;
+    };
+    info: {
+      value: Color;
+    };
+  };
+  iconColor: {
+    primary: {
+      value: Color;
+    };
+    secondary: {
+      value: Color;
+    };
+    muted: {
+      value: Color;
+    };
+    placeholder: {
+      value: Color;
+    };
+    active: {
+      value: Color;
+    };
+    feature: {
+      value: Color;
+    };
+    ok: {
+      value: Color;
+    };
+    error: {
+      value: Color;
+    };
+    warning: {
+      value: Color;
+    };
+    info: {
+      value: Color;
+    };
   };
   syntax: {
     primary: SyntaxHighlightStyle;

styles/workspace.ts 🔗

@@ -0,0 +1,133 @@
+import { backgroundColor, border, iconColor, text } from "./components";
+import Theme from "./theme";
+
+export default function workspace(theme: Theme) {
+  const signInPrompt = {
+    ...text(theme, "sans", "secondary"),
+    size: 13,
+    underline: true,
+    padding: {
+      right: 8,
+    },
+  };
+
+  const tab = {
+    height: 34,
+    iconClose: iconColor(theme, "secondary"),
+    iconCloseActive: iconColor(theme, "active"),
+    iconConflict: iconColor(theme, "warning"),
+    iconDirty: iconColor(theme, "info"),
+    iconWidth: 8,
+    spacing: 10,
+    text: text(theme, "mono", "secondary"),
+    border: border(theme, "primary", {
+      left: true,
+      bottom: true,
+      overlay: true,
+    }),
+    padding: {
+      left: 12,
+      right: 12,
+    },
+  };
+
+  const activeTab = {
+    ...tab,
+    background: backgroundColor(theme, 300),
+    text: text(theme, "mono", "primary"),
+    border: {
+      ...tab.border,
+      bottom: false,
+    },
+  };
+
+  const sidebarItem = {
+    height: "$workspace.tab.height",
+    iconColor: "$text.muted.color",
+    iconSize: 18,
+  };
+  const sidebar = {
+    width: 30,
+    border: border(theme, "primary", { right: true }),
+    item: sidebarItem,
+    activeItem: {
+      ...sidebarItem,
+      iconColor: iconColor(theme, "primary"),
+    },
+    resizeHandle: {
+      background: border(theme, "primary").color,
+      padding: {
+        left: 1,
+      },
+    },
+  };
+
+  return {
+    background: backgroundColor(theme, 500),
+    leaderBorderOpacity: 0.7,
+    leaderBorderWidth: 2.0,
+    tab,
+    activeTab,
+    leftSidebar: {
+      ...sidebar,
+      border: border(theme, "primary", { right: true }),
+    },
+    rightSidebar: {
+      ...sidebar,
+      border: border(theme, "primary", { left: true }),
+    },
+    paneDivider: {
+      color: border(theme, "primary").color,
+      width: 1,
+    },
+    status_bar: {
+      height: 24,
+      itemSpacing: 8,
+      padding: {
+        left: 6,
+        right: 6,
+      },
+      cursorPosition: text(theme, "sans", "muted"),
+      diagnosticMessage: text(theme, "sans", "muted"),
+      lspMessage: text(theme, "sans", "muted"),
+    },
+    titlebar: {
+      avatarWidth: 18,
+      height: 32,
+      shareIconColor: iconColor(theme, "secondary"),
+      shareIconActiveColor: iconColor(theme, "active"),
+      title: text(theme, "sans", "primary"),
+      avatar: {
+        cornerRadius: 10,
+        border: {
+          color: "#00000088",
+          width: 1,
+        },
+      },
+      avatarRibbon: {
+        height: 3,
+        width: 12,
+      },
+      border: border(theme, "primary", { bottom: true }),
+      signInPrompt,
+      hoveredSignInPrompt: {
+        ...signInPrompt,
+        ...text(theme, "mono", "active"),
+      },
+      offlineIcon: {
+        color: iconColor(theme, "muted"),
+        width: 16,
+        padding: {
+          right: 4,
+        },
+      },
+      outdatedWarning: {
+        ...text(theme, "sans", "muted"),
+        size: 13,
+      },
+    },
+    toolbar: {
+      height: 44,
+    },
+  };
+}