From 73372aa078a94b5945d50ab15c291468bf66184c Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Mon, 13 Oct 2025 20:26:13 +0300 Subject: [PATCH 1/3] refactor: enhance ThemeProvider with dynamic storage initialization --- src/components/Viewport/Viewport.stories.tsx | 4 +- src/providers/theme/ThemeProvider.tsx | 45 +++++++++----------- src/providers/theme/ThemeStorage.tsx | 13 ++++-- src/providers/theme/index.ts | 4 +- src/providers/ui/UIProvider.tsx | 33 ++++++++------ 5 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/components/Viewport/Viewport.stories.tsx b/src/components/Viewport/Viewport.stories.tsx index b2e2798..f7f7a5a 100644 --- a/src/components/Viewport/Viewport.stories.tsx +++ b/src/components/Viewport/Viewport.stories.tsx @@ -37,7 +37,7 @@ export const Viewport = () => { const App = () => { const [arr, setArr] = useState(Array.from(Array(5))); - const {setMode, resetSizes, setSizes} = useViewport(); + const {setMode, setSizes} = useViewport(); return (
{
diff --git a/src/providers/theme/ThemeProvider.tsx b/src/providers/theme/ThemeProvider.tsx index 1c05051..6a41698 100644 --- a/src/providers/theme/ThemeProvider.tsx +++ b/src/providers/theme/ThemeProvider.tsx @@ -1,11 +1,12 @@ -import React, {FC, PropsWithChildren, useCallback, useEffect, useState} from "react"; -import {getBrowser} from "adnbn"; +import React, {FC, PropsWithChildren, useCallback, useEffect, useMemo, useState} from "react"; import {ThemeContext} from "./context"; import {Theme, ThemeStorageContract} from "../../types/theme"; import {Config} from "../../types/config"; +import ThemeStorage from "./ThemeStorage"; + const isDarkMedia = () => window?.matchMedia("(prefers-color-scheme: dark)")?.matches; const isValid = (theme: Theme | undefined): theme is Theme => { @@ -13,22 +14,29 @@ const isValid = (theme: Theme | undefined): theme is Theme => { }; export interface ThemeProviderProps extends Pick { - storage?: ThemeStorageContract; - view?: string; + storage?: ThemeStorageContract | true; } -const ThemeProvider: FC> = ({children, components, storage, view}) => { +const ThemeProvider: FC> = ({children, components, storage}) => { const [theme, setTheme] = useState(() => (isDarkMedia() ? Theme.Dark : Theme.Light)); + const currentStorage: ThemeStorageContract | undefined = useMemo(() => { + if (!storage) return; + + if (storage === true) return new ThemeStorage(); + + return storage; + }, [storage]); + const changeTheme = useCallback( (theme: Theme) => { - if (storage) { - storage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); + if (currentStorage) { + currentStorage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); } else { setTheme(theme); } }, - [storage] + [currentStorage] ); const toggleTheme = useCallback(() => { @@ -36,31 +44,20 @@ const ThemeProvider: FC> = ({children, com }, [theme, changeTheme]); useEffect(() => { - if (!storage) return; + if (!currentStorage) return; - storage + currentStorage .get() .then(newTheme => isValid(newTheme) && setTheme(newTheme)) .catch(e => console.error("ThemeProvider: get theme from storage error", e)); - const unsubscribe = storage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); + const unsubscribe = currentStorage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); return () => unsubscribe(); - }, [storage]); + }, [currentStorage]); useEffect(() => { - const html = document.querySelector("html"); - if (html) { - html.setAttribute("theme", theme); - - view && html.setAttribute("view", view); - - try { - html.setAttribute("browser", getBrowser()); - } catch (e) { - console.error("ThemeProvider: get browser error", e); - } - } + document.querySelector("html")?.setAttribute("theme", theme); }, [theme]); return ( diff --git a/src/providers/theme/ThemeStorage.tsx b/src/providers/theme/ThemeStorage.tsx index 2f8a769..af89dea 100644 --- a/src/providers/theme/ThemeStorage.tsx +++ b/src/providers/theme/ThemeStorage.tsx @@ -1,14 +1,21 @@ -import {Storage} from "@addon-core/storage"; +import {Storage, StorageProvider} from "@addon-core/storage"; import {Theme, ThemeStorageContract} from "../../types/theme"; +export type ThemeStorageState = Record; + export default class implements ThemeStorageContract { - private readonly storage = new Storage>({ + protected storage: StorageProvider = new Storage({ area: "local", namespace: "addon-ui", }); - private readonly key = "theme"; + protected key: string = "theme"; + + constructor(storage?: StorageProvider, key?: string) { + this.storage = storage ? storage : this.storage; + this.key = key ? key : this.key; + } public async get(): Promise { return await this.storage.get(this.key); diff --git a/src/providers/theme/index.ts b/src/providers/theme/index.ts index 2a96847..01ce551 100644 --- a/src/providers/theme/index.ts +++ b/src/providers/theme/index.ts @@ -1,3 +1,3 @@ -export {default as ThemeProvider} from "./ThemeProvider"; -export {default as ThemeStorage} from "./ThemeStorage"; +export {default as ThemeProvider, type ThemeProviderProps} from "./ThemeProvider"; +export {default as ThemeStorage, type ThemeStorageState} from "./ThemeStorage"; export {useTheme, useComponentProps} from "./context"; diff --git a/src/providers/ui/UIProvider.tsx b/src/providers/ui/UIProvider.tsx index 7ced52b..3a56daa 100644 --- a/src/providers/ui/UIProvider.tsx +++ b/src/providers/ui/UIProvider.tsx @@ -1,9 +1,12 @@ -import React, {FC, PropsWithChildren, useMemo, useRef} from "react"; +import React, {FC, PropsWithChildren, useEffect, useMemo} from "react"; +import {getBrowser} from "adnbn"; + import {merge} from "ts-deepmerge"; -import {ExtraProvider, IconsProvider, ThemeProvider, ThemeStorage} from "../index"; +import {ExtraProvider} from "../extra"; +import {IconsProvider} from "../icons"; +import {ThemeProvider, ThemeProviderProps} from "../theme"; -import {ThemeStorageContract} from "../../types/theme"; import {ComponentsProps, Config, ExtraProps, Icons} from "../../types/config"; import "./styles/default.scss"; @@ -12,9 +15,9 @@ import "addon-ui-style.scss"; import config from "addon-ui-config"; -export type UIProviderProps = Partial & { - view?: string -}; +export interface UIProviderProps extends Partial, Pick { + view?: string; +} const UIProvider: FC> = ( { @@ -22,13 +25,9 @@ const UIProvider: FC> = ( components = {}, extra = {}, icons = {}, + storage, view }) => { - const storageRef = useRef(null); - - if (!storageRef.current) { - storageRef.current = new ThemeStorage(); - } const componentsProps = useMemo(() => merge(config.components || {}, components), [components]); @@ -36,8 +35,18 @@ const UIProvider: FC> = ( const svgIcons = useMemo(() => merge(config.icons || {}, icons), [icons]); + useEffect(() => { + const html = document.querySelector("html"); + if (html) { + if (view) { + html.setAttribute("view", view); + } + html.setAttribute("browser", getBrowser()); + } + }, [view]); + return ( - + {children} From 4a32156ce892bc5262590733f8678fcd8693dc62 Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Tue, 14 Oct 2025 00:42:51 +0300 Subject: [PATCH 2/3] Revert "refactor: enhance ThemeProvider with dynamic storage initialization" This reverts commit 73372aa078a94b5945d50ab15c291468bf66184c. --- src/components/Viewport/Viewport.stories.tsx | 4 +- src/providers/theme/ThemeProvider.tsx | 45 +++++++++++--------- src/providers/theme/ThemeStorage.tsx | 13 ++---- src/providers/theme/index.ts | 4 +- src/providers/ui/UIProvider.tsx | 33 ++++++-------- 5 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/components/Viewport/Viewport.stories.tsx b/src/components/Viewport/Viewport.stories.tsx index f7f7a5a..b2e2798 100644 --- a/src/components/Viewport/Viewport.stories.tsx +++ b/src/components/Viewport/Viewport.stories.tsx @@ -37,7 +37,7 @@ export const Viewport = () => { const App = () => { const [arr, setArr] = useState(Array.from(Array(5))); - const {setMode, setSizes} = useViewport(); + const {setMode, resetSizes, setSizes} = useViewport(); return (
{
diff --git a/src/providers/theme/ThemeProvider.tsx b/src/providers/theme/ThemeProvider.tsx index 6a41698..1c05051 100644 --- a/src/providers/theme/ThemeProvider.tsx +++ b/src/providers/theme/ThemeProvider.tsx @@ -1,12 +1,11 @@ -import React, {FC, PropsWithChildren, useCallback, useEffect, useMemo, useState} from "react"; +import React, {FC, PropsWithChildren, useCallback, useEffect, useState} from "react"; +import {getBrowser} from "adnbn"; import {ThemeContext} from "./context"; import {Theme, ThemeStorageContract} from "../../types/theme"; import {Config} from "../../types/config"; -import ThemeStorage from "./ThemeStorage"; - const isDarkMedia = () => window?.matchMedia("(prefers-color-scheme: dark)")?.matches; const isValid = (theme: Theme | undefined): theme is Theme => { @@ -14,29 +13,22 @@ const isValid = (theme: Theme | undefined): theme is Theme => { }; export interface ThemeProviderProps extends Pick { - storage?: ThemeStorageContract | true; + storage?: ThemeStorageContract; + view?: string; } -const ThemeProvider: FC> = ({children, components, storage}) => { +const ThemeProvider: FC> = ({children, components, storage, view}) => { const [theme, setTheme] = useState(() => (isDarkMedia() ? Theme.Dark : Theme.Light)); - const currentStorage: ThemeStorageContract | undefined = useMemo(() => { - if (!storage) return; - - if (storage === true) return new ThemeStorage(); - - return storage; - }, [storage]); - const changeTheme = useCallback( (theme: Theme) => { - if (currentStorage) { - currentStorage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); + if (storage) { + storage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); } else { setTheme(theme); } }, - [currentStorage] + [storage] ); const toggleTheme = useCallback(() => { @@ -44,20 +36,31 @@ const ThemeProvider: FC> = ({children, com }, [theme, changeTheme]); useEffect(() => { - if (!currentStorage) return; + if (!storage) return; - currentStorage + storage .get() .then(newTheme => isValid(newTheme) && setTheme(newTheme)) .catch(e => console.error("ThemeProvider: get theme from storage error", e)); - const unsubscribe = currentStorage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); + const unsubscribe = storage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); return () => unsubscribe(); - }, [currentStorage]); + }, [storage]); useEffect(() => { - document.querySelector("html")?.setAttribute("theme", theme); + const html = document.querySelector("html"); + if (html) { + html.setAttribute("theme", theme); + + view && html.setAttribute("view", view); + + try { + html.setAttribute("browser", getBrowser()); + } catch (e) { + console.error("ThemeProvider: get browser error", e); + } + } }, [theme]); return ( diff --git a/src/providers/theme/ThemeStorage.tsx b/src/providers/theme/ThemeStorage.tsx index af89dea..2f8a769 100644 --- a/src/providers/theme/ThemeStorage.tsx +++ b/src/providers/theme/ThemeStorage.tsx @@ -1,21 +1,14 @@ -import {Storage, StorageProvider} from "@addon-core/storage"; +import {Storage} from "@addon-core/storage"; import {Theme, ThemeStorageContract} from "../../types/theme"; -export type ThemeStorageState = Record; - export default class implements ThemeStorageContract { - protected storage: StorageProvider = new Storage({ + private readonly storage = new Storage>({ area: "local", namespace: "addon-ui", }); - protected key: string = "theme"; - - constructor(storage?: StorageProvider, key?: string) { - this.storage = storage ? storage : this.storage; - this.key = key ? key : this.key; - } + private readonly key = "theme"; public async get(): Promise { return await this.storage.get(this.key); diff --git a/src/providers/theme/index.ts b/src/providers/theme/index.ts index 01ce551..2a96847 100644 --- a/src/providers/theme/index.ts +++ b/src/providers/theme/index.ts @@ -1,3 +1,3 @@ -export {default as ThemeProvider, type ThemeProviderProps} from "./ThemeProvider"; -export {default as ThemeStorage, type ThemeStorageState} from "./ThemeStorage"; +export {default as ThemeProvider} from "./ThemeProvider"; +export {default as ThemeStorage} from "./ThemeStorage"; export {useTheme, useComponentProps} from "./context"; diff --git a/src/providers/ui/UIProvider.tsx b/src/providers/ui/UIProvider.tsx index 3a56daa..7ced52b 100644 --- a/src/providers/ui/UIProvider.tsx +++ b/src/providers/ui/UIProvider.tsx @@ -1,12 +1,9 @@ -import React, {FC, PropsWithChildren, useEffect, useMemo} from "react"; -import {getBrowser} from "adnbn"; - +import React, {FC, PropsWithChildren, useMemo, useRef} from "react"; import {merge} from "ts-deepmerge"; -import {ExtraProvider} from "../extra"; -import {IconsProvider} from "../icons"; -import {ThemeProvider, ThemeProviderProps} from "../theme"; +import {ExtraProvider, IconsProvider, ThemeProvider, ThemeStorage} from "../index"; +import {ThemeStorageContract} from "../../types/theme"; import {ComponentsProps, Config, ExtraProps, Icons} from "../../types/config"; import "./styles/default.scss"; @@ -15,9 +12,9 @@ import "addon-ui-style.scss"; import config from "addon-ui-config"; -export interface UIProviderProps extends Partial, Pick { - view?: string; -} +export type UIProviderProps = Partial & { + view?: string +}; const UIProvider: FC> = ( { @@ -25,9 +22,13 @@ const UIProvider: FC> = ( components = {}, extra = {}, icons = {}, - storage, view }) => { + const storageRef = useRef(null); + + if (!storageRef.current) { + storageRef.current = new ThemeStorage(); + } const componentsProps = useMemo(() => merge(config.components || {}, components), [components]); @@ -35,18 +36,8 @@ const UIProvider: FC> = ( const svgIcons = useMemo(() => merge(config.icons || {}, icons), [icons]); - useEffect(() => { - const html = document.querySelector("html"); - if (html) { - if (view) { - html.setAttribute("view", view); - } - html.setAttribute("browser", getBrowser()); - } - }, [view]); - return ( - + {children} From 3e2ecac11f066d3bc4aaca2439ba45356fc88213 Mon Sep 17 00:00:00 2001 From: Rostyslav Nihrutsa Date: Tue, 14 Oct 2025 01:05:43 +0300 Subject: [PATCH 3/3] refactor: enhance ThemeProvider with dynamic storage initialization --- src/components/Viewport/Viewport.stories.tsx | 4 +- src/plugin/index.ts | 2 +- src/providers/theme/ThemeProvider.tsx | 45 +++++++++----------- src/providers/theme/ThemeStorage.tsx | 13 ++++-- src/providers/theme/index.ts | 4 +- src/providers/ui/UIProvider.tsx | 31 +++++++++----- 6 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/components/Viewport/Viewport.stories.tsx b/src/components/Viewport/Viewport.stories.tsx index 66fb6a7..36ffddf 100644 --- a/src/components/Viewport/Viewport.stories.tsx +++ b/src/components/Viewport/Viewport.stories.tsx @@ -37,7 +37,7 @@ export const Viewport = () => { const App = () => { const [arr, setArr] = useState(Array.from(Array(5))); - const {setMode, resetSizes, setSizes} = useViewport(); + const {setMode, setSizes} = useViewport(); return (
{
diff --git a/src/plugin/index.ts b/src/plugin/index.ts index 5ba2e11..3383d12 100644 --- a/src/plugin/index.ts +++ b/src/plugin/index.ts @@ -36,7 +36,7 @@ export default definePlugin((options: PluginOptions = {}) => { let styleBuilder: BuilderContract; return { - name: "adnbn-ui", + name: "addon-ui", startup: ({config}) => { const {srcDir, appsDir, sharedDir, app, appSrcDir} = config; const normalizeThemeDir = path.normalize(themeDir).split(path.sep); diff --git a/src/providers/theme/ThemeProvider.tsx b/src/providers/theme/ThemeProvider.tsx index 1c05051..6a41698 100644 --- a/src/providers/theme/ThemeProvider.tsx +++ b/src/providers/theme/ThemeProvider.tsx @@ -1,11 +1,12 @@ -import React, {FC, PropsWithChildren, useCallback, useEffect, useState} from "react"; -import {getBrowser} from "adnbn"; +import React, {FC, PropsWithChildren, useCallback, useEffect, useMemo, useState} from "react"; import {ThemeContext} from "./context"; import {Theme, ThemeStorageContract} from "../../types/theme"; import {Config} from "../../types/config"; +import ThemeStorage from "./ThemeStorage"; + const isDarkMedia = () => window?.matchMedia("(prefers-color-scheme: dark)")?.matches; const isValid = (theme: Theme | undefined): theme is Theme => { @@ -13,22 +14,29 @@ const isValid = (theme: Theme | undefined): theme is Theme => { }; export interface ThemeProviderProps extends Pick { - storage?: ThemeStorageContract; - view?: string; + storage?: ThemeStorageContract | true; } -const ThemeProvider: FC> = ({children, components, storage, view}) => { +const ThemeProvider: FC> = ({children, components, storage}) => { const [theme, setTheme] = useState(() => (isDarkMedia() ? Theme.Dark : Theme.Light)); + const currentStorage: ThemeStorageContract | undefined = useMemo(() => { + if (!storage) return; + + if (storage === true) return new ThemeStorage(); + + return storage; + }, [storage]); + const changeTheme = useCallback( (theme: Theme) => { - if (storage) { - storage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); + if (currentStorage) { + currentStorage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e)); } else { setTheme(theme); } }, - [storage] + [currentStorage] ); const toggleTheme = useCallback(() => { @@ -36,31 +44,20 @@ const ThemeProvider: FC> = ({children, com }, [theme, changeTheme]); useEffect(() => { - if (!storage) return; + if (!currentStorage) return; - storage + currentStorage .get() .then(newTheme => isValid(newTheme) && setTheme(newTheme)) .catch(e => console.error("ThemeProvider: get theme from storage error", e)); - const unsubscribe = storage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); + const unsubscribe = currentStorage.watch(newTheme => isValid(newTheme) && setTheme(newTheme)); return () => unsubscribe(); - }, [storage]); + }, [currentStorage]); useEffect(() => { - const html = document.querySelector("html"); - if (html) { - html.setAttribute("theme", theme); - - view && html.setAttribute("view", view); - - try { - html.setAttribute("browser", getBrowser()); - } catch (e) { - console.error("ThemeProvider: get browser error", e); - } - } + document.querySelector("html")?.setAttribute("theme", theme); }, [theme]); return ( diff --git a/src/providers/theme/ThemeStorage.tsx b/src/providers/theme/ThemeStorage.tsx index 2f8a769..af89dea 100644 --- a/src/providers/theme/ThemeStorage.tsx +++ b/src/providers/theme/ThemeStorage.tsx @@ -1,14 +1,21 @@ -import {Storage} from "@addon-core/storage"; +import {Storage, StorageProvider} from "@addon-core/storage"; import {Theme, ThemeStorageContract} from "../../types/theme"; +export type ThemeStorageState = Record; + export default class implements ThemeStorageContract { - private readonly storage = new Storage>({ + protected storage: StorageProvider = new Storage({ area: "local", namespace: "addon-ui", }); - private readonly key = "theme"; + protected key: string = "theme"; + + constructor(storage?: StorageProvider, key?: string) { + this.storage = storage ? storage : this.storage; + this.key = key ? key : this.key; + } public async get(): Promise { return await this.storage.get(this.key); diff --git a/src/providers/theme/index.ts b/src/providers/theme/index.ts index 2a96847..01ce551 100644 --- a/src/providers/theme/index.ts +++ b/src/providers/theme/index.ts @@ -1,3 +1,3 @@ -export {default as ThemeProvider} from "./ThemeProvider"; -export {default as ThemeStorage} from "./ThemeStorage"; +export {default as ThemeProvider, type ThemeProviderProps} from "./ThemeProvider"; +export {default as ThemeStorage, type ThemeStorageState} from "./ThemeStorage"; export {useTheme, useComponentProps} from "./context"; diff --git a/src/providers/ui/UIProvider.tsx b/src/providers/ui/UIProvider.tsx index 4194c5b..f6e633d 100644 --- a/src/providers/ui/UIProvider.tsx +++ b/src/providers/ui/UIProvider.tsx @@ -1,9 +1,12 @@ -import React, {FC, PropsWithChildren, useMemo, useRef} from "react"; +import React, {FC, PropsWithChildren, useEffect, useMemo} from "react"; +import {getBrowser} from "adnbn"; + import {merge} from "ts-deepmerge"; -import {ExtraProvider, IconsProvider, ThemeProvider, ThemeStorage} from "../index"; +import {ExtraProvider} from "../extra"; +import {IconsProvider} from "../icons"; +import {ThemeProvider, ThemeProviderProps} from "../theme"; -import {ThemeStorageContract} from "../../types/theme"; import {ComponentsProps, Config, ExtraProps, Icons} from "../../types/config"; import "./styles/default.scss"; @@ -12,22 +15,18 @@ import "addon-ui-style.scss"; import config from "addon-ui-config"; -export type UIProviderProps = Partial & { +export interface UIProviderProps extends Partial, Pick { view?: string; -}; +} const UIProvider: FC> = ({ children, components = {}, extra = {}, icons = {}, + storage, view, }) => { - const storageRef = useRef(null); - - if (!storageRef.current) { - storageRef.current = new ThemeStorage(); - } const componentsProps = useMemo(() => merge(config.components || {}, components), [components]); @@ -35,8 +34,18 @@ const UIProvider: FC> = ({ const svgIcons = useMemo(() => merge(config.icons || {}, icons), [icons]); + useEffect(() => { + const html = document.querySelector("html"); + if (html) { + if (view) { + html.setAttribute("view", view); + } + html.setAttribute("browser", getBrowser()); + } + }, [view]); + return ( - + {children}