From bbbdaff3807533508014c2d50674c753d1602c14 Mon Sep 17 00:00:00 2001 From: Oleg Isonen Date: Thu, 7 Jul 2022 09:46:48 +0200 Subject: [PATCH 01/19] wip theme switcher --- apps/designer/app/designer/designer.tsx | 2 + .../designer/features/topbar/menu/menu.tsx | 55 ++++++++++++++++++- .../designer/shared/client-settings/index.ts | 1 + .../shared/client-settings/settings.ts | 49 +++++++++++++++++ .../designer/shared/client-settings/values.ts | 4 ++ .../components/dropdown-menu.tsx | 6 ++ .../shared/design-system/components/theme.tsx | 30 ++++++++++ .../app/shared/design-system/index.ts | 1 + .../app/shared/documents/designer.tsx | 5 +- apps/designer/tsconfig.json | 13 ++++- 10 files changed, 161 insertions(+), 5 deletions(-) create mode 100644 apps/designer/app/designer/shared/client-settings/index.ts create mode 100644 apps/designer/app/designer/shared/client-settings/settings.ts create mode 100644 apps/designer/app/designer/shared/client-settings/values.ts create mode 100644 apps/designer/app/shared/design-system/components/theme.tsx diff --git a/apps/designer/app/designer/designer.tsx b/apps/designer/app/designer/designer.tsx index f2814f61fb..30d637e1c4 100644 --- a/apps/designer/app/designer/designer.tsx +++ b/apps/designer/app/designer/designer.tsx @@ -32,6 +32,7 @@ import { import { usePublishShortcuts } from "./shared/shortcuts"; import { type SyncStatus } from "~/shared/sync"; import { useIsPreviewMode, useRootInstance } from "~/shared/nano-states"; +import { useSubscribeClientSetting } from "./shared/client-settings"; export const links = () => { return [ @@ -169,6 +170,7 @@ export const Designer = ({ config, project }: DesignerProps) => { useSubscribeSelectedInstanceData(); useSubscribeHoveredInstanceData(); useSubscribeBreakpoints(); + useSubscribeClientSetting(); const [publish, publishRef] = usePublish(); const [isPreviewMode] = useIsPreviewMode(); const [isDragging, setIsDragging] = useIsDragging(); diff --git a/apps/designer/app/designer/features/topbar/menu/menu.tsx b/apps/designer/app/designer/features/topbar/menu/menu.tsx index 2c5b030ece..68659e753f 100644 --- a/apps/designer/app/designer/features/topbar/menu/menu.tsx +++ b/apps/designer/app/designer/features/topbar/menu/menu.tsx @@ -1,22 +1,26 @@ import { useNavigate } from "react-router-dom"; -import { type Publish } from "@webstudio-is/sdk"; +import { publish, type Publish } from "@webstudio-is/sdk"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, + DropdownMenuTriggerItem, + DropdownMenuCheckboxItem, DropdownMenuArrow, DropdownMenuSeparator, IconButton, Text, } from "~/shared/design-system"; -import { HamburgerMenuIcon } from "~/shared/icons"; +import { HamburgerMenuIcon, ChevronRightIcon } from "~/shared/icons"; import type { Config } from "~/config"; import { ShortcutHint } from "./shortcut-hint"; import { useIsShareDialogOpen, useIsPublishDialogOpen, } from "~/designer/shared/nano-states"; +import { feature } from "~/shared/feature-flags"; +import { getSetting } from "~/designer/shared/client-settings"; const menuItemCss = { display: "flex", @@ -31,6 +35,52 @@ const textCss = { fontSize: "$1", }; +const ThemeMenuItem = () => { + if (feature("theme") === false) return null; + + const currentTheme = getSetting("theme"); + + const publishThemeChange = (value: string) => { + publish({ + type: "setClientSetting", + payload: { + name: "theme", + value, + }, + }); + }; + + const labels = { + light: "Light", + dark: "Dark", + system: "System theme", + } as const; + const themes = Object.keys(labels) as Array; + + return ( + + + Theme + + + + {themes.map((theme) => ( + { + publishThemeChange(theme); + }} + > + {labels[theme]} + + ))} + + + ); +}; + type MenuProps = { config: Config; publish: Publish; @@ -154,6 +204,7 @@ export const Menu = ({ config, publish }: MenuProps) => { + { diff --git a/apps/designer/app/designer/shared/client-settings/index.ts b/apps/designer/app/designer/shared/client-settings/index.ts new file mode 100644 index 0000000000..dcf101b0c6 --- /dev/null +++ b/apps/designer/app/designer/shared/client-settings/index.ts @@ -0,0 +1 @@ +export * from "./settings"; diff --git a/apps/designer/app/designer/shared/client-settings/settings.ts b/apps/designer/app/designer/shared/client-settings/settings.ts new file mode 100644 index 0000000000..049fb4f349 --- /dev/null +++ b/apps/designer/app/designer/shared/client-settings/settings.ts @@ -0,0 +1,49 @@ +import * as values from "./values"; +import { useSubscribe } from "@webstudio-is/sdk"; + +type Name = keyof typeof values; +type Value = typeof values[Name]["values"][number]; +type Settings = Partial>; + +const namespace = "__webstudio_user_settings__"; + +const read = (): Settings => { + try { + const settings = localStorage.getItem(namespace); + if (settings === null) return {}; + return JSON.parse(settings); + } catch (error) {} + return {}; +}; + +const write = (settings: Settings) => { + localStorage.setItem(namespace, JSON.stringify(settings)); +}; + +/** + * Get a value from local storage or a default. + */ +export const getSetting = (name: Name) => { + const settings = read(); + const validValues = values[name].values; + const value = settings[name]; + const isValidValue = value !== undefined && validValues.includes(value); + if (isValidValue) return value; + return values[name].defaultValue; +}; + +export const setSetting = (name: Name, value: Value) => { + const settings = read(); + const validValues = values[name].values; + const isValidValue = validValues.includes(value); + if (isValidValue) write({ ...settings, [name]: value }); +}; + +export const useSubscribeClientSetting = (name: Name) => { + useSubscribe<"setClientSetting", { name: Name; value: Value }>( + "setClientSetting", + (payload) => { + setSetting(payload.name, payload.value); + } + ); +}; diff --git a/apps/designer/app/designer/shared/client-settings/values.ts b/apps/designer/app/designer/shared/client-settings/values.ts new file mode 100644 index 0000000000..6baca6b084 --- /dev/null +++ b/apps/designer/app/designer/shared/client-settings/values.ts @@ -0,0 +1,4 @@ +export const theme = { + defaultValue: "system", + values: ["dark", "light", "system"], +} as const; diff --git a/apps/designer/app/shared/design-system/components/dropdown-menu.tsx b/apps/designer/app/shared/design-system/components/dropdown-menu.tsx index 458e3975bc..6e5b38d3c1 100644 --- a/apps/designer/app/shared/design-system/components/dropdown-menu.tsx +++ b/apps/designer/app/shared/design-system/components/dropdown-menu.tsx @@ -12,6 +12,12 @@ export const DropdownMenuArrow = styled(DropdownMenuPrimitive.Arrow, { fill: "$panel", }); export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +export const DropdownMenuTriggerItem = styled( + DropdownMenuPrimitive.TriggerItem, + itemCss +); + export const DropdownMenuContent = styled( DropdownMenuPrimitive.Content, menuCss, diff --git a/apps/designer/app/shared/design-system/components/theme.tsx b/apps/designer/app/shared/design-system/components/theme.tsx new file mode 100644 index 0000000000..f12f2a00be --- /dev/null +++ b/apps/designer/app/shared/design-system/components/theme.tsx @@ -0,0 +1,30 @@ +import { getSetting } from "~/designer/shared/client-settings"; +import { darkTheme } from "../stitches.config"; + +const getTheme = () => { + const theme = getSetting("theme"); + if (theme === "dark") return darkTheme.toString(); + return "light-theme"; + // @todo handle system theme +}; + +export const Theme = () => { + globalThis.theme = getTheme(); + console.log(1111, globalThis.theme); + + const code = ` + !(function () { + setTimeout(function() { + console.log(222, globalThis.theme) + document.documentElement.classList.add(globalThis.theme) + }); + })(); + `; + return ( +