Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/Viewport/Viewport.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div
Expand Down Expand Up @@ -76,7 +76,7 @@ const App = () => {
<Button onClick={() => setSizes({width: 500})}>500px width</Button>
</div>
<ScrollArea
xOffset={10}
xOffset={2}
type="always"
style={{flex: 1, display: "flex", flexDirection: "column", overflow: "hidden"}}
>
Expand Down
2 changes: 1 addition & 1 deletion src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
45 changes: 21 additions & 24 deletions src/providers/theme/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,63 @@
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 => {
return !!theme && [Theme.Light, Theme.Dark].includes(theme);
};

export interface ThemeProviderProps extends Pick<Config, "components"> {
storage?: ThemeStorageContract;
view?: string;
storage?: ThemeStorageContract | true;
}

const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({children, components, storage, view}) => {
const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({children, components, storage}) => {
const [theme, setTheme] = useState<Theme>(() => (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(() => {
changeTheme(theme === Theme.Dark ? Theme.Light : Theme.Dark);
}, [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 (
Expand Down
13 changes: 10 additions & 3 deletions src/providers/theme/ThemeStorage.tsx
Original file line number Diff line number Diff line change
@@ -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<string, Theme>;

export default class implements ThemeStorageContract {
private readonly storage = new Storage<Record<string, Theme>>({
protected storage: StorageProvider<ThemeStorageState> = new Storage<ThemeStorageState>({
area: "local",
namespace: "addon-ui",
});

private readonly key = "theme";
protected key: string = "theme";

constructor(storage?: StorageProvider<ThemeStorageState>, key?: string) {
this.storage = storage ? storage : this.storage;
this.key = key ? key : this.key;
}

public async get(): Promise<Theme | undefined> {
return await this.storage.get(this.key);
Expand Down
4 changes: 2 additions & 2 deletions src/providers/theme/index.ts
Original file line number Diff line number Diff line change
@@ -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";
31 changes: 20 additions & 11 deletions src/providers/ui/UIProvider.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -12,31 +15,37 @@ import "addon-ui-style.scss";

import config from "addon-ui-config";

export type UIProviderProps = Partial<Config> & {
export interface UIProviderProps extends Partial<Config>, Pick<ThemeProviderProps, 'storage'> {
view?: string;
};
}

const UIProvider: FC<PropsWithChildren<UIProviderProps>> = ({
children,
components = {},
extra = {},
icons = {},
storage,
view,
}) => {
const storageRef = useRef<ThemeStorageContract | null>(null);

if (!storageRef.current) {
storageRef.current = new ThemeStorage();
}

const componentsProps = useMemo<ComponentsProps>(() => merge(config.components || {}, components), [components]);

const extraProps = useMemo<ExtraProps>(() => merge(config.extra || {}, extra), [extra]);

const svgIcons = useMemo<Icons>(() => 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 (
<ThemeProvider components={componentsProps} storage={storageRef.current} view={view}>
<ThemeProvider components={componentsProps} storage={storage} >
<ExtraProvider extra={extraProps}>
<IconsProvider icons={svgIcons}>{children}</IconsProvider>
</ExtraProvider>
Expand Down