addon-ui 0.3.1 → 0.4.0

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/package.json CHANGED
@@ -1,28 +1,28 @@
1
1
  {
2
2
  "name": "addon-ui",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.4.0",
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
- "addonbone",
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 <addonbonedev@gmail.com> (https://github.com/atldays)"
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.git"
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.9",
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.2.9",
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,6 +7,10 @@
7
7
  box-sizing: border-box;
8
8
  line-height: var(--list-item-line-height, var(--line-height, 1 rem));
9
9
 
10
+ [dir="rtl"] & {
11
+ flex-direction: row-reverse;
12
+ }
13
+
10
14
  &__center {
11
15
  flex-grow: 1;
12
16
  display: flex;
@@ -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
- color var(--transition-speed-sm);
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
- animation-name: contentShow;
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
- animation-name: contentHide;
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 contentShow {
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 contentHide {
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
+ }
@@ -4,3 +4,8 @@ export enum ModalRadius {
4
4
  Medium = "medium",
5
5
  Large = "large",
6
6
  }
7
+
8
+ export enum ModalAnimation {
9
+ Fade = "fade",
10
+ FadeScale = "fadeScale",
11
+ }
@@ -30,6 +30,7 @@ $root: text-field;
30
30
  width: 100%;
31
31
  color: inherit;
32
32
  font-size: inherit;
33
+ font-weight: inherit;
33
34
  font-family: inherit;
34
35
  padding: 0;
35
36
  border: none;
@@ -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,2 @@
1
+ export {default as ViewportProvider, type ViewportProps} from "./Provider";
2
+ export {useViewport, type ViewportSizes, ViewportMode} from "./context";
@@ -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
+ }
@@ -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";
@@ -36,7 +36,7 @@ export default definePlugin((options: PluginOptions = {}) => {
36
36
  let styleBuilder: BuilderContract;
37
37
 
38
38
  return {
39
- name: "adnbn-ui",
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 (storage) {
24
- storage.change(theme).catch(e => console.error("ThemeProvider: set theme to storage error", e));
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
- [storage]
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 (!storage) return;
47
+ if (!currentStorage) return;
38
48
 
39
- storage
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 = storage.watch(newTheme => isValid(newTheme) && setTheme(newTheme));
54
+ const unsubscribe = currentStorage.watch(newTheme => isValid(newTheme) && setTheme(newTheme));
45
55
 
46
56
  return () => unsubscribe();
47
- }, [storage]);
57
+ }, [currentStorage]);
48
58
 
49
59
  useEffect(() => {
50
60
  document.querySelector("html")?.setAttribute("theme", theme);
@@ -1,14 +1,21 @@
1
- import {Storage} from "adnbn/storage";
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
- private readonly storage = new Storage<Record<string, Theme>>({
8
+ protected storage: StorageProvider<ThemeStorageState> = new Storage<ThemeStorageState>({
7
9
  area: "local",
8
- namespace: "adnbn-ui",
10
+ namespace: "addon-ui",
9
11
  });
10
12
 
11
- private readonly key = "theme";
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";