Use theme store to pass `color_scheme` directly to components

Nate Butler created

Change summary

styles/package-lock.json                             | 64 +++++++++++++
styles/package.json                                  | 15 +-
styles/src/build_themes.ts                           | 14 +-
styles/src/component/icon_button.ts                  | 13 +-
styles/src/component/text_button.ts                  | 17 ++-
styles/src/styleTree/editor.ts                       |  0 
styles/src/style_tree/app.ts                         | 56 ++++++-----
styles/src/style_tree/assistant.ts                   |  6 
styles/src/style_tree/command_palette.ts             |  6 
styles/src/style_tree/contact_finder.ts              |  8 +
styles/src/style_tree/contact_list.ts                |  6 
styles/src/style_tree/contact_notification.ts        |  6 
styles/src/style_tree/contacts_popover.ts            |  6 
styles/src/style_tree/context_menu.ts                |  6 
styles/src/style_tree/copilot.ts                     |  6 
styles/src/style_tree/editor.ts                      |  9 +
styles/src/style_tree/feedback.ts                    |  6 
styles/src/style_tree/hover_popover.ts               |  6 
styles/src/style_tree/incoming_call_notification.ts  |  8 
styles/src/style_tree/picker.ts                      |  6 
styles/src/style_tree/project_diagnostics.ts         |  6 
styles/src/style_tree/project_panel.ts               | 23 ++--
styles/src/style_tree/project_shared_notification.ts |  8 
styles/src/style_tree/search.ts                      |  6 
styles/src/style_tree/shared_screen.ts               |  6 
styles/src/style_tree/simple_message_notification.ts |  6 
styles/src/style_tree/status_bar.ts                  |  6 
styles/src/style_tree/tab_bar.ts                     |  6 
styles/src/style_tree/terminal.ts                    |  6 
styles/src/style_tree/titlebar.ts                    | 20 ++-
styles/src/style_tree/toolbar_dropdown_menu.ts       |  6 
styles/src/style_tree/tooltip.ts                     |  6 
styles/src/style_tree/update_notification.ts         |  6 
styles/src/style_tree/welcome.ts                     |  6 
styles/src/style_tree/workspace.ts                   | 14 +-
styles/src/theme/index.ts                            | 21 ++++
36 files changed, 280 insertions(+), 136 deletions(-)

Detailed changes

styles/package-lock.json 🔗

@@ -27,7 +27,8 @@
                 "ts-node": "^10.9.1",
                 "typescript": "^5.1.5",
                 "utility-types": "^3.10.0",
-                "vitest": "^0.32.0"
+                "vitest": "^0.32.0",
+                "zustand": "^4.3.8"
             }
         },
         "node_modules/@aashutoshrathi/word-wrap": {
@@ -2595,6 +2596,12 @@
                 "node": ">= 0.8"
             }
         },
+        "node_modules/js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "peer": true
+        },
         "node_modules/js-yaml": {
             "version": "4.1.0",
             "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2706,6 +2713,18 @@
             "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
             "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
         },
+        "node_modules/loose-envify": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+            "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+            "peer": true,
+            "dependencies": {
+                "js-tokens": "^3.0.0 || ^4.0.0"
+            },
+            "bin": {
+                "loose-envify": "cli.js"
+            }
+        },
         "node_modules/loupe": {
             "version": "2.3.6",
             "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
@@ -3292,6 +3311,18 @@
                 }
             ]
         },
+        "node_modules/react": {
+            "version": "18.2.0",
+            "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
+            "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
+            "peer": true,
+            "dependencies": {
+                "loose-envify": "^1.1.0"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
         "node_modules/react-is": {
             "version": "17.0.2",
             "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
@@ -4025,6 +4056,14 @@
                 "punycode": "^2.1.0"
             }
         },
+        "node_modules/use-sync-external-store": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+            "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+            "peerDependencies": {
+                "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+            }
+        },
         "node_modules/utility-types": {
             "version": "3.10.0",
             "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.10.0.tgz",
@@ -4305,6 +4344,29 @@
             "funding": {
                 "url": "https://github.com/sponsors/sindresorhus"
             }
+        },
+        "node_modules/zustand": {
+            "version": "4.3.8",
+            "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.8.tgz",
+            "integrity": "sha512-4h28KCkHg5ii/wcFFJ5Fp+k1J3gJoasaIbppdgZFO4BPJnsNxL0mQXBSFgOgAdCdBj35aDTPvdAJReTMntFPGg==",
+            "dependencies": {
+                "use-sync-external-store": "1.2.0"
+            },
+            "engines": {
+                "node": ">=12.7.0"
+            },
+            "peerDependencies": {
+                "immer": ">=9.0",
+                "react": ">=16.8"
+            },
+            "peerDependenciesMeta": {
+                "immer": {
+                    "optional": true
+                },
+                "react": {
+                    "optional": true
+                }
+            }
         }
     }
 }

styles/package.json 🔗

@@ -16,21 +16,22 @@
         "@tokens-studio/types": "^0.2.3",
         "@types/chroma-js": "^2.4.0",
         "@types/node": "^18.14.1",
+        "@typescript-eslint/eslint-plugin": "^5.60.1",
+        "@typescript-eslint/parser": "^5.60.1",
+        "@vitest/coverage-v8": "^0.32.0",
         "ayu": "^8.0.1",
         "chroma-js": "^2.4.2",
         "deepmerge": "^4.3.0",
+        "eslint": "^8.43.0",
+        "eslint-import-resolver-typescript": "^3.5.5",
+        "eslint-plugin-import": "^2.27.5",
         "json-schema-to-typescript": "^13.0.2",
         "toml": "^3.0.0",
         "ts-deepmerge": "^6.0.3",
         "ts-node": "^10.9.1",
+        "typescript": "^5.1.5",
         "utility-types": "^3.10.0",
         "vitest": "^0.32.0",
-        "@typescript-eslint/eslint-plugin": "^5.60.1",
-        "@typescript-eslint/parser": "^5.60.1",
-        "@vitest/coverage-v8": "^0.32.0",
-        "eslint": "^8.43.0",
-        "eslint-import-resolver-typescript": "^3.5.5",
-        "eslint-plugin-import": "^2.27.5",
-        "typescript": "^5.1.5"
+        "zustand": "^4.3.8"
     }
 }

styles/src/build_themes.ts 🔗

@@ -4,6 +4,7 @@ import * as path from "path"
 import app from "./style_tree/app"
 import { ColorScheme, create_color_scheme } from "./theme/color_scheme"
 import { themes } from "./themes"
+import { useThemeStore } from "./theme"
 
 const assets_directory = `${__dirname}/../../assets`
 const temp_directory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"))
@@ -20,10 +21,17 @@ function clear_themes(theme_directory: string) {
     }
 }
 
+const all_themes: ColorScheme[] = themes.map((theme) =>
+    create_color_scheme(theme)
+)
+
 function write_themes(themes: ColorScheme[], output_directory: string) {
     clear_themes(output_directory)
     for (const color_scheme of themes) {
-        const style_tree = app(color_scheme)
+        const { setTheme } = useThemeStore.getState()
+        setTheme(color_scheme)
+
+        const style_tree = app()
         const style_tree_json = JSON.stringify(style_tree, null, 2)
         const temp_path = path.join(temp_directory, `${color_scheme.name}.json`)
         const out_path = path.join(
@@ -36,8 +44,4 @@ function write_themes(themes: ColorScheme[], output_directory: string) {
     }
 }
 
-const all_themes: ColorScheme[] = themes.map((theme) =>
-    create_color_scheme(theme)
-)
-
 write_themes(all_themes, `${assets_directory}/themes`)

styles/src/component/icon_button.ts 🔗

@@ -1,6 +1,6 @@
 import { interactive, toggleable } from "../element"
 import { background, foreground } from "../style_tree/components"
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme, ColorScheme } from "../theme"
 
 export type Margin = {
     top: number
@@ -22,10 +22,9 @@ type ToggleableIconButtonOptions = IconButtonOptions & {
     active_color?: keyof ColorScheme["lowest"]
 }
 
-export function icon_button(
-    theme: ColorScheme,
-    { color, margin, layer }: IconButtonOptions
-) {
+export function icon_button({ color, margin, layer }: IconButtonOptions) {
+    const theme = useTheme()
+
     if (!color) color = "base"
 
     const m = {
@@ -75,8 +74,8 @@ export function toggleable_icon_button(
 
     return toggleable({
         state: {
-            inactive: icon_button(theme, { color, margin }),
-            active: icon_button(theme, {
+            inactive: icon_button({ color, margin }),
+            active: icon_button({
                 color: active_color ? active_color : color,
                 margin,
                 layer: theme.middle,

styles/src/component/text_button.ts 🔗

@@ -5,7 +5,7 @@ import {
     foreground,
     text,
 } from "../style_tree/components"
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme, ColorScheme } from "../theme"
 import { Margin } from "./icon_button"
 
 interface TextButtonOptions {
@@ -22,10 +22,13 @@ type ToggleableTextButtonOptions = TextButtonOptions & {
     active_color?: keyof ColorScheme["lowest"]
 }
 
-export function text_button(
-    theme: ColorScheme,
-    { color, layer, margin, text_properties }: TextButtonOptions
-) {
+export function text_button({
+    color,
+    layer,
+    margin,
+    text_properties,
+}: TextButtonOptions) {
+    const theme = useTheme()
     if (!color) color = "base"
 
     const text_options: TextProperties = {
@@ -79,8 +82,8 @@ export function toggleable_text_button(
 
     return toggleable({
         state: {
-            inactive: text_button(theme, { color, margin }),
-            active: text_button(theme, {
+            inactive: text_button({ color, margin }),
+            active: text_button({
                 color: active_color ? active_color : color,
                 margin,
                 layer: theme.middle,

styles/src/style_tree/app.ts 🔗

@@ -17,45 +17,47 @@ import terminal from "./terminal"
 import contact_list from "./contact_list"
 import toolbar_dropdown_menu from "./toolbar_dropdown_menu"
 import incoming_call_notification from "./incoming_call_notification"
-import { ColorScheme } from "../theme/color_scheme"
 import welcome from "./welcome"
 import copilot from "./copilot"
 import assistant from "./assistant"
 import { titlebar } from "./titlebar"
 import editor from "./editor"
 import feedback from "./feedback"
+import { useTheme } from "../common"
+
+export default function app(): any {
+    const theme = useTheme()
 
-export default function app(theme: ColorScheme): any {
     return {
         meta: {
             name: theme.name,
             is_light: theme.is_light,
         },
-        command_palette: command_palette(theme),
-        contact_notification: contact_notification(theme),
-        project_shared_notification: project_shared_notification(theme),
-        incoming_call_notification: incoming_call_notification(theme),
-        picker: picker(theme),
-        workspace: workspace(theme),
-        titlebar: titlebar(theme),
-        copilot: copilot(theme),
-        welcome: welcome(theme),
-        context_menu: context_menu(theme),
-        editor: editor(theme),
-        project_diagnostics: project_diagnostics(theme),
-        project_panel: project_panel(theme),
-        contacts_popover: contacts_popover(theme),
-        contact_finder: contact_finder(theme),
-        contact_list: contact_list(theme),
-        toolbar_dropdown_menu: toolbar_dropdown_menu(theme),
-        search: search(theme),
-        shared_screen: shared_screen(theme),
-        update_notification: update_notification(theme),
-        simple_message_notification: simple_message_notification(theme),
-        tooltip: tooltip(theme),
-        terminal: terminal(theme),
-        assistant: assistant(theme),
-        feedback: feedback(theme),
+        command_palette: command_palette(),
+        contact_notification: contact_notification(),
+        project_shared_notification: project_shared_notification(),
+        incoming_call_notification: incoming_call_notification(),
+        picker: picker(),
+        workspace: workspace(),
+        titlebar: titlebar(),
+        copilot: copilot(),
+        welcome: welcome(),
+        context_menu: context_menu(),
+        editor: editor(),
+        project_diagnostics: project_diagnostics(),
+        project_panel: project_panel(),
+        contacts_popover: contacts_popover(),
+        contact_finder: contact_finder(),
+        contact_list: contact_list(),
+        toolbar_dropdown_menu: toolbar_dropdown_menu(),
+        search: search(),
+        shared_screen: shared_screen(),
+        update_notification: update_notification(),
+        simple_message_notification: simple_message_notification(),
+        tooltip: tooltip(),
+        terminal: terminal(),
+        assistant: assistant(),
+        feedback: feedback(),
         color_scheme: {
             ...theme,
             players: Object.values(theme.players),

styles/src/style_tree/assistant.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { text, border, background, foreground } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function assistant(): any {
+    const theme = useTheme()
 
-export default function assistant(theme: ColorScheme): any {
     return {
         container: {
             background: background(theme.highest),

styles/src/style_tree/command_palette.ts 🔗

@@ -1,9 +1,11 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import { text, background } from "./components"
 import { toggleable } from "../element"
+import { useTheme } from "../theme"
+
+export default function command_palette(): any {
+    const theme = useTheme()
 
-export default function command_palette(theme: ColorScheme): any {
     const key = toggleable({
         base: {
             text: text(theme.highest, "mono", "variant", "default", {

styles/src/style_tree/contact_finder.ts 🔗

@@ -1,8 +1,10 @@
 import picker from "./picker"
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, foreground, text } from "./components"
+import { useTheme } from "../theme"
+
+export default function contact_finder(): any {
+    const theme = useTheme()
 
-export default function contact_finder(theme: ColorScheme): any {
     const side_margin = 6
     const contact_button = {
         background: background(theme.middle, "variant"),
@@ -12,7 +14,7 @@ export default function contact_finder(theme: ColorScheme): any {
         corner_radius: 8,
     }
 
-    const picker_style = picker(theme)
+    const picker_style = picker()
     const picker_input = {
         background: background(theme.middle, "on"),
         corner_radius: 6,

styles/src/style_tree/contact_list.ts 🔗

@@ -1,4 +1,3 @@
-import { ColorScheme } from "../theme/color_scheme"
 import {
     background,
     border,
@@ -7,7 +6,10 @@ import {
     text,
 } from "./components"
 import { interactive, toggleable } from "../element"
-export default function contacts_panel(theme: ColorScheme): any {
+import { useTheme } from "../theme"
+export default function contacts_panel(): any {
+    const theme = useTheme()
+
     const name_margin = 8
     const side_padding = 12
 

styles/src/style_tree/contact_notification.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, foreground, text } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function contact_notification(): any {
+    const theme = useTheme()
 
-export default function contact_notification(theme: ColorScheme): any {
     const avatar_size = 12
     const header_padding = 8
 

styles/src/style_tree/contacts_popover.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, border } from "./components"
 
-export default function contacts_popover(theme: ColorScheme): any {
+export default function contacts_popover(): any {
+    const theme = useTheme()
+
     return {
         background: background(theme.middle),
         corner_radius: 6,

styles/src/style_tree/context_menu.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, border_color, text } from "./components"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
+
+export default function context_menu(): any {
+    const theme = useTheme()
 
-export default function context_menu(theme: ColorScheme): any {
     return {
         background: background(theme.middle),
         corner_radius: 10,

styles/src/style_tree/copilot.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, foreground, svg, text } from "./components"
 import { interactive } from "../element"
-export default function copilot(theme: ColorScheme): any {
+import { useTheme } from "../theme"
+export default function copilot(): any {
+    const theme = useTheme()
+
     const content_width = 264
 
     const cta_button =

styles/src/style_tree/editor.ts 🔗

@@ -1,5 +1,5 @@
 import { with_opacity } from "../theme/color"
-import { ColorScheme, Layer, StyleSets } from "../theme/color_scheme"
+import { Layer, StyleSets } from "../theme/color_scheme"
 import {
     background,
     border,
@@ -11,8 +11,11 @@ import hover_popover from "./hover_popover"
 
 import { build_syntax } from "../theme/syntax"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
+
+export default function editor(): any {
+    const theme = useTheme()
 
-export default function editor(theme: ColorScheme): any {
     const { is_light } = theme
 
     const layer = theme.highest
@@ -248,7 +251,7 @@ export default function editor(theme: ColorScheme): any {
         invalid_hint_diagnostic: diagnostic(theme.middle, "base"),
         invalid_information_diagnostic: diagnostic(theme.middle, "base"),
         invalid_warning_diagnostic: diagnostic(theme.middle, "base"),
-        hover_popover: hover_popover(theme),
+        hover_popover: hover_popover(),
         link_definition: {
             color: syntax.link_uri.color,
             underline: syntax.link_uri.underline,

styles/src/style_tree/feedback.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, text } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function feedback(): any {
+    const theme = useTheme()
 
-export default function feedback(theme: ColorScheme): any {
     return {
         submit_button: interactive({
             base: {

styles/src/style_tree/hover_popover.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, border, foreground, text } from "./components"
 
-export default function hover_popover(theme: ColorScheme): any {
+export default function hover_popover(): any {
+    const theme = useTheme()
+
     const base_container = {
         background: background(theme.middle),
         corner_radius: 8,

styles/src/style_tree/incoming_call_notification.ts 🔗

@@ -1,9 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, border, text } from "./components"
 
-export default function incoming_call_notification(
-    theme: ColorScheme
-): unknown {
+export default function incoming_call_notification(): unknown {
+    const theme = useTheme()
+
     const avatar_size = 48
     return {
         window_height: 74,

styles/src/style_tree/picker.ts 🔗

@@ -1,9 +1,11 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import { background, border, text } from "./components"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
+
+export default function picker(): any {
+    const theme = useTheme()
 
-export default function picker(theme: ColorScheme): any {
     const container = {
         background: background(theme.lowest),
         border: border(theme.lowest),

styles/src/style_tree/project_diagnostics.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, text } from "./components"
 
-export default function project_diagnostics(theme: ColorScheme): any {
+export default function project_diagnostics(): any {
+    const theme = useTheme()
+
     return {
         background: background(theme.highest),
         tab_icon_spacing: 4,

styles/src/style_tree/project_panel.ts 🔗

@@ -1,4 +1,3 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import {
     Border,
@@ -10,7 +9,10 @@ import {
 } from "./components"
 import { interactive, toggleable } from "../element"
 import merge from "ts-deepmerge"
-export default function project_panel(theme: ColorScheme): any {
+import { useTheme } from "../theme"
+export default function project_panel(): any {
+    const theme = useTheme()
+
     const { is_light } = theme
 
     type EntryStateProps = {
@@ -65,13 +67,12 @@ export default function project_panel(theme: ColorScheme): any {
         const unselected_hovered_style = merge(
             base_properties,
             { background: background(theme.middle, "hovered") },
-            unselected?.hovered ?? {},
+            unselected?.hovered ?? {}
         )
         const unselected_clicked_style = merge(
             base_properties,
-            { background: background(theme.middle, "pressed"), }
-            ,
-            unselected?.clicked ?? {},
+            { background: background(theme.middle, "pressed") },
+            unselected?.clicked ?? {}
         )
         const selected_default_style = merge(
             base_properties,
@@ -79,18 +80,15 @@ export default function project_panel(theme: ColorScheme): any {
                 background: background(theme.lowest),
                 text: text(theme.lowest, "sans", { size: "sm" }),
             },
-            selected_style?.default ?? {},
-
+            selected_style?.default ?? {}
         )
         const selected_hovered_style = merge(
             base_properties,
             {
                 background: background(theme.lowest, "hovered"),
                 text: text(theme.lowest, "sans", { size: "sm" }),
-
             },
-            selected_style?.hovered ?? {},
-
+            selected_style?.hovered ?? {}
         )
         const selected_clicked_style = merge(
             base_properties,
@@ -98,8 +96,7 @@ export default function project_panel(theme: ColorScheme): any {
                 background: background(theme.lowest, "pressed"),
                 text: text(theme.lowest, "sans", { size: "sm" }),
             },
-            selected_style?.clicked ?? {},
-
+            selected_style?.clicked ?? {}
         )
 
         return toggleable({

styles/src/style_tree/project_shared_notification.ts 🔗

@@ -1,9 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, border, text } from "./components"
 
-export default function project_shared_notification(
-    theme: ColorScheme
-): unknown {
+export default function project_shared_notification(): unknown {
+    const theme = useTheme()
+
     const avatar_size = 48
     return {
         window_height: 74,

styles/src/style_tree/search.ts 🔗

@@ -1,9 +1,11 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import { background, border, foreground, text } from "./components"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
+
+export default function search(): any {
+    const theme = useTheme()
 
-export default function search(theme: ColorScheme): any {
     // Search input
     const editor = {
         background: background(theme.highest),

styles/src/style_tree/shared_screen.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background } from "./components"
 
-export default function sharedScreen(theme: ColorScheme) {
+export default function sharedScreen() {
+    const theme = useTheme()
+
     return {
         background: background(theme.highest),
     }

styles/src/style_tree/simple_message_notification.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, foreground, text } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function simple_message_notification(): any {
+    const theme = useTheme()
 
-export default function simple_message_notification(theme: ColorScheme): any {
     const header_padding = 8
 
     return {

styles/src/style_tree/status_bar.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, foreground, text } from "./components"
 import { interactive, toggleable } from "../element"
-export default function status_bar(theme: ColorScheme): any {
+import { useTheme } from "../common"
+export default function status_bar(): any {
+    const theme = useTheme()
+
     const layer = theme.lowest
 
     const status_container = {

styles/src/style_tree/tab_bar.ts 🔗

@@ -1,9 +1,11 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import { text, border, background, foreground } from "./components"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../common"
+
+export default function tab_bar(): any {
+    const theme = useTheme()
 
-export default function tab_bar(theme: ColorScheme): any {
     const height = 32
 
     const active_layer = theme.highest

styles/src/style_tree/terminal.ts 🔗

@@ -1,6 +1,8 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
+
+export default function terminal() {
+    const theme = useTheme()
 
-export default function terminal(theme: ColorScheme) {
     /**
      * Colors are controlled per-cell in the terminal grid.
      * Cells can be set to any of these more 'theme-capable' colors

styles/src/style_tree/titlebar.ts 🔗

@@ -1,7 +1,7 @@
-import { ColorScheme } from "../common"
 import { icon_button, toggleable_icon_button } from "../component/icon_button"
 import { toggleable_text_button } from "../component/text_button"
 import { interactive, toggleable } from "../element"
+import { useTheme } from "../theme"
 import { with_opacity } from "../theme/color"
 import { background, border, foreground, text } from "./components"
 
@@ -22,7 +22,9 @@ function build_spacing(
     }
 }
 
-function call_controls(theme: ColorScheme) {
+function call_controls() {
+    const theme = useTheme()
+
     const button_height = 18
 
     const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
@@ -69,7 +71,9 @@ function call_controls(theme: ColorScheme) {
  * When logged in shows the user's avatar and a chevron,
  * When logged out only shows a chevron.
  */
-function user_menu(theme: ColorScheme) {
+function user_menu() {
+    const theme = useTheme()
+
     const button_height = 18
 
     const space = build_spacing(TITLEBAR_HEIGHT, button_height, ITEM_SPACING)
@@ -155,7 +159,9 @@ function user_menu(theme: ColorScheme) {
     }
 }
 
-export function titlebar(theme: ColorScheme): any {
+export function titlebar(): any {
+    const theme = useTheme()
+
     const avatar_width = 15
     const avatar_outer_width = avatar_width + 4
     const follower_avatar_width = 14
@@ -237,14 +243,14 @@ export function titlebar(theme: ColorScheme): any {
             corner_radius: 6,
         },
 
-        leave_call_button: icon_button(theme, {
+        leave_call_button: icon_button({
             margin: {
                 left: ITEM_SPACING / 2,
                 right: ITEM_SPACING,
             },
         }),
 
-        ...call_controls(theme),
+        ...call_controls(),
 
         toggle_contacts_button: toggleable_icon_button(theme, {
             margin: {
@@ -261,6 +267,6 @@ export function titlebar(theme: ColorScheme): any {
             background: foreground(theme.lowest, "accent"),
         },
         share_button: toggleable_text_button(theme, {}),
-        user_menu: user_menu(theme),
+        user_menu: user_menu(),
     }
 }

styles/src/style_tree/toolbar_dropdown_menu.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { background, border, text } from "./components"
 import { interactive, toggleable } from "../element"
-export default function dropdown_menu(theme: ColorScheme): any {
+import { useTheme } from "../theme"
+export default function dropdown_menu(): any {
+    const theme = useTheme()
+
     return {
         row_height: 30,
         background: background(theme.middle),

styles/src/style_tree/tooltip.ts 🔗

@@ -1,7 +1,9 @@
-import { ColorScheme } from "../theme/color_scheme"
+import { useTheme } from "../theme"
 import { background, border, text } from "./components"
 
-export default function tooltip(theme: ColorScheme): any {
+export default function tooltip(): any {
+    const theme = useTheme()
+
     return {
         background: background(theme.middle),
         border: border(theme.middle),

styles/src/style_tree/update_notification.ts 🔗

@@ -1,8 +1,10 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { foreground, text } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function update_notification(): any {
+    const theme = useTheme()
 
-export default function update_notification(theme: ColorScheme): any {
     const header_padding = 8
 
     return {

styles/src/style_tree/welcome.ts 🔗

@@ -1,4 +1,3 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import {
     border,
@@ -9,8 +8,11 @@ import {
     svg,
 } from "./components"
 import { interactive } from "../element"
+import { useTheme } from "../theme"
+
+export default function welcome(): any {
+    const theme = useTheme()
 
-export default function welcome(theme: ColorScheme): any {
     const checkbox_base = {
         corner_radius: 4,
         padding: {

styles/src/style_tree/workspace.ts 🔗

@@ -1,4 +1,3 @@
-import { ColorScheme } from "../theme/color_scheme"
 import { with_opacity } from "../theme/color"
 import {
     background,
@@ -11,9 +10,12 @@ import {
 import statusBar from "./status_bar"
 import tabBar from "./tab_bar"
 import { interactive } from "../element"
-
 import { titlebar } from "./titlebar"
-export default function workspace(theme: ColorScheme): any {
+import { useTheme } from "../theme"
+
+export default function workspace(): any {
+    const theme = useTheme()
+
     const { is_light } = theme
 
     return {
@@ -85,7 +87,7 @@ export default function workspace(theme: ColorScheme): any {
         },
         leader_border_opacity: 0.7,
         leader_border_width: 2.0,
-        tab_bar: tabBar(theme),
+        tab_bar: tabBar(),
         modal: {
             margin: {
                 bottom: 52,
@@ -123,8 +125,8 @@ export default function workspace(theme: ColorScheme): any {
             color: border_color(theme.lowest),
             width: 1,
         },
-        status_bar: statusBar(theme),
-        titlebar: titlebar(theme),
+        status_bar: statusBar(),
+        titlebar: titlebar(),
         toolbar: {
             height: 34,
             background: background(theme.highest),

styles/src/theme/index.ts 🔗

@@ -1,3 +1,24 @@
+import { create } from "zustand"
+import { ColorScheme } from "./color_scheme"
+
+type ThemeState = {
+    theme: ColorScheme | undefined
+    setTheme: (theme: ColorScheme) => void
+}
+
+export const useThemeStore = create<ThemeState>((set) => ({
+    theme: undefined,
+    setTheme: (theme) => set(() => ({ theme })),
+}))
+
+export const useTheme = (): ColorScheme => {
+    const { theme } = useThemeStore.getState()
+
+    if (!theme) throw new Error("Tried to use theme before it was loaded")
+
+    return theme
+}
+
 export * from "./color_scheme"
 export * from "./ramps"
 export * from "./syntax"