Integrate color scheme preference into ui library (#950)

This commit is contained in:
Nathan Bierema 2021-11-06 13:28:35 -04:00 committed by GitHub
parent a8883f287d
commit 1de7e11a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 194 additions and 170 deletions

View File

@ -12,6 +12,7 @@
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react-hooks/recommended",
"prettier" "prettier"
], ],
"settings": { "settings": {

View File

@ -6,6 +6,7 @@
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended", "plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jest/recommended", "plugin:jest/recommended",
"plugin:jest/style", "plugin:jest/style",
"prettier" "prettier"

View File

@ -62,12 +62,10 @@ class App extends Component<Props> {
function mapStateToProps(state: StoreState) { function mapStateToProps(state: StoreState) {
const instances = state.instances; const instances = state.instances;
const id = getActiveInstance(instances); const id = getActiveInstance(instances);
const { themeColorPreference, ...themeData } = state.theme;
return { return {
options: instances.options[id], options: instances.options[id],
section: state.section, section: state.section,
theme: themeData, theme: state.theme,
notification: state.notification, notification: state.notification,
}; };
} }

View File

@ -8,10 +8,7 @@ import configureStore from '../../../app/stores/panelStore';
import '../../views/devpanel.pug'; import '../../views/devpanel.pug';
import { Action, Store } from 'redux'; import { Action, Store } from 'redux';
import { import { StoreAction } from '@redux-devtools/app/lib/actions';
applyMediaFeaturesPreferences,
StoreAction,
} from '@redux-devtools/app/lib/actions';
import { PanelMessage } from '../../../app/middlewares/api'; import { PanelMessage } from '../../../app/middlewares/api';
import { StoreStateWithoutSocket } from '../../../app/reducers/panel'; import { StoreStateWithoutSocket } from '../../../app/reducers/panel';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
@ -36,20 +33,9 @@ function renderDevTools() {
unmountComponentAtNode(node!); unmountComponentAtNode(node!);
clearTimeout(naTimeout); clearTimeout(naTimeout);
({ store, persistor } = configureStore(position, bgConnection)); ({ store, persistor } = configureStore(position, bgConnection));
const onBeforeLift = () => {
if (store) {
store.dispatch(applyMediaFeaturesPreferences());
}
};
render( render(
<Provider store={store}> <Provider store={store}>
<PersistGate <PersistGate loading={null} persistor={persistor}>
loading={null}
persistor={persistor}
onBeforeLift={onBeforeLift}
>
<App position={position} /> <App position={position} />
</PersistGate> </PersistGate>
</Provider>, </Provider>,

View File

@ -8,7 +8,6 @@ import configureStore from '../../../app/stores/windowStore';
import { MonitorMessage } from '../../../app/middlewares/api'; import { MonitorMessage } from '../../../app/middlewares/api';
import '../../views/window.pug'; import '../../views/window.pug';
import { applyMediaFeaturesPreferences } from '@redux-devtools/app/lib/actions';
const position = location.hash; const position = location.hash;
@ -26,17 +25,9 @@ chrome.runtime.getBackgroundPage((window) => {
bg.onMessage.addListener(update); bg.onMessage.addListener(update);
update(); update();
const onBeforeLift = () => {
localStore.dispatch(applyMediaFeaturesPreferences());
};
render( render(
<Provider store={localStore}> <Provider store={localStore}>
<PersistGate <PersistGate loading={null} persistor={persistor}>
loading={null}
persistor={persistor}
onBeforeLift={onBeforeLift}
>
<App position={position} /> <App position={position} />
</PersistGate> </PersistGate>
</Provider>, </Provider>,

View File

@ -4,6 +4,20 @@ import { Provider } from 'react-redux';
import configureStore from '../../../src/app/stores/windowStore'; import configureStore from '../../../src/app/stores/windowStore';
import App from '../../../src/app/containers/App'; import App from '../../../src/app/containers/App';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const { store } = configureStore(store); const { store } = configureStore(store);
describe('App container', () => { describe('App container', () => {

View File

@ -7,6 +7,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"lerna": "^4.0.0", "lerna": "^4.0.0",
"prettier": "2.4.1", "prettier": "2.4.1",

View File

@ -35,6 +35,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",

View File

@ -57,6 +57,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-test-renderer": "^17.0.2", "react-test-renderer": "^17.0.2",

View File

@ -41,6 +41,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",

View File

@ -65,6 +65,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-test-renderer": "^17.0.2", "react-test-renderer": "^17.0.2",

View File

@ -3,6 +3,6 @@ module.exports = {
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'], setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
testEnvironment: 'jsdom', testEnvironment: 'jsdom',
moduleNameMapper: { moduleNameMapper: {
'\\.css$': '<rootDir>/test/__mocks__/styleMock.js', '\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
}, },
}; };

View File

@ -91,6 +91,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-loader": "^3.0.0", "html-loader": "^3.0.0",

View File

@ -3,7 +3,6 @@ import { AuthStates, States } from 'socketcluster-client/lib/scclientsocket';
import { import {
CHANGE_SECTION, CHANGE_SECTION,
CHANGE_THEME, CHANGE_THEME,
APPLY_MEDIA_FEATURES_PREFERENCES,
SELECT_INSTANCE, SELECT_INSTANCE,
SELECT_MONITOR, SELECT_MONITOR,
UPDATE_MONITOR_STATE, UPDATE_MONITOR_STATE,
@ -45,10 +44,9 @@ import {
import { Action } from 'redux'; import { Action } from 'redux';
import { Features, State } from '../reducers/instances'; import { Features, State } from '../reducers/instances';
import { MonitorStateMonitorState } from '../reducers/monitor'; import { MonitorStateMonitorState } from '../reducers/monitor';
import { LiftedAction, LiftedState } from '@redux-devtools/core'; import { LiftedAction } from '@redux-devtools/core';
import { Data } from '../reducers/reports'; import { Data } from '../reducers/reports';
import { prefersDarkColorScheme } from '../utils/media-queries'; import { LiftedState } from '@redux-devtools/core';
import { ThemeColorPreference } from '../reducers/theme';
let monitorReducer: ( let monitorReducer: (
monitorProps: unknown, monitorProps: unknown,
@ -68,53 +66,19 @@ export function changeSection(section: string): ChangeSectionAction {
interface ChangeThemeFormData { interface ChangeThemeFormData {
readonly theme: Theme; readonly theme: Theme;
readonly scheme: Scheme; readonly scheme: Scheme;
readonly themeColorPreference: ThemeColorPreference; readonly colorPreference: 'auto' | 'light' | 'dark';
} }
export interface ChangeThemeData { interface ChangeThemeData {
readonly formData: ChangeThemeFormData; readonly formData: ChangeThemeFormData;
} }
export interface ChangeThemeAction { export interface ChangeThemeAction {
readonly type: typeof CHANGE_THEME; readonly type: typeof CHANGE_THEME;
readonly theme: Theme; readonly theme: Theme;
readonly scheme: Scheme; readonly scheme: Scheme;
readonly dark: boolean; readonly colorPreference: 'auto' | 'light' | 'dark';
readonly themeColorPreference: ThemeColorPreference;
} }
export interface ApplyMediaFeaturesPreferencesAction {
readonly type: typeof APPLY_MEDIA_FEATURES_PREFERENCES;
readonly prefersDarkColorScheme: boolean;
}
export function changeTheme(data: ChangeThemeData): ChangeThemeAction { export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
const { themeColorPreference } = data.formData; return { type: CHANGE_THEME, ...data.formData };
let dark: boolean;
switch (themeColorPreference) {
case 'light':
dark = false;
break;
case 'dark':
dark = true;
break;
default:
dark = prefersDarkColorScheme();
}
return { type: CHANGE_THEME, ...data.formData, dark };
}
/**
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries/Using_media_queries#media_features
*/
export function applyMediaFeaturesPreferences(
payload?: Partial<Omit<ApplyMediaFeaturesPreferencesAction, 'type'>>
): ApplyMediaFeaturesPreferencesAction {
return {
prefersDarkColorScheme: prefersDarkColorScheme(),
...payload,
type: APPLY_MEDIA_FEATURES_PREFERENCES,
};
} }
export interface InitMonitorAction { export interface InitMonitorAction {
@ -600,7 +564,6 @@ export interface ErrorAction {
export type StoreActionWithoutUpdateStateOrLiftedAction = export type StoreActionWithoutUpdateStateOrLiftedAction =
| ChangeSectionAction | ChangeSectionAction
| ChangeThemeAction | ChangeThemeAction
| ApplyMediaFeaturesPreferencesAction
| MonitorActionAction | MonitorActionAction
| SelectInstanceAction | SelectInstanceAction
| SelectMonitorAction | SelectMonitorAction

View File

@ -4,10 +4,6 @@ import { Container, Form } from '@redux-devtools/ui';
import { listSchemes, listThemes } from '@redux-devtools/ui/lib/utils/theme'; import { listSchemes, listThemes } from '@redux-devtools/ui/lib/utils/theme';
import { changeTheme } from '../../actions'; import { changeTheme } from '../../actions';
import { StoreState } from '../../reducers'; import { StoreState } from '../../reducers';
import {
defaultThemeColorPreference,
themeColorPreferences,
} from '../../reducers/theme';
type StateProps = ReturnType<typeof mapStateToProps>; type StateProps = ReturnType<typeof mapStateToProps>;
type DispatchProps = ResolveThunks<typeof actionCreators>; type DispatchProps = ResolveThunks<typeof actionCreators>;
@ -19,8 +15,7 @@ export class Themes extends Component<Props> {
const formData = { const formData = {
theme: theme.theme, theme: theme.theme,
scheme: theme.scheme, scheme: theme.scheme,
themeColorPreference: colorPreference: theme.colorPreference,
theme.themeColorPreference ?? defaultThemeColorPreference,
}; };
return ( return (
@ -38,10 +33,10 @@ export class Themes extends Component<Props> {
type: 'string', type: 'string',
enum: listSchemes(), enum: listSchemes(),
}, },
themeColorPreference: { colorPreference: {
title: 'theme color', title: 'theme color',
type: 'string', type: 'string',
enum: themeColorPreferences as unknown as string[], enum: ['auto', 'light', 'dark'],
}, },
}, },
}} }}

View File

@ -1,7 +1,5 @@
export const CHANGE_SECTION = 'main/CHANGE_SECTION'; export const CHANGE_SECTION = 'main/CHANGE_SECTION';
export const CHANGE_THEME = 'main/CHANGE_THEME'; export const CHANGE_THEME = 'main/CHANGE_THEME';
export const APPLY_MEDIA_FEATURES_PREFERENCES =
'main/APPLY_MEDIA_FEATURES_PREFERENCES';
export const UPDATE_STATE = 'devTools/UPDATE_STATE'; export const UPDATE_STATE = 'devTools/UPDATE_STATE';
export const SET_STATE = 'devTools/SET_STATE'; export const SET_STATE = 'devTools/SET_STATE';

View File

@ -7,7 +7,7 @@ import configureStore from './store/configureStore';
import { CONNECT_REQUEST } from './constants/socketActionTypes'; import { CONNECT_REQUEST } from './constants/socketActionTypes';
import App from './containers/App'; import App from './containers/App';
import { StoreState } from './reducers'; import { StoreState } from './reducers';
import { StoreAction, applyMediaFeaturesPreferences } from './actions'; import { StoreAction } from './actions';
class Root extends Component { class Root extends Component {
store?: Store<StoreState, StoreAction>; store?: Store<StoreState, StoreAction>;
@ -27,26 +27,11 @@ class Root extends Component {
this.persistor = persistor; this.persistor = persistor;
} }
/**
* @hidden
* @private
*/
private _checkMediaFeaturesPreferences = () => {
if (this.store) {
this.store.dispatch(applyMediaFeaturesPreferences());
}
};
render() { render() {
if (!this.store) return null; if (!this.store) return null;
return ( return (
<Provider store={this.store}> <Provider store={this.store}>
<PersistGate <PersistGate loading={null} persistor={this.persistor!}>
loading={null}
persistor={this.persistor!}
onBeforeLift={this._checkMediaFeaturesPreferences}
>
<App /> <App />
</PersistGate> </PersistGate>
</Provider> </Provider>

View File

@ -1,33 +1,18 @@
import { Theme, Scheme } from '@redux-devtools/ui'; import { Scheme, Theme } from '@redux-devtools/ui';
import { import { CHANGE_THEME } from '../constants/actionTypes';
CHANGE_THEME,
APPLY_MEDIA_FEATURES_PREFERENCES,
} from '../constants/actionTypes';
import { StoreAction } from '../actions'; import { StoreAction } from '../actions';
export const defaultThemeColorPreference = 'auto';
export const themeColorPreferences = [
defaultThemeColorPreference,
'light',
'dark',
] as const;
export type ThemeColorPreference = typeof themeColorPreferences[number];
export interface ThemeState { export interface ThemeState {
readonly theme: Theme; readonly theme: Theme;
readonly scheme: Scheme; readonly scheme: Scheme;
readonly light: boolean; readonly colorPreference: 'auto' | 'light' | 'dark';
readonly themeColorPreference?: ThemeColorPreference;
} }
export default function theme( export default function theme(
state: ThemeState = { state: ThemeState = {
theme: 'default' as const, theme: 'default',
scheme: 'default' as const, scheme: 'default',
light: true, colorPreference: 'auto',
themeColorPreference: defaultThemeColorPreference,
}, },
action: StoreAction action: StoreAction
) { ) {
@ -35,23 +20,8 @@ export default function theme(
return { return {
theme: action.theme, theme: action.theme,
scheme: action.scheme, scheme: action.scheme,
light: !action.dark, colorPreference: action.colorPreference,
themeColorPreference: action.themeColorPreference,
}; };
} }
if (
action.type === APPLY_MEDIA_FEATURES_PREFERENCES &&
(!state.themeColorPreference ||
state.themeColorPreference === defaultThemeColorPreference)
) {
return {
...state,
themeColorPreference:
state.themeColorPreference ?? defaultThemeColorPreference,
light: !action.prefersDarkColorScheme,
};
}
return state; return state;
} }

View File

@ -1,13 +0,0 @@
/**
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme
*/
export function prefersDarkColorScheme(): boolean {
if (
typeof window !== 'undefined' &&
typeof window.matchMedia === 'function'
) {
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
return false;
}

View File

@ -1 +0,0 @@
module.exports = {};

View File

@ -0,0 +1 @@
export default {};

View File

@ -9,6 +9,20 @@ import rootReducer from '../src/reducers';
import { DATA_TYPE_KEY } from '../src/constants/dataTypes'; import { DATA_TYPE_KEY } from '../src/constants/dataTypes';
import stringifyJSON from '../src/utils/stringifyJSON'; import stringifyJSON from '../src/utils/stringifyJSON';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
const store = createStore(rootReducer, applyMiddleware(exportState, api)); const store = createStore(rootReducer, applyMiddleware(exportState, api));
describe('App container', () => { describe('App container', () => {

View File

@ -56,6 +56,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -57,6 +57,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -48,6 +48,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",

View File

@ -58,7 +58,11 @@ render(
<Provider store={store}> <Provider store={store}>
<ConnectedRouter history={history}> <ConnectedRouter history={history}>
<Container <Container
themeData={{ theme: 'default', scheme: 'default', light: true }} themeData={{
theme: 'default',
scheme: 'default',
colorPreference: 'auto',
}}
> >
<Route path={ROOT}> <Route path={ROOT}>
<DemoApp /> <DemoApp />

View File

@ -73,6 +73,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@ -57,6 +57,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@ -48,6 +48,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",

View File

@ -70,6 +70,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -59,6 +59,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -57,7 +57,6 @@
"@typescript-eslint/parser": "^5.2.0", "@typescript-eslint/parser": "^5.2.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "~4.4.4" "typescript": "~4.4.4"

View File

@ -49,6 +49,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",

View File

@ -69,6 +69,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -47,6 +47,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",

View File

@ -48,6 +48,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"react": "^17.0.2", "react": "^17.0.2",
"redux": "^4.1.2", "redux": "^4.1.2",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",

View File

@ -13,6 +13,7 @@ export const globalTypes = {
defaultValue: 'default', defaultValue: 'default',
toolbar: { toolbar: {
items: listThemes(), items: listThemes(),
showName: true,
}, },
}, },
scheme: { scheme: {
@ -21,6 +22,7 @@ export const globalTypes = {
defaultValue: 'default', defaultValue: 'default',
toolbar: { toolbar: {
items: listSchemes(), items: listSchemes(),
showName: true,
}, },
}, },
color: { color: {
@ -28,7 +30,8 @@ export const globalTypes = {
description: 'Global color for components', description: 'Global color for components',
defaultValue: 'light', defaultValue: 'light',
toolbar: { toolbar: {
items: ['light', 'dark'], items: ['auto', 'light', 'dark'],
showName: true,
}, },
}, },
}; };
@ -38,7 +41,7 @@ const withThemeProvider = (Story, context) => (
themeData={{ themeData={{
theme: context.globals.theme, theme: context.globals.theme,
scheme: context.globals.scheme, scheme: context.globals.scheme,
light: context.globals.color === 'light', colorPreference: context.globals.color,
}} }}
> >
<Story {...context} /> <Story {...context} />

View File

@ -73,6 +73,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@ -1,10 +1,28 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ThemeProvider } from 'styled-components'; import { ThemeProvider } from 'styled-components';
import { getTheme, ThemeData } from '../utils/theme'; import { useTheme, ThemeData } from '../utils/theme';
import { MainContainerWrapper, ContainerWrapper } from './styles'; import { MainContainerWrapper, ContainerWrapper } from './styles';
import { Theme } from '../themes/default'; import { Theme } from '../themes/default';
interface ContainerFromThemeDataProps {
children?: React.ReactNode;
themeData: ThemeData;
className?: string;
}
const ContainerFromThemeData: React.FunctionComponent<ContainerFromThemeDataProps> =
({ themeData, className, children }) => {
const theme = useTheme(themeData);
return (
<ThemeProvider theme={theme}>
<MainContainerWrapper className={className}>
{children}
</MainContainerWrapper>
</ThemeProvider>
);
};
interface Props { interface Props {
children?: React.ReactNode; children?: React.ReactNode;
themeData?: ThemeData; themeData?: ThemeData;
@ -27,11 +45,9 @@ const Container: React.FunctionComponent<Props> = ({
} }
return ( return (
<ThemeProvider theme={getTheme(themeData)}> <ContainerFromThemeData themeData={themeData} className={className}>
<MainContainerWrapper className={className}>
{children} {children}
</MainContainerWrapper> </ContainerFromThemeData>
</ThemeProvider>
); );
}; };

View File

@ -1,3 +1,4 @@
import { useEffect, useMemo, useState } from 'react';
import * as themes from '../themes'; import * as themes from '../themes';
import { nicinabox as defaultDarkScheme } from 'redux-devtools-themes'; import { nicinabox as defaultDarkScheme } from 'redux-devtools-themes';
import * as baseSchemes from 'base16'; import * as baseSchemes from 'base16';
@ -15,7 +16,7 @@ export type Scheme = keyof typeof schemes;
export interface ThemeData { export interface ThemeData {
theme: keyof typeof themes; theme: keyof typeof themes;
scheme: keyof typeof schemes; scheme: keyof typeof schemes;
light: boolean; colorPreference: 'auto' | 'light' | 'dark';
} }
export interface ThemeFromProvider extends ThemeBase { export interface ThemeFromProvider extends ThemeBase {
@ -23,11 +24,11 @@ export interface ThemeFromProvider extends ThemeBase {
light: boolean; light: boolean;
} }
export const getTheme = ({ const getTheme = (
theme: type, type: keyof typeof themes,
scheme, scheme: keyof typeof schemes,
light, light: boolean
}: ThemeData): ThemeFromProvider => { ): ThemeFromProvider => {
let colors; let colors;
if (scheme === 'default') { if (scheme === 'default') {
colors = light ? schemes.default : defaultDarkScheme; colors = light ? schemes.default : defaultDarkScheme;
@ -47,3 +48,40 @@ export const getTheme = ({
return theme; return theme;
}; };
export const useTheme = ({
theme: type,
scheme,
colorPreference,
}: ThemeData): ThemeFromProvider => {
const [prefersDarkColorScheme, setPrefersDarkColorScheme] = useState(
window.matchMedia('(prefers-color-scheme: dark)').matches
);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleChange = ({ matches }: MediaQueryListEvent) => {
if (matches && !prefersDarkColorScheme) {
setPrefersDarkColorScheme(true);
}
if (!matches && prefersDarkColorScheme) {
setPrefersDarkColorScheme(false);
}
};
mediaQuery.addEventListener('change', handleChange);
return () => mediaQuery.removeEventListener('change', handleChange);
}, [prefersDarkColorScheme]);
const light = useMemo(
() =>
colorPreference === 'auto'
? !prefersDarkColorScheme
: colorPreference === 'light',
[colorPreference, prefersDarkColorScheme]
);
return getTheme(type, scheme, light);
};

View File

@ -2,11 +2,29 @@ import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { Container } from '../src'; import { Container } from '../src';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
describe('Container', function () { describe('Container', function () {
it('renders correctly', () => { it('renders correctly', () => {
const { container } = render( const { container } = render(
<Container <Container
themeData={{ theme: 'default', scheme: 'default', light: false }} themeData={{
theme: 'default',
scheme: 'default',
colorPreference: 'auto',
}}
> >
Text Text
</Container> </Container>

View File

@ -2,7 +2,7 @@
exports[`Container renders correctly 1`] = ` exports[`Container renders correctly 1`] = `
<div <div
class="sc-bdvvtL gyKeHC" class="sc-bdvvtL bKcxHw"
> >
Text Text
</div> </div>

View File

@ -48,6 +48,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"ts-node": "^10.4.0", "ts-node": "^10.4.0",

View File

@ -62,6 +62,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"fork-ts-checker-webpack-plugin": "^6.4.0", "fork-ts-checker-webpack-plugin": "^6.4.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",

View File

@ -60,6 +60,7 @@
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^25.2.2", "eslint-plugin-jest": "^25.2.2",
"eslint-plugin-react": "^7.26.1", "eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"jest": "^27.3.1", "jest": "^27.3.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",

View File

@ -4576,6 +4576,7 @@ __metadata:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
file-loader: ^6.2.0 file-loader: ^6.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-loader: ^3.0.0 html-loader: ^3.0.0
@ -4634,6 +4635,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
redux: ^4.1.2 redux: ^4.1.2
@ -4730,6 +4732,7 @@ __metadata:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
jest: ^27.3.1 jest: ^27.3.1
lodash: ^4.17.21 lodash: ^4.17.21
prop-types: ^15.7.2 prop-types: ^15.7.2
@ -4765,6 +4768,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
parse-key: ^0.2.1 parse-key: ^0.2.1
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
@ -4829,6 +4833,7 @@ __metadata:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
javascript-stringify: ^2.1.0 javascript-stringify: ^2.1.0
jest: ^27.3.1 jest: ^27.3.1
jsan: ^3.1.13 jsan: ^3.1.13
@ -4879,6 +4884,7 @@ __metadata:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
html-entities: ^2.3.2 html-entities: ^2.3.2
jest: ^27.3.1 jest: ^27.3.1
path-browserify: ^1.0.1 path-browserify: ^1.0.1
@ -4927,6 +4933,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
hex-rgba: ^1.0.2 hex-rgba: ^1.0.2
immutable: ^4.0.0 immutable: ^4.0.0
javascript-stringify: ^2.1.0 javascript-stringify: ^2.1.0
@ -4998,6 +5005,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
lodash.debounce: ^4.0.8 lodash.debounce: ^4.0.8
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
@ -5030,7 +5038,6 @@ __metadata:
"@typescript-eslint/parser": ^5.2.0 "@typescript-eslint/parser": ^5.2.0
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1
jsan: ^3.1.13 jsan: ^3.1.13
querystring: ^0.2.1 querystring: ^0.2.1
redux: ^4.1.2 redux: ^4.1.2
@ -5065,6 +5072,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
hex-rgba: ^1.0.2 hex-rgba: ^1.0.2
immutable: ^4.0.0 immutable: ^4.0.0
jss: ^10.8.2 jss: ^10.8.2
@ -5132,6 +5140,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
redux: ^4.1.2 redux: ^4.1.2
@ -5182,6 +5191,7 @@ __metadata:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
jest: ^27.3.1 jest: ^27.3.1
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
@ -11807,6 +11817,7 @@ __metadata:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
prop-types: ^15.7.2 prop-types: ^15.7.2
@ -16700,6 +16711,7 @@ fsevents@^1.2.7:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
history: ^4.10.1 history: ^4.10.1
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
@ -23744,6 +23756,7 @@ fsevents@^1.2.7:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
react: ^17.0.2 react: ^17.0.2
@ -23782,6 +23795,7 @@ fsevents@^1.2.7:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
jest: ^27.3.1 jest: ^27.3.1
lodash.debounce: ^4.0.8 lodash.debounce: ^4.0.8
prop-types: ^15.7.2 prop-types: ^15.7.2
@ -23947,6 +23961,7 @@ fsevents@^1.2.7:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
immutable: ^4.0.0 immutable: ^4.0.0
@ -23984,6 +23999,7 @@ fsevents@^1.2.7:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
jest: ^27.3.1 jest: ^27.3.1
prop-types: ^15.7.2 prop-types: ^15.7.2
react: ^17.0.2 react: ^17.0.2
@ -25352,6 +25368,7 @@ resolve@^2.0.0-next.3:
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-jest: ^25.2.2 eslint-plugin-jest: ^25.2.2
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
jest: ^27.3.1 jest: ^27.3.1
lerna: ^4.0.0 lerna: ^4.0.0
prettier: 2.4.1 prettier: 2.4.1
@ -25400,6 +25417,7 @@ resolve@^2.0.0-next.3:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
framer-motion: ^4.1.17 framer-motion: ^4.1.17
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
@ -26171,6 +26189,7 @@ resolve@^2.0.0-next.3:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
prop-types: ^15.7.2 prop-types: ^15.7.2
@ -27670,6 +27689,7 @@ resolve@^2.0.0-next.3:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
history: ^4.10.1 history: ^4.10.1
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
@ -27949,6 +27969,7 @@ resolve@^2.0.0-next.3:
eslint: ^7.32.0 eslint: ^7.32.0
eslint-config-prettier: ^8.3.0 eslint-config-prettier: ^8.3.0
eslint-plugin-react: ^7.26.1 eslint-plugin-react: ^7.26.1
eslint-plugin-react-hooks: ^4.2.0
fork-ts-checker-webpack-plugin: ^6.4.0 fork-ts-checker-webpack-plugin: ^6.4.0
html-webpack-plugin: ^5.5.0 html-webpack-plugin: ^5.5.0
prop-types: ^15.7.2 prop-types: ^15.7.2