addon-ui 0.3.1 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +110 -838
- package/package.json +31 -8
- package/src/components/Dialog/dialog.module.scss +3 -0
- package/src/components/Footer/footer.module.scss +24 -0
- package/src/components/ListItem/list-item.module.scss +4 -0
- package/src/components/Modal/Modal.tsx +4 -1
- package/src/components/Modal/modal.module.scss +42 -5
- package/src/components/Modal/types.ts +5 -0
- package/src/components/TextField/text-field.module.scss +1 -0
- package/src/components/Viewport/Provider.tsx +99 -0
- package/src/components/Viewport/context.ts +38 -0
- package/src/components/Viewport/index.ts +2 -0
- package/src/components/Viewport/viewport.module.scss +31 -0
- package/src/components/index.ts +1 -1
- package/src/plugin/index.ts +1 -1
- package/src/providers/theme/ThemeProvider.tsx +19 -9
- package/src/providers/theme/ThemeStorage.tsx +11 -4
- package/src/providers/theme/index.ts +2 -2
- package/src/providers/ui/UIProvider.tsx +29 -13
- package/src/providers/ui/styles/default.scss +7 -4
- package/src/styles/mixins.scss +30 -0
- package/src/components/Layout/Provider.tsx +0 -60
- package/src/components/Layout/context.ts +0 -24
- package/src/components/Layout/index.ts +0 -2
- package/src/components/Layout/layout.module.scss +0 -17
package/package.json
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "addon-ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"description": "A comprehensive React UI component library designed exclusively for the AddonBone browser extension framework with customizable theming and consistent design patterns",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"react",
|
|
8
8
|
"ui",
|
|
9
9
|
"components",
|
|
10
10
|
"adnbn",
|
|
11
|
-
"
|
|
11
|
+
"addon-bone",
|
|
12
12
|
"browser-extension",
|
|
13
13
|
"theme",
|
|
14
14
|
"customizable",
|
|
15
15
|
"typescript",
|
|
16
16
|
"design-system"
|
|
17
17
|
],
|
|
18
|
-
"author": "Addon Stack",
|
|
18
|
+
"author": "Addon Stack <addonbonedev@gmail.com>",
|
|
19
19
|
"contributors": [
|
|
20
|
-
"Anjey Tsibylskij
|
|
20
|
+
"Anjey Tsibylskij (https://github.com/atldays)"
|
|
21
21
|
],
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "https://github.com/addon-stack/addon-ui
|
|
25
|
+
"url": "https://github.com/addon-stack/addon-ui"
|
|
26
26
|
},
|
|
27
27
|
"bugs": {
|
|
28
28
|
"url": "https://github.com/addon-stack/addon-ui/issues"
|
|
@@ -55,7 +55,13 @@
|
|
|
55
55
|
"lint": "eslint .",
|
|
56
56
|
"format": "prettier --write .",
|
|
57
57
|
"storybook": "storybook dev -p 6006",
|
|
58
|
-
"build-storybook": "storybook build"
|
|
58
|
+
"build-storybook": "storybook build",
|
|
59
|
+
"test": "jest",
|
|
60
|
+
"test:ci": "jest --ci --passWithNoTests --coverage",
|
|
61
|
+
"test:related": "jest --bail --passWithNoTests",
|
|
62
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
63
|
+
"release": "release-it",
|
|
64
|
+
"release:preview": "release-it --no-github.release --no-npm.publish --no-git.tag --ci"
|
|
59
65
|
},
|
|
60
66
|
"dependencies": {
|
|
61
67
|
"autosize": "^6.0.1",
|
|
@@ -67,6 +73,11 @@
|
|
|
67
73
|
"ts-deepmerge": "^7.0.3"
|
|
68
74
|
},
|
|
69
75
|
"devDependencies": {
|
|
76
|
+
"@commitlint/cli": "^20.0.0",
|
|
77
|
+
"@commitlint/config-conventional": "^20.0.0",
|
|
78
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
79
|
+
"@types/chrome": "^0.1.12",
|
|
80
|
+
"@types/jest": "^30.0.0",
|
|
70
81
|
"@eslint/js": "^9.21.0",
|
|
71
82
|
"@rsbuild/plugin-sass": "^1.4.0",
|
|
72
83
|
"@storybook/react": "^9.1.3",
|
|
@@ -75,13 +86,16 @@
|
|
|
75
86
|
"@types/react": "^19.0.10",
|
|
76
87
|
"@types/react-dom": "^19.0.4",
|
|
77
88
|
"@types/react-highlight-words": "^0.20.0",
|
|
78
|
-
"adnbn": "^0.2
|
|
89
|
+
"adnbn": "^0.4.2",
|
|
79
90
|
"depcheck": "^1.4.7",
|
|
80
91
|
"eslint": "^9.21.0",
|
|
81
92
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
82
93
|
"eslint-plugin-react-refresh": "^0.4.19",
|
|
83
94
|
"globals": "^15.15.0",
|
|
84
95
|
"prettier": "^3.5.3",
|
|
96
|
+
"husky": "^9.1.7",
|
|
97
|
+
"jest": "^30.1.3",
|
|
98
|
+
"release-it": "^19.0.5",
|
|
85
99
|
"react": "^19.1.0",
|
|
86
100
|
"react-dom": "^19.1.0",
|
|
87
101
|
"rspack-plugin-virtual-module": "^1.0.0",
|
|
@@ -93,7 +107,7 @@
|
|
|
93
107
|
"peerDependencies": {
|
|
94
108
|
"@types/react": "*",
|
|
95
109
|
"@types/react-dom": "*",
|
|
96
|
-
"adnbn": ">=0.
|
|
110
|
+
"adnbn": ">=0.4.1",
|
|
97
111
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
|
98
112
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
|
99
113
|
},
|
|
@@ -105,6 +119,15 @@
|
|
|
105
119
|
"optional": true
|
|
106
120
|
}
|
|
107
121
|
},
|
|
122
|
+
"overrides": {
|
|
123
|
+
"flat-cache": "^6.1.18",
|
|
124
|
+
"html-rspack-tags-plugin": {
|
|
125
|
+
"glob": "^10.4.5"
|
|
126
|
+
},
|
|
127
|
+
"test-exclude": {
|
|
128
|
+
"glob": "^10.4.5"
|
|
129
|
+
}
|
|
130
|
+
},
|
|
108
131
|
"eslintConfig": {
|
|
109
132
|
"extends": [
|
|
110
133
|
"plugin:storybook/recommended"
|
|
@@ -24,11 +24,14 @@ $root: dialog;
|
|
|
24
24
|
z-index: 9999;
|
|
25
25
|
transition:
|
|
26
26
|
background-color var(--transition-speed-sm),
|
|
27
|
+
transform var(--transition-speed-sm),
|
|
28
|
+
opacity var(--transition-speed-sm),
|
|
27
29
|
color var(--transition-speed-sm);
|
|
28
30
|
animation-timing-function: ease-in-out;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
&-children {
|
|
34
|
+
flex: 1;
|
|
32
35
|
display: flex;
|
|
33
36
|
flex-direction: column;
|
|
34
37
|
width: 100%;
|
|
@@ -13,12 +13,20 @@ $root: footer;
|
|
|
13
13
|
background-color var(--transition-speed-sm),
|
|
14
14
|
box-shadow var(--transition-speed-sm);
|
|
15
15
|
|
|
16
|
+
[dir="rtl"] & {
|
|
17
|
+
flex-direction: row-reverse;
|
|
18
|
+
}
|
|
19
|
+
|
|
16
20
|
&--shadow {
|
|
17
21
|
box-shadow: var(--footer-box-shadow, 0px -4px 4px 0px rgba(0, 0, 0, 0.03));
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
&--reverse {
|
|
21
25
|
flex-direction: row-reverse;
|
|
26
|
+
|
|
27
|
+
[dir="rtl"] & {
|
|
28
|
+
flex-direction: row;
|
|
29
|
+
}
|
|
22
30
|
}
|
|
23
31
|
|
|
24
32
|
&-children {
|
|
@@ -26,8 +34,16 @@ $root: footer;
|
|
|
26
34
|
display: flex;
|
|
27
35
|
justify-content: space-between;
|
|
28
36
|
|
|
37
|
+
[dir="rtl"] & {
|
|
38
|
+
flex-direction: row-reverse;
|
|
39
|
+
}
|
|
40
|
+
|
|
29
41
|
.#{$root}--reverse & {
|
|
30
42
|
flex-direction: row-reverse;
|
|
43
|
+
|
|
44
|
+
[dir="rtl"] & {
|
|
45
|
+
flex-direction: row;
|
|
46
|
+
}
|
|
31
47
|
}
|
|
32
48
|
}
|
|
33
49
|
|
|
@@ -35,11 +51,19 @@ $root: footer;
|
|
|
35
51
|
display: flex;
|
|
36
52
|
align-items: center;
|
|
37
53
|
gap: var(--footer-left-gap, var(--footer-gap, 15px));
|
|
54
|
+
|
|
55
|
+
[dir="rtl"] & {
|
|
56
|
+
flex-direction: row-reverse;
|
|
57
|
+
}
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
&-right {
|
|
41
61
|
display: flex;
|
|
42
62
|
align-items: center;
|
|
43
63
|
gap: var(--footer-right-gap, var(--footer-gap, 15px));
|
|
64
|
+
|
|
65
|
+
[dir="rtl"] & {
|
|
66
|
+
flex-direction: row-reverse;
|
|
67
|
+
}
|
|
44
68
|
}
|
|
45
69
|
}
|
|
@@ -7,7 +7,7 @@ import {cloneOrCreateElement} from "../../utils";
|
|
|
7
7
|
import {Dialog, DialogProps, dialogPropsKeys} from "../Dialog";
|
|
8
8
|
import {IconButton, IconButtonProps} from "../IconButton";
|
|
9
9
|
|
|
10
|
-
import {ModalRadius} from "./types";
|
|
10
|
+
import {ModalRadius, ModalAnimation} from "./types";
|
|
11
11
|
|
|
12
12
|
import styles from "./modal.module.scss";
|
|
13
13
|
|
|
@@ -15,6 +15,7 @@ export interface ModalProps extends DialogProps {
|
|
|
15
15
|
radius?: ModalRadius;
|
|
16
16
|
closeButton?: boolean | IconButtonProps | ReactElement;
|
|
17
17
|
onClose?: () => void;
|
|
18
|
+
animation?: ModalAnimation;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export const modalPropsKeys = new Set<keyof ModalProps>(["radius", "closeButton", "onClose", ...dialogPropsKeys]);
|
|
@@ -30,6 +31,7 @@ const Modal: ForwardRefRenderFunction<HTMLDivElement, ModalProps> = (props, ref)
|
|
|
30
31
|
className,
|
|
31
32
|
overlayClassName,
|
|
32
33
|
childrenClassName,
|
|
34
|
+
animation = ModalAnimation.FadeScale,
|
|
33
35
|
...other
|
|
34
36
|
} = {...useComponentProps("modal"), ...props};
|
|
35
37
|
|
|
@@ -73,6 +75,7 @@ const Modal: ForwardRefRenderFunction<HTMLDivElement, ModalProps> = (props, ref)
|
|
|
73
75
|
{
|
|
74
76
|
[styles["modal-content--fullscreen"]]: fullscreen,
|
|
75
77
|
[styles[`modal-content--${radius}-radius`]]: radius,
|
|
78
|
+
[styles[`modal-content--${animation}-animation`]]: animation,
|
|
76
79
|
},
|
|
77
80
|
className
|
|
78
81
|
)}
|
|
@@ -14,20 +14,39 @@ $root: modal;
|
|
|
14
14
|
width: var(--modal-width, 90vw);
|
|
15
15
|
max-width: var(--modal-max-width, 350px);
|
|
16
16
|
max-height: var(--modal-max-height, 85vh);
|
|
17
|
+
height: auto;
|
|
17
18
|
padding: var(--modal-padding, 0);
|
|
18
19
|
border-radius: var(--modal-border-radius, 10px);
|
|
19
20
|
box-shadow: var(--modal-box-shadow, 0 0 4px rgba(0, 0, 0, 0.5));
|
|
20
21
|
background-color: var(--modal-bg-color, var(--bg-primary-color));
|
|
21
22
|
transition:
|
|
22
23
|
background-color var(--transition-speed-sm),
|
|
23
|
-
|
|
24
|
+
transform var(--transition-speed-sm),
|
|
25
|
+
opacity var(--transition-speed-sm),
|
|
26
|
+
color var(--transition-speed-sm) !important;
|
|
24
27
|
|
|
25
28
|
&[data-state="open"] {
|
|
26
|
-
|
|
29
|
+
&.#{$root}-content {
|
|
30
|
+
&--fade-animation {
|
|
31
|
+
animation-name: fadeIn;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
&--fadeScale-animation {
|
|
35
|
+
animation-name: fadeScaleIn;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
27
38
|
}
|
|
28
39
|
|
|
29
40
|
&[data-state="closed"] {
|
|
30
|
-
|
|
41
|
+
&.#{$root}-content {
|
|
42
|
+
&--fade-animation {
|
|
43
|
+
animation-name: fadeOut;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&--fadeScale-animation {
|
|
47
|
+
animation-name: fadeScaleIn;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
&--fullscreen {
|
|
@@ -74,7 +93,7 @@ $root: modal;
|
|
|
74
93
|
}
|
|
75
94
|
}
|
|
76
95
|
|
|
77
|
-
@keyframes
|
|
96
|
+
@keyframes fadeScaleIn {
|
|
78
97
|
from {
|
|
79
98
|
opacity: 0;
|
|
80
99
|
transform: translate(-50%, -48%) scale(var(--modal-animation-content-scale, 0.96));
|
|
@@ -85,7 +104,7 @@ $root: modal;
|
|
|
85
104
|
}
|
|
86
105
|
}
|
|
87
106
|
|
|
88
|
-
@keyframes
|
|
107
|
+
@keyframes fadeScaleOut {
|
|
89
108
|
from {
|
|
90
109
|
opacity: 1;
|
|
91
110
|
transform: translate(-50%, -50%) scale(1);
|
|
@@ -95,3 +114,21 @@ $root: modal;
|
|
|
95
114
|
transform: translate(-50%, -48%) scale(var(--modal-animation-content-scale, 0.96));
|
|
96
115
|
}
|
|
97
116
|
}
|
|
117
|
+
|
|
118
|
+
@keyframes fadeIn {
|
|
119
|
+
from {
|
|
120
|
+
opacity: 0;
|
|
121
|
+
}
|
|
122
|
+
to {
|
|
123
|
+
opacity: 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
@keyframes fadeOut {
|
|
128
|
+
from {
|
|
129
|
+
opacity: 1;
|
|
130
|
+
}
|
|
131
|
+
to {
|
|
132
|
+
opacity: 0;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
ComponentProps,
|
|
3
|
+
CSSProperties,
|
|
4
|
+
forwardRef,
|
|
5
|
+
ForwardRefRenderFunction,
|
|
6
|
+
PropsWithChildren,
|
|
7
|
+
useCallback,
|
|
8
|
+
useMemo,
|
|
9
|
+
useState,
|
|
10
|
+
} from "react";
|
|
11
|
+
|
|
12
|
+
import {ViewportContext, ViewportMode, ViewportSizes} from "./context";
|
|
13
|
+
|
|
14
|
+
import classnames from "classnames";
|
|
15
|
+
|
|
16
|
+
import styles from "./viewport.module.scss";
|
|
17
|
+
|
|
18
|
+
export type ViewportProps = ComponentProps<"div"> & {
|
|
19
|
+
mode?: ViewportMode;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const Provider: ForwardRefRenderFunction<HTMLDivElement, PropsWithChildren<ViewportProps>> = (
|
|
23
|
+
{children, className, style, mode: viewportMode = ViewportMode.Adaptive, ...props},
|
|
24
|
+
ref
|
|
25
|
+
) => {
|
|
26
|
+
const [mode, setModeState] = useState<ViewportMode>(viewportMode);
|
|
27
|
+
const [sizes, setSizesState] = useState<ViewportSizes | null>(null);
|
|
28
|
+
|
|
29
|
+
const setSizes = useCallback((sizes: ViewportSizes) => {
|
|
30
|
+
setSizesState(prev => ({...prev, ...sizes}));
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const setMode = useCallback((mode: ViewportMode) => setModeState(mode), []);
|
|
34
|
+
|
|
35
|
+
const resetSizes = useCallback(() => setSizesState(null), []);
|
|
36
|
+
|
|
37
|
+
const contextValue = useMemo(
|
|
38
|
+
() => ({
|
|
39
|
+
mode,
|
|
40
|
+
setSizes,
|
|
41
|
+
setMode,
|
|
42
|
+
resetSizes,
|
|
43
|
+
}),
|
|
44
|
+
[mode, setSizes, setMode, resetSizes]
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const computedStyles: CSSProperties = useMemo(() => {
|
|
48
|
+
if (!sizes) return {...style};
|
|
49
|
+
|
|
50
|
+
const {width, height} = sizes;
|
|
51
|
+
|
|
52
|
+
let baseStyles: CSSProperties = {};
|
|
53
|
+
|
|
54
|
+
if (mode === ViewportMode.Fixed) {
|
|
55
|
+
baseStyles = {
|
|
56
|
+
width,
|
|
57
|
+
minWidth: width,
|
|
58
|
+
maxWidth: width,
|
|
59
|
+
|
|
60
|
+
height,
|
|
61
|
+
minHeight: height,
|
|
62
|
+
maxHeight: height,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (mode === ViewportMode.Adaptive) {
|
|
67
|
+
baseStyles = {
|
|
68
|
+
minWidth: sizes.width,
|
|
69
|
+
minHeight: sizes.height,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {...style, ...baseStyles};
|
|
74
|
+
}, [style, sizes, mode]);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<ViewportContext.Provider value={contextValue}>
|
|
78
|
+
<div
|
|
79
|
+
ref={ref}
|
|
80
|
+
style={computedStyles}
|
|
81
|
+
className={classnames(
|
|
82
|
+
styles["viewport"],
|
|
83
|
+
{
|
|
84
|
+
[styles["viewport--expanded"]]: mode === ViewportMode.Expanded,
|
|
85
|
+
[styles["viewport--fixed"]]: mode === ViewportMode.Fixed,
|
|
86
|
+
},
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
</ViewportContext.Provider>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
Provider.displayName = "ViewportProvider";
|
|
98
|
+
|
|
99
|
+
export default forwardRef(Provider);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {createContext, useContext} from "react";
|
|
2
|
+
|
|
3
|
+
export enum ViewportMode {
|
|
4
|
+
Fixed = "fixed",
|
|
5
|
+
Adaptive = "adaptive",
|
|
6
|
+
Expanded = "expanded",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type ViewportSize = number | string;
|
|
10
|
+
|
|
11
|
+
export type ViewportSizes = {
|
|
12
|
+
height?: ViewportSize;
|
|
13
|
+
width?: ViewportSize;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface ViewportContract {
|
|
17
|
+
mode: ViewportMode;
|
|
18
|
+
|
|
19
|
+
setMode(mode: ViewportMode): void;
|
|
20
|
+
|
|
21
|
+
setSizes(sizes: ViewportSizes): void;
|
|
22
|
+
|
|
23
|
+
resetSizes(): void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const ViewportContext = createContext<ViewportContract>({
|
|
27
|
+
mode: ViewportMode.Adaptive,
|
|
28
|
+
|
|
29
|
+
setMode() {},
|
|
30
|
+
|
|
31
|
+
setSizes() {},
|
|
32
|
+
|
|
33
|
+
resetSizes() {},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ViewportContext.displayName = "ViewportContext";
|
|
37
|
+
|
|
38
|
+
export const useViewport = () => useContext(ViewportContext);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.viewport {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
overflow: hidden;
|
|
5
|
+
min-width: var(--viewport-min-width);
|
|
6
|
+
max-width: var(--viewport-max-width);
|
|
7
|
+
min-height: var(--viewport-min-height);
|
|
8
|
+
max-height: var(--viewport-max-height);
|
|
9
|
+
transition:
|
|
10
|
+
height var(--transition-speed-md) ease-in-out,
|
|
11
|
+
width var(--transition-speed-md) ease-in-out,
|
|
12
|
+
min-height var(--transition-speed-md) ease-in-out,
|
|
13
|
+
min-width var(--transition-speed-md) ease-in-out,
|
|
14
|
+
max-height var(--transition-speed-md) ease-in-out,
|
|
15
|
+
max-width var(--transition-speed-md) ease-in-out;
|
|
16
|
+
|
|
17
|
+
&--expanded {
|
|
18
|
+
min-height: var(--viewport-max-height);
|
|
19
|
+
min-width: var(--viewport-max-width);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
&--fixed {
|
|
23
|
+
width: var(--viewport-width);
|
|
24
|
+
min-width: var(--viewport-width);
|
|
25
|
+
max-width: var(--viewport-width);
|
|
26
|
+
|
|
27
|
+
height: var(--viewport-height);
|
|
28
|
+
min-height: var(--viewport-height);
|
|
29
|
+
max-height: var(--viewport-height);
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -9,7 +9,6 @@ export * from "./Header";
|
|
|
9
9
|
export * from "./Highlight";
|
|
10
10
|
export * from "./Icon";
|
|
11
11
|
export * from "./IconButton";
|
|
12
|
-
export * from "./Layout";
|
|
13
12
|
export * from "./List";
|
|
14
13
|
export * from "./ListItem";
|
|
15
14
|
export * from "./Modal";
|
|
@@ -25,3 +24,4 @@ export * from "./Tooltip";
|
|
|
25
24
|
export * from "./View";
|
|
26
25
|
export * from "./ViewDrawer";
|
|
27
26
|
export * from "./ViewModal";
|
|
27
|
+
export * from "./Viewport";
|
package/src/plugin/index.ts
CHANGED
|
@@ -36,7 +36,7 @@ export default definePlugin((options: PluginOptions = {}) => {
|
|
|
36
36
|
let styleBuilder: BuilderContract;
|
|
37
37
|
|
|
38
38
|
return {
|
|
39
|
-
name: "
|
|
39
|
+
name: "addon-ui",
|
|
40
40
|
startup: ({config}) => {
|
|
41
41
|
const {srcDir, appsDir, sharedDir, app, appSrcDir} = config;
|
|
42
42
|
const normalizeThemeDir = path.normalize(themeDir).split(path.sep);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import React, {FC, PropsWithChildren, useCallback, useEffect, useState} from "react";
|
|
1
|
+
import React, {FC, PropsWithChildren, useCallback, useEffect, useMemo, useState} from "react";
|
|
2
2
|
|
|
3
3
|
import {ThemeContext} from "./context";
|
|
4
4
|
|
|
5
5
|
import {Theme, ThemeStorageContract} from "../../types/theme";
|
|
6
6
|
import {Config} from "../../types/config";
|
|
7
7
|
|
|
8
|
+
import ThemeStorage from "./ThemeStorage";
|
|
9
|
+
|
|
8
10
|
const isDarkMedia = () => window?.matchMedia("(prefers-color-scheme: dark)")?.matches;
|
|
9
11
|
|
|
10
12
|
const isValid = (theme: Theme | undefined): theme is Theme => {
|
|
@@ -12,21 +14,29 @@ const isValid = (theme: Theme | undefined): theme is Theme => {
|
|
|
12
14
|
};
|
|
13
15
|
|
|
14
16
|
export interface ThemeProviderProps extends Pick<Config, "components"> {
|
|
15
|
-
storage?: ThemeStorageContract;
|
|
17
|
+
storage?: ThemeStorageContract | true;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({children, components, storage}) => {
|
|
19
21
|
const [theme, setTheme] = useState<Theme>(() => (isDarkMedia() ? Theme.Dark : Theme.Light));
|
|
20
22
|
|
|
23
|
+
const currentStorage: ThemeStorageContract | undefined = useMemo(() => {
|
|
24
|
+
if (!storage) return;
|
|
25
|
+
|
|
26
|
+
if (storage === true) return new ThemeStorage();
|
|
27
|
+
|
|
28
|
+
return storage;
|
|
29
|
+
}, [storage]);
|
|
30
|
+
|
|
21
31
|
const changeTheme = useCallback(
|
|
22
32
|
(theme: Theme) => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
33
|
+
if (currentStorage) {
|
|
34
|
+
currentStorage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e));
|
|
25
35
|
} else {
|
|
26
36
|
setTheme(theme);
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
|
-
[
|
|
39
|
+
[currentStorage]
|
|
30
40
|
);
|
|
31
41
|
|
|
32
42
|
const toggleTheme = useCallback(() => {
|
|
@@ -34,17 +44,17 @@ const ThemeProvider: FC<PropsWithChildren<ThemeProviderProps>> = ({children, com
|
|
|
34
44
|
}, [theme, changeTheme]);
|
|
35
45
|
|
|
36
46
|
useEffect(() => {
|
|
37
|
-
if (!
|
|
47
|
+
if (!currentStorage) return;
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
currentStorage
|
|
40
50
|
.get()
|
|
41
51
|
.then(newTheme => isValid(newTheme) && setTheme(newTheme))
|
|
42
52
|
.catch(e => console.error("ThemeProvider: get theme from storage error", e));
|
|
43
53
|
|
|
44
|
-
const unsubscribe =
|
|
54
|
+
const unsubscribe = currentStorage.watch(newTheme => isValid(newTheme) && setTheme(newTheme));
|
|
45
55
|
|
|
46
56
|
return () => unsubscribe();
|
|
47
|
-
}, [
|
|
57
|
+
}, [currentStorage]);
|
|
48
58
|
|
|
49
59
|
useEffect(() => {
|
|
50
60
|
document.querySelector("html")?.setAttribute("theme", theme);
|
|
@@ -1,14 +1,21 @@
|
|
|
1
|
-
import {Storage} from "
|
|
1
|
+
import {Storage, StorageProvider} from "@addon-core/storage";
|
|
2
2
|
|
|
3
3
|
import {Theme, ThemeStorageContract} from "../../types/theme";
|
|
4
4
|
|
|
5
|
+
export type ThemeStorageState = Record<string, Theme>;
|
|
6
|
+
|
|
5
7
|
export default class implements ThemeStorageContract {
|
|
6
|
-
|
|
8
|
+
protected storage: StorageProvider<ThemeStorageState> = new Storage<ThemeStorageState>({
|
|
7
9
|
area: "local",
|
|
8
|
-
namespace: "
|
|
10
|
+
namespace: "addon-ui",
|
|
9
11
|
});
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
protected key: string = "theme";
|
|
14
|
+
|
|
15
|
+
constructor(storage?: StorageProvider<ThemeStorageState>, key?: string) {
|
|
16
|
+
this.storage = storage ? storage : this.storage;
|
|
17
|
+
this.key = key ? key : this.key;
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
public async get(): Promise<Theme | undefined> {
|
|
14
21
|
return await this.storage.get(this.key);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {default as ThemeProvider} from "./ThemeProvider";
|
|
2
|
-
export {default as ThemeStorage} from "./ThemeStorage";
|
|
1
|
+
export {default as ThemeProvider, type ThemeProviderProps} from "./ThemeProvider";
|
|
2
|
+
export {default as ThemeStorage, type ThemeStorageState} from "./ThemeStorage";
|
|
3
3
|
export {useTheme, useComponentProps} from "./context";
|