From 1615c6150aca13c82ad09ccc1972c7262f100863 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 29 Mar 2022 13:08:00 -0600 Subject: [PATCH] Start on TypeScript-based styling system Co-Authored-By: Nate Butler <1714999+iamnbutler@users.noreply.github.com> --- .gitignore | 1 + styles/app.ts | 632 +++++++++++++++++++++++++++++++++++++++ styles/components.ts | 62 ++++ styles/core.ts | 38 +++ styles/package-lock.json | 28 ++ styles/package.json | 14 + styles/selector-modal.ts | 59 ++++ styles/theme.ts | 121 ++++++++ 8 files changed, 955 insertions(+) create mode 100644 styles/app.ts create mode 100644 styles/components.ts create mode 100644 styles/core.ts create mode 100644 styles/package-lock.json create mode 100644 styles/package.json create mode 100644 styles/selector-modal.ts create mode 100644 styles/theme.ts diff --git a/.gitignore b/.gitignore index e88434ce4e..140053fc2c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /zed.xcworkspace .DS_Store /script/node_modules +/styles/node_modules /crates/server/.env.toml /crates/server/static/styles.css /vendor/bin diff --git a/styles/app.ts b/styles/app.ts new file mode 100644 index 0000000000..a7ae4348be --- /dev/null +++ b/styles/app.ts @@ -0,0 +1,632 @@ +import { selectorModal } from "./selector-modal"; +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, + }, + }, + chat_panel: { + extends: "$panel", + channel_name: { + extends: "$text.primary", + weight: "bold", + }, + channel_name_hash: { + text: "$text.muted", + padding: { + right: 8, + }, + }, + channel_select: { + active_item: { + extends: "$chat_panel.channel_select.item", + name: "$text.primary", + }, + header: { + extends: "$chat_panel.channel_select.active_item", + padding: { + bottom: 4, + left: 0, + }, + }, + hovered_active_item: { + extends: "$chat_panel.channel_select.hovered_item", + name: "$text.primary", + }, + hovered_item: { + background: "$state.hover", + corner_radius: 6, + extends: "$chat_panel.channel_select.item", + }, + item: { + name: "$text.secondary", + padding: 4, + hash: { + extends: "$text.muted", + margin: { + right: 8, + }, + }, + }, + menu: { + background: "$surface.500", + corner_radius: 6, + padding: 4, + border: { + color: "$border.primary", + width: 1, + }, + shadow: { + blur: 16, + color: "$shadow.0", + offset: [0, 2], + }, + }, + }, + hovered_sign_in_prompt: { + color: "$text.secondary.color", + extends: "$chat_panel.sign_in_prompt", + }, + input_editor: { + background: "$surface.300", + corner_radius: 6, + placeholder_text: "$text.muted", + selection: "$selection.host", + text: "$text.primary", + border: { + color: "$border.primary", + width: 1, + }, + padding: { + bottom: 7, + left: 8, + right: 8, + top: 7, + }, + }, + message: { + body: "$text.secondary", + timestamp: "$text.muted", + padding: { + bottom: 6, + }, + sender: { + extends: "$text.primary", + weight: "bold", + margin: { + right: 8, + }, + }, + }, + pending_message: { + extends: "$chat_panel.message", + body: { + color: "$text.muted.color", + }, + sender: { + color: "$text.muted.color", + }, + timestamp: { + color: "$text.muted.color", + }, + }, + sign_in_prompt: { + extends: "$text.primary", + underline: true, + }, + }, + contacts_panel: { + extends: "$panel", + host_row_height: 28, + tree_branch_color: "$surface.100", + tree_branch_width: 1, + host_avatar: { + corner_radius: 10, + width: 18, + }, + host_username: { + extends: "$text.primary", + padding: { + left: 8, + }, + }, + hovered_shared_project: { + background: "$state.hover", + corner_radius: 6, + extends: "$contacts_panel.shared_project", + }, + hovered_unshared_project: { + background: "$state.hover", + corner_radius: 6, + extends: "$contacts_panel.unshared_project", + }, + project: { + guest_avatar_spacing: 4, + height: 24, + guest_avatar: { + corner_radius: 8, + width: 14, + }, + name: { + extends: "$text.secondary", + margin: { + right: 6, + }, + }, + padding: { + left: 8, + }, + }, + shared_project: { + extends: "$contacts_panel.project", + name: { + color: "$text.primary.color", + }, + }, + unshared_project: { + extends: "$contacts_panel.project", + }, + }, + editor: { + active_line_background: "$state.active_line", + background: "$surface.300", + code_actions_indicator: "$text.muted.color", + diff_background_deleted: "$state.deleted_line", + diff_background_inserted: "$state.inserted_line", + document_highlight_read_background: "#99999920", + document_highlight_write_background: "#99999916", + error_color: "$status.bad", + guest_selections: "$selection.guests", + gutter_background: "$surface.300", + gutter_padding_factor: 2.5, + highlighted_line_background: "$state.highlighted_line", + line_number: "$text.muted.color", + line_number_active: "$text.primary.color", + rename_fade: 0.6, + selection: "$selection.host", + text_color: "$text.secondary.color", + unnecessary_code_fade: 0.5, + autocomplete: { + background: "$surface.100", + corner_radius: 6, + padding: 6, + border: { + color: "$border.secondary", + width: 2, + }, + hovered_item: { + background: "$state.hover", + extends: "$editor.autocomplete.item", + }, + item: { + corner_radius: 6, + padding: { + bottom: 2, + left: 6, + right: 6, + top: 2, + }, + }, + margin: { + left: -14, + }, + match_highlight: { + color: "$editor.syntax.keyword.color", + weight: "$editor.syntax.keyword.weight", + }, + selected_item: { + background: "$state.selected", + extends: "$editor.autocomplete.item", + }, + }, + diagnostic_header: { + background: "$editor.background", + icon_width_factor: 1.5, + text_scale_factor: 0.857, + border: { + bottom: true, + color: "$border.secondary", + top: true, + width: 1, + }, + code: { + extends: "$text.muted", + size: 14, + margin: { + left: 10, + }, + }, + message: { + highlight_text: { + extends: "$text.primary", + size: 14, + weight: "bold", + }, + text: { + extends: "$text.secondary", + size: 14, + }, + }, + }, + diagnostic_path_header: { + background: "$state.active_line", + text_scale_factor: 0.857, + filename: { + extends: "$text.primary", + size: 14, + }, + path: { + extends: "$text.muted", + size: 14, + margin: { + left: 12, + }, + }, + }, + error_diagnostic: { + text_scale_factor: 0.857, + header: { + border: { + color: "$border.primary", + top: true, + width: 1, + }, + }, + message: { + highlight_text: { + color: "$status.bad", + extends: "$text.secondary", + size: 14, + weight: "bold", + }, + text: { + color: "$status.bad", + extends: "$text.secondary", + size: 14, + }, + }, + }, + hint_diagnostic: { + extends: "$editor.error_diagnostic", + message: { + highlight_text: { + color: "$status.info", + }, + text: { + color: "$status.info", + }, + }, + }, + information_diagnostic: { + extends: "$editor.error_diagnostic", + message: { + highlight_text: { + color: "$status.info", + }, + text: { + color: "$status.info", + }, + }, + }, + invalid_error_diagnostic: { + extends: "$editor.error_diagnostic", + message: { + highlight_text: { + color: "$text.muted.color", + }, + text: { + color: "$text.muted.color", + }, + }, + }, + invalid_hint_diagnostic: { + extends: "$editor.hint_diagnostic", + message: { + highlight_text: { + color: "$text.muted.color", + }, + text: { + color: "$text.muted.color", + }, + }, + }, + invalid_information_diagnostic: { + extends: "$editor.information_diagnostic", + message: { + highlight_text: { + color: "$text.muted.color", + }, + text: { + color: "$text.muted.color", + }, + }, + }, + invalid_warning_diagnostic: { + extends: "$editor.warning_diagnostic", + message: { + highlight_text: { + color: "$text.muted.color", + }, + text: { + color: "$text.muted.color", + }, + }, + }, + warning_diagnostic: { + extends: "$editor.error_diagnostic", + message: { + highlight_text: { + color: "$status.warn", + }, + text: { + color: "$status.warn", + }, + }, + }, + }, + project_diagnostics: { + background: "$surface.300", + tab_icon_spacing: 4, + tab_icon_width: 13, + tab_summary_spacing: 10, + empty_message: { + extends: "$text.primary", + size: 18, + }, + status_bar_item: { + extends: "$text.muted", + margin: { + right: 10, + }, + }, + }, + project_panel: { + extends: "$panel", + entry: { + height: 22, + icon_color: "$text.muted.color", + icon_size: 8, + icon_spacing: 8, + text: "$text.secondary", + }, + hovered_entry: { + background: "$state.hover", + extends: "$project_panel.entry", + }, + hovered_selected_entry: { + extends: "$project_panel.hovered_entry", + text: { + extends: "$text.primary", + }, + }, + padding: { + top: 6, + }, + selected_entry: { + extends: "$project_panel.entry", + text: { + extends: "$text.primary", + }, + }, + }, + search: { + background: "$surface.300", + match_background: "$state.highlighted_line", + tab_icon_spacing: 4, + tab_icon_width: 14, + active_hovered_option_button: { + background: "$surface.100", + extends: "$search.option_button", + }, + active_option_button: { + background: "$surface.100", + extends: "$search.option_button", + }, + editor: { + background: "$surface.500", + corner_radius: 6, + max_width: 400, + placeholder_text: "$text.muted", + selection: "$selection.host", + text: "$text.primary", + border: { + color: "$border.primary", + width: 1, + }, + margin: { + bottom: 5, + left: 5, + right: 5, + top: 5, + }, + padding: { + bottom: 3, + left: 13, + right: 13, + top: 3, + }, + }, + hovered_option_button: { + background: "$surface.100", + extends: "$search.option_button", + }, + invalid_editor: { + extends: "$search.editor", + border: { + color: "$status.bad", + width: 1, + }, + }, + match_index: { + extends: "$text.secondary", + padding: 6, + }, + option_button: { + background: "$surface.300", + corner_radius: 6, + extends: "$text.secondary", + border: { + color: "$border.primary", + width: 1, + }, + margin: { + left: 1, + right: 1, + }, + padding: { + bottom: 1, + left: 6, + right: 6, + top: 1, + }, + }, + option_button_group: { + padding: { + left: 2, + right: 2, + }, + }, + results_status: { + extends: "$text.primary", + size: 18, + }, + }, + }; +} diff --git a/styles/components.ts b/styles/components.ts new file mode 100644 index 0000000000..226d1d9e37 --- /dev/null +++ b/styles/components.ts @@ -0,0 +1,62 @@ +import chroma from "chroma-js"; +import core, { Color } from "./core"; +import Theme, { BackgroundColor, Weight } from "./theme"; + +export function text( + theme: Theme, + fontFamily: keyof typeof core.fontFamily, + color: keyof Theme["textColor"], + properties?: { size?: keyof typeof core["fontSize"]; weight?: Weight } +) { + const sizeKey = properties.size || fontFamily === "sans" ? "sm" : "md"; + const size = core.fontSize[sizeKey].value; + + return { + family: core.fontFamily[fontFamily], + color: theme.textColor[color].value, + ...properties, + size, + }; +} + +export function border(theme: Theme, color: keyof Theme["borderColor"]) { + return { + color: theme.borderColor[color].value, + width: 1, + }; +} + +export interface Player { + selection: { + cursor: Color; + selection: Color; + }; +} + +export function player( + theme: Theme, + playerNumber: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 +): Player { + return { + selection: { + cursor: theme.player[playerNumber].cursorColor.value, + selection: theme.player[playerNumber].selectionColor.value, + }, + }; +} + +export function backgroundColor( + theme: Theme, + name: keyof Theme["backgroundColor"], + state?: keyof BackgroundColor +): Color { + return theme.backgroundColor[name][state || "base"].value; +} + +export function shadow(theme) { + return { + blur: 16, + color: chroma("black").alpha(theme.shadowAlpha.value).hex(), + offset: [0, 2], + }; +} diff --git a/styles/core.ts b/styles/core.ts new file mode 100644 index 0000000000..6c63b2285e --- /dev/null +++ b/styles/core.ts @@ -0,0 +1,38 @@ +export type Color = string; + +export default { + fontFamily: { + sans: "Zed Sans", + mono: "Zed Mono", + }, + fontSize: { + "3xs": { + value: "8", + type: "fontSizes", + }, + "2xs": { + value: "10", + type: "fontSizes", + }, + xs: { + value: "12", + type: "fontSizes", + }, + sm: { + value: "14", + type: "fontSizes", + }, + md: { + value: "16", + type: "fontSizes", + }, + lg: { + value: "18", + type: "fontSizes", + }, + xl: { + value: "20", + type: "fontSizes", + }, + }, +}; diff --git a/styles/package-lock.json b/styles/package-lock.json new file mode 100644 index 0000000000..3abb465628 --- /dev/null +++ b/styles/package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "styles", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "styles", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "chroma-js": "^2.4.2" + } + }, + "node_modules/chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + } + }, + "dependencies": { + "chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==" + } + } +} diff --git a/styles/package.json b/styles/package.json new file mode 100644 index 0000000000..92f0546c56 --- /dev/null +++ b/styles/package.json @@ -0,0 +1,14 @@ +{ + "name": "styles", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "chroma-js": "^2.4.2" + } +} diff --git a/styles/selector-modal.ts b/styles/selector-modal.ts new file mode 100644 index 0000000000..f7101c9151 --- /dev/null +++ b/styles/selector-modal.ts @@ -0,0 +1,59 @@ +import { backgroundColor, border, player, shadow, text } from "./components"; +import Theme from "./theme"; + +export function selectorModal(theme: Theme): Object { + const item = { + padding: { + bottom: 4, + left: 16, + right: 16, + top: 4, + }, + cornerRadius: 6, + text: text(theme, "sans", "secondary"), + highlightText: text(theme, "sans", "feature", { weight: "bold" }), + }; + + const activeItem = { + ...item, + background: backgroundColor(theme, 500, "active"), + text: text(theme, "sans", "primary"), + }; + + return { + background: backgroundColor(theme, 500), + cornerRadius: 6, + padding: 8, + item, + activeItem, + border: border(theme, "primary"), + empty: { + text: text(theme, "sans", "muted"), + padding: { + bottom: 4, + left: 16, + right: 16, + top: 8, + }, + }, + inputEditor: { + background: backgroundColor(theme, 300), + corner_radius: 6, + placeholderText: text(theme, "sans", "placeholder"), + selection: player(theme, 1).selection, + text: text(theme, "mono", "primary"), + border: border(theme, "primary"), + padding: { + bottom: 7, + left: 16, + right: 16, + top: 7, + }, + }, + margin: { + bottom: 52, + top: 52, + }, + shadow: shadow(theme), + }; +} diff --git a/styles/theme.ts b/styles/theme.ts new file mode 100644 index 0000000000..5363a8c01c --- /dev/null +++ b/styles/theme.ts @@ -0,0 +1,121 @@ +export type Color = string; +export type Weight = + | "thin" + | "extra_light" + | "light" + | "normal" + | "medium" + | "semibold" + | "bold" + | "extra_bold" + | "black"; + +interface SyntaxHighlightStyle { + color: { value: Color }; + weight: { value: Weight }; +} + +interface Player { + baseColor: { + value: Color; + }; + cursorColor: { + value: Color; + }; + selectionColor: { + value: Color; + }; + borderColor: { + value: Color; + }; +} + +export interface BackgroundColor { + base: { + value: Color; + }; + hover: { + value: Color; + }; + active: { + value: Color; + }; + focused: { + value: Color; + }; +} + +export default interface Theme { + backgroundColor: { + 100: BackgroundColor; + 300: BackgroundColor; + 500: BackgroundColor; + }; + borderColor: { + primary: { + value: Color; + }; + secondary: { + value: Color; + }; + muted: { + value: Color; + }; + focused: { + value: Color; + }; + active: { + value: Color; + }; + }; + textColor: { + primary: { + value: Color; + }; + secondary: { + value: Color; + }; + muted: { + value: Color; + }; + placeholder: { + value: Color; + }; + active: { + value: Color; + }; + feature: { + value: Color; + }; + }; + syntax: { + primary: SyntaxHighlightStyle; + comment: SyntaxHighlightStyle; + punctuation: SyntaxHighlightStyle; + constant: SyntaxHighlightStyle; + keyword: SyntaxHighlightStyle; + function: SyntaxHighlightStyle; + type: SyntaxHighlightStyle; + variant: SyntaxHighlightStyle; + property: SyntaxHighlightStyle; + enum: SyntaxHighlightStyle; + operator: SyntaxHighlightStyle; + string: SyntaxHighlightStyle; + number: SyntaxHighlightStyle; + boolean: SyntaxHighlightStyle; + predictive: SyntaxHighlightStyle; + }; + player: { + 1: Player; + 2: Player; + 3: Player; + 4: Player; + 5: Player; + 6: Player; + 7: Player; + 8: Player; + }; + shadowAlpha: { + value: number; + }; +}