mirror of
https://github.com/reduxjs/redux-devtools.git
synced 2024-11-10 19:56:54 +03:00
Feat/use dark mode preference to set initial theme.light (#861)
* feat(redux-devtools-app): use `prefers-color-scheme` to set `theme.light` if user has not set a preferred theme * chore(@redux-devtools/app): fix lint error Error message: 0:0 error Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: test/__mocks__/styleMock.ts. The file must be included in at least one of the projects provided * chore: run prettier * feat(app): add theme color dropdown * refactor(rename): system preference option from default to auto
This commit is contained in:
parent
a9ef668d9b
commit
90cde4cfdf
|
@ -62,10 +62,12 @@ class App extends Component<Props> {
|
|||
function mapStateToProps(state: StoreState) {
|
||||
const instances = state.instances;
|
||||
const id = getActiveInstance(instances);
|
||||
const { themeColorPreference, ...themeData } = state.theme;
|
||||
|
||||
return {
|
||||
options: instances.options[id],
|
||||
section: state.section,
|
||||
theme: state.theme,
|
||||
theme: themeData,
|
||||
notification: state.notification,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,7 +8,10 @@ import configureStore from '../../../app/stores/panelStore';
|
|||
|
||||
import '../../views/devpanel.pug';
|
||||
import { Action, Store } from 'redux';
|
||||
import { StoreAction } from '@redux-devtools/app/lib/actions';
|
||||
import {
|
||||
applyMediaFeaturesPreferences,
|
||||
StoreAction,
|
||||
} from '@redux-devtools/app/lib/actions';
|
||||
import { PanelMessage } from '../../../app/middlewares/api';
|
||||
import { StoreStateWithoutSocket } from '../../../app/reducers/panel';
|
||||
import { PersistGate } from 'redux-persist/integration/react';
|
||||
|
@ -33,9 +36,20 @@ function renderDevTools() {
|
|||
unmountComponentAtNode(node!);
|
||||
clearTimeout(naTimeout);
|
||||
({ store, persistor } = configureStore(position, bgConnection));
|
||||
|
||||
const onBeforeLift = () => {
|
||||
if (store) {
|
||||
store.dispatch(applyMediaFeaturesPreferences());
|
||||
}
|
||||
};
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<PersistGate
|
||||
loading={null}
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
>
|
||||
<App position={position} />
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
|
|
|
@ -8,6 +8,7 @@ import configureStore from '../../../app/stores/windowStore';
|
|||
import { MonitorMessage } from '../../../app/middlewares/api';
|
||||
|
||||
import '../../views/window.pug';
|
||||
import { applyMediaFeaturesPreferences } from '@redux-devtools/app/lib/actions';
|
||||
|
||||
const position = location.hash;
|
||||
|
||||
|
@ -25,9 +26,17 @@ chrome.runtime.getBackgroundPage((window) => {
|
|||
bg.onMessage.addListener(update);
|
||||
update();
|
||||
|
||||
const onBeforeLift = () => {
|
||||
localStore.dispatch(applyMediaFeaturesPreferences());
|
||||
};
|
||||
|
||||
render(
|
||||
<Provider store={localStore}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
<PersistGate
|
||||
loading={null}
|
||||
persistor={persistor}
|
||||
onBeforeLift={onBeforeLift}
|
||||
>
|
||||
<App position={position} />
|
||||
</PersistGate>
|
||||
</Provider>,
|
||||
|
|
|
@ -3,6 +3,6 @@ module.exports = {
|
|||
setupFilesAfterEnv: ['<rootDir>/test/setup.ts'],
|
||||
testEnvironment: 'jsdom',
|
||||
moduleNameMapper: {
|
||||
'\\.css$': '<rootDir>/test/__mocks__/styleMock.ts',
|
||||
'\\.css$': '<rootDir>/test/__mocks__/styleMock.js',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ import { AuthStates, States } from 'socketcluster-client/lib/scclientsocket';
|
|||
import {
|
||||
CHANGE_SECTION,
|
||||
CHANGE_THEME,
|
||||
APPLY_MEDIA_FEATURES_PREFERENCES,
|
||||
SELECT_INSTANCE,
|
||||
SELECT_MONITOR,
|
||||
UPDATE_MONITOR_STATE,
|
||||
|
@ -44,9 +45,10 @@ import {
|
|||
import { Action } from 'redux';
|
||||
import { Features, State } from '../reducers/instances';
|
||||
import { MonitorStateMonitorState } from '../reducers/monitor';
|
||||
import { LiftedAction } from '@redux-devtools/core';
|
||||
import { LiftedAction, LiftedState } from '@redux-devtools/core';
|
||||
import { Data } from '../reducers/reports';
|
||||
import { LiftedState } from '@redux-devtools/core';
|
||||
import { prefersDarkColorScheme } from '../utils/media-queries';
|
||||
import { ThemeColorPreference } from '../reducers/theme';
|
||||
|
||||
let monitorReducer: (
|
||||
monitorProps: unknown,
|
||||
|
@ -66,9 +68,9 @@ export function changeSection(section: string): ChangeSectionAction {
|
|||
interface ChangeThemeFormData {
|
||||
readonly theme: Theme;
|
||||
readonly scheme: Scheme;
|
||||
readonly dark: boolean;
|
||||
readonly themeColorPreference: ThemeColorPreference;
|
||||
}
|
||||
interface ChangeThemeData {
|
||||
export interface ChangeThemeData {
|
||||
readonly formData: ChangeThemeFormData;
|
||||
}
|
||||
export interface ChangeThemeAction {
|
||||
|
@ -76,9 +78,43 @@ export interface ChangeThemeAction {
|
|||
readonly theme: Theme;
|
||||
readonly scheme: Scheme;
|
||||
readonly dark: boolean;
|
||||
readonly themeColorPreference: ThemeColorPreference;
|
||||
}
|
||||
|
||||
export interface ApplyMediaFeaturesPreferencesAction {
|
||||
readonly type: typeof APPLY_MEDIA_FEATURES_PREFERENCES;
|
||||
readonly prefersDarkColorScheme: boolean;
|
||||
}
|
||||
|
||||
export function changeTheme(data: ChangeThemeData): ChangeThemeAction {
|
||||
return { type: CHANGE_THEME, ...data.formData };
|
||||
const { themeColorPreference } = 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 {
|
||||
|
@ -564,6 +600,7 @@ export interface ErrorAction {
|
|||
export type StoreActionWithoutUpdateStateOrLiftedAction =
|
||||
| ChangeSectionAction
|
||||
| ChangeThemeAction
|
||||
| ApplyMediaFeaturesPreferencesAction
|
||||
| MonitorActionAction
|
||||
| SelectInstanceAction
|
||||
| SelectMonitorAction
|
||||
|
|
|
@ -4,6 +4,10 @@ import { Container, Form } from '@redux-devtools/ui';
|
|||
import { listSchemes, listThemes } from '@redux-devtools/ui/lib/utils/theme';
|
||||
import { changeTheme } from '../../actions';
|
||||
import { StoreState } from '../../reducers';
|
||||
import {
|
||||
defaultThemeColorPreference,
|
||||
themeColorPreferences,
|
||||
} from '../../reducers/theme';
|
||||
|
||||
type StateProps = ReturnType<typeof mapStateToProps>;
|
||||
type DispatchProps = ResolveThunks<typeof actionCreators>;
|
||||
|
@ -15,7 +19,8 @@ export class Themes extends Component<Props> {
|
|||
const formData = {
|
||||
theme: theme.theme,
|
||||
scheme: theme.scheme,
|
||||
dark: !theme.light,
|
||||
themeColorPreference:
|
||||
theme.themeColorPreference ?? defaultThemeColorPreference,
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -33,8 +38,10 @@ export class Themes extends Component<Props> {
|
|||
type: 'string',
|
||||
enum: listSchemes(),
|
||||
},
|
||||
dark: {
|
||||
type: 'boolean',
|
||||
themeColorPreference: {
|
||||
title: 'theme color',
|
||||
type: 'string',
|
||||
enum: themeColorPreferences as unknown as string[],
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
export const CHANGE_SECTION = 'main/CHANGE_SECTION';
|
||||
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 SET_STATE = 'devTools/SET_STATE';
|
||||
|
|
|
@ -7,7 +7,7 @@ import configureStore from './store/configureStore';
|
|||
import { CONNECT_REQUEST } from './constants/socketActionTypes';
|
||||
import App from './containers/App';
|
||||
import { StoreState } from './reducers';
|
||||
import { StoreAction } from './actions';
|
||||
import { StoreAction, applyMediaFeaturesPreferences } from './actions';
|
||||
|
||||
class Root extends Component {
|
||||
store?: Store<StoreState, StoreAction>;
|
||||
|
@ -27,11 +27,26 @@ class Root extends Component {
|
|||
this.persistor = persistor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
* @private
|
||||
*/
|
||||
private _checkMediaFeaturesPreferences = () => {
|
||||
if (this.store) {
|
||||
this.store.dispatch(applyMediaFeaturesPreferences());
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.store) return null;
|
||||
|
||||
return (
|
||||
<Provider store={this.store}>
|
||||
<PersistGate loading={null} persistor={this.persistor!}>
|
||||
<PersistGate
|
||||
loading={null}
|
||||
persistor={this.persistor!}
|
||||
onBeforeLift={this._checkMediaFeaturesPreferences}
|
||||
>
|
||||
<App />
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
|
|
|
@ -1,11 +1,25 @@
|
|||
import { Scheme, Theme } from '@redux-devtools/ui';
|
||||
import { CHANGE_THEME } from '../constants/actionTypes';
|
||||
import { Theme, Scheme } from '@redux-devtools/ui';
|
||||
import {
|
||||
CHANGE_THEME,
|
||||
APPLY_MEDIA_FEATURES_PREFERENCES,
|
||||
} from '../constants/actionTypes';
|
||||
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 {
|
||||
readonly theme: Theme;
|
||||
readonly scheme: Scheme;
|
||||
readonly light: boolean;
|
||||
readonly themeColorPreference?: ThemeColorPreference;
|
||||
}
|
||||
|
||||
export default function theme(
|
||||
|
@ -13,6 +27,7 @@ export default function theme(
|
|||
theme: 'default' as const,
|
||||
scheme: 'default' as const,
|
||||
light: true,
|
||||
themeColorPreference: defaultThemeColorPreference,
|
||||
},
|
||||
action: StoreAction
|
||||
) {
|
||||
|
@ -21,7 +36,22 @@ export default function theme(
|
|||
theme: action.theme,
|
||||
scheme: action.scheme,
|
||||
light: !action.dark,
|
||||
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;
|
||||
}
|
||||
|
|
13
packages/redux-devtools-app/src/utils/media-queries.ts
Normal file
13
packages/redux-devtools-app/src/utils/media-queries.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* @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;
|
||||
}
|
1
packages/redux-devtools-app/test/__mocks__/styleMock.js
Normal file
1
packages/redux-devtools-app/test/__mocks__/styleMock.js
Normal file
|
@ -0,0 +1 @@
|
|||
module.exports = {};
|
|
@ -1 +0,0 @@
|
|||
export default {};
|
Loading…
Reference in New Issue
Block a user