This commit is contained in:
pincman
2024-05-15 06:36:45 +08:00
parent 112b737cc4
commit a9638cfa38
15 changed files with 634 additions and 184 deletions
+1
View File
@@ -23,6 +23,7 @@
"lodash": "^4.17.21",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-use": "^17.5.0",
"zustand": "^4.5.2"
},
"devDependencies": {
+51
View File
@@ -0,0 +1,51 @@
import { StyleProvider } from '@ant-design/cssinjs';
import { ConfigProvider, App as AntdApp } from 'antd';
import { FC, useMemo } from 'react';
import $styles from './app.module.css';
import { localeData } from './components/demo/constants';
import ContextDemo from './components/demo/context';
import CustomDemo from './components/demo/custom';
import { useLocale } from './components/demo/hooks';
import { useAntdAlgorithm } from './components/theme/hooks';
export const Wrapper: FC = () => {
const locale = useLocale();
const antdLocaleData = useMemo(() => {
if (!Object.keys(localeData).find((v) => v === locale.name)) {
return localeData[0];
}
return localeData[locale.name];
}, [locale.name]);
// const themeState = useTheme();
// const algorithm = useMemo(() => {
// const result = [themeState.compact ? theme.compactAlgorithm : theme.defaultAlgorithm];
// if (themeState.mode === 'dark') result.push(theme.darkAlgorithm);
// return result;
// }, [themeState]);
const algorithm = useAntdAlgorithm();
return (
<ConfigProvider
locale={antdLocaleData}
theme={{
algorithm,
token: {},
}}
>
<StyleProvider hashPriority="high">
<AntdApp>
<div className={$styles.app}>
{/* <StateDemo />
<EffectDemo />
<RefDemo />
<MemoDemo />
<CallbackDemo /> */}
<ContextDemo />
{/* <ReducerDemo /> */}
<CustomDemo />
</div>
</AntdApp>
</StyleProvider>
</ConfigProvider>
);
};
+16 -13
View File
@@ -1,15 +1,16 @@
import { StyleProvider } from '@ant-design/cssinjs';
import { ConfigProvider, theme, App as AntdApp } from 'antd';
import { ConfigProvider, App as AntdApp } from 'antd';
// import 'dayjs/locale/zh-cn';
import { FC, useMemo } from 'react';
import $styles from './app.module.css';
import { localeData } from './components/demo/constants';
import ContextDemo, { Locale } from './components/demo/context';
import CustomDemo from './components/demo/custom';
import { useLocale, useTheme } from './components/demo/hooks';
import ReducerDemo, { Theme } from './components/demo/reducer';
import { Locale } from './components/demo/context';
import { useLocale } from './components/demo/hooks';
import Theme from './components/theme';
import ThemeDemo from './components/theme/demo';
import { useAntdAlgorithm } from './components/theme/hooks';
const Wrapper: FC = () => {
const locale = useLocale();
@@ -19,12 +20,13 @@ const Wrapper: FC = () => {
}
return localeData[locale.name];
}, [locale.name]);
const themeState = useTheme();
const algorithm = useMemo(() => {
const result = [themeState.compact ? theme.compactAlgorithm : theme.defaultAlgorithm];
if (themeState.mode === 'dark') result.push(theme.darkAlgorithm);
return result;
}, [themeState]);
// const themeState = useTheme();
// const algorithm = useMemo(() => {
// const result = [themeState.compact ? theme.compactAlgorithm : theme.defaultAlgorithm];
// if (themeState.mode === 'dark') result.push(theme.darkAlgorithm);
// return result;
// }, [themeState]);
const algorithm = useAntdAlgorithm();
return (
<ConfigProvider
locale={antdLocaleData}
@@ -40,10 +42,11 @@ const Wrapper: FC = () => {
<EffectDemo />
<RefDemo />
<MemoDemo />
<CallbackDemo /> */}
<CallbackDemo />
<ContextDemo />
<ReducerDemo />
<CustomDemo />
<CustomDemo /> */}
<ThemeDemo />
</div>
</AntdApp>
</StyleProvider>
+5 -1
View File
@@ -21,12 +21,16 @@ export type ThemeState = {
export enum ThemeActionType {
CHANGE_MODE = 'change_mode',
TOOGLE_MODE = 'toggle_mode',
CHANGE_COMPACT = 'change_compact',
TOOGLE_COMPACT = 'toggle_compact',
}
export type ThemeAction =
| { type: `${ThemeActionType.CHANGE_MODE}`; value: `${ThemeMode}` }
| { type: `${ThemeActionType.CHANGE_COMPACT}`; value: boolean };
| { type: `${ThemeActionType.TOOGLE_MODE}` }
| { type: `${ThemeActionType.CHANGE_COMPACT}`; value: boolean }
| { type: `${ThemeActionType.TOOGLE_COMPACT}` };
export type ThemeContextType = {
state: ThemeState;
+167 -2
View File
@@ -1,2 +1,167 @@
export * from './utils';
export * from './types';
import { capitalize } from 'lodash';
import { create, StateCreator, Mutate, StoreApi, UseBoundStore } from 'zustand';
import {
subscribeWithSelector,
devtools,
persist,
PersistOptions,
DevtoolsOptions,
redux,
} from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
export type ZustandHookSelectors<StateType> = {
[Key in keyof StateType as `use${Capitalize<string & Key>}`]: () => StateType[Key];
};
export interface ZustandGetterSelectors<StateType> {
getters: {
[key in keyof StateType]: () => StateType[key];
};
}
/**
* 创建包含订阅,immer以及devtoools功能的普通状态商店
* @param creator
* @param devtoolsOptions
*/
export const createStore = <T extends object>(
creator: StateCreator<
T,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>,
devtoolsOptions?: DevtoolsOptions,
) => {
return create<T>()(subscribeWithSelector(immer(devtools(creator, devtoolsOptions))));
};
/**
* 创建包含订阅,immer以及devtoools功能的普通状态商店
* 同时支持自动存储到客户端,默认存储到localstorage
* @param creator
* @param persistOptions
* @param devtoolsOptions
*/
export const createPersistStore = <T extends object, P = T>(
creator: StateCreator<
T,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
['zustand/persist', P],
]
>,
persistOptions: PersistOptions<T, P>,
devtoolsOptions?: DevtoolsOptions,
) => {
return create<T>()(
subscribeWithSelector(
immer(devtools(persist(creator as unknown as any, persistOptions), devtoolsOptions)),
),
);
};
/**
* 创建包含订阅,immer以及devtoools功能的reducer状态商店
* 同时支持自动存储到客户端,默认存储到localstorage
* @param reducer
* @param initialState
* @param devtoolsOptions
*/
export const createReduxStore = <
T extends object,
A extends {
type: string;
},
>(
reducer: (state: T, action: A) => T,
initialState: T,
devtoolsOptions?: DevtoolsOptions,
) => create(subscribeWithSelector(immer(devtools(redux(reducer, initialState), devtoolsOptions))));
/**
* 创建包含订阅,immer以及devtoools功能的reducer状态商店
* @param reducer
* @param initialState
* @param persistOptions
* @param devtoolsOptions
*/
export const createPersistReduxStore = <
T extends object,
A extends {
type: string;
},
P = T,
>(
reducer: (state: T, action: A) => T,
initialState: T,
persistOptions: PersistOptions<T, P>,
devtoolsOptions?: DevtoolsOptions,
) =>
create(
subscribeWithSelector(
immer(
devtools(
persist(redux(reducer, initialState), persistOptions as any),
devtoolsOptions,
),
),
),
);
/**
* 直接通过getters获取状态值,比如store.getters.xxx()
* @param store
*/
export function createStoreGetters<T extends object>(
store: UseBoundStore<
Mutate<
StoreApi<T>,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>
>,
) {
const storeIn = store as any;
storeIn.getters = {};
Object.keys(storeIn.getState()).forEach((key) => {
const selector = (state: T) => state[key as keyof T];
storeIn.getters[key] = () => storeIn(selector);
});
return storeIn as typeof store & ZustandGetterSelectors<T>;
}
/**
* 直接通过类似hooks的方法获取状态值,比如store.useXxx()
* @param store
*/
export function createStoreHooks<T extends Record<string, any>>(
store: UseBoundStore<
Mutate<
StoreApi<T>,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>
>,
) {
const storeIn = store as any;
Object.keys(storeIn.getState()).forEach((key) => {
const selector = (state: T) => state[key as keyof T];
storeIn[`use${capitalize(key)}`] = () => storeIn(selector);
});
return storeIn as typeof store & ZustandHookSelectors<T>;
}
-8
View File
@@ -1,8 +0,0 @@
export type ZustandHookSelectors<StateType> = {
[Key in keyof StateType as `use${Capitalize<string & Key>}`]: () => StateType[Key];
};
export interface ZustandGetterSelectors<StateType> {
getters: {
[key in keyof StateType]: () => StateType[key];
};
}
-160
View File
@@ -1,160 +0,0 @@
import { capitalize } from 'lodash';
import { create, Mutate, StateCreator, StoreApi, UseBoundStore } from 'zustand';
import {
subscribeWithSelector,
devtools,
persist,
PersistOptions,
DevtoolsOptions,
redux,
} from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { ZustandGetterSelectors, ZustandHookSelectors } from './types';
/**
* 创建包含订阅,immer以及devtoools功能的普通状态商店
* @param creator
* @param devtoolsOptions
*/
export const createStore = <T extends object>(
creator: StateCreator<
T,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>,
devtoolsOptions?: DevtoolsOptions,
) => {
return create<T>()(subscribeWithSelector(immer(devtools(creator, devtoolsOptions))));
};
/**
* 创建包含订阅,immer以及devtoools功能的普通状态商店
* 同时支持自动存储到客户端,默认存储到localstorage
* @param creator
* @param persistOptions
* @param devtoolsOptions
*/
export const createPersistStore = <T extends object, P = T>(
creator: StateCreator<
T,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
['zustand/persist', P],
]
>,
persistOptions: PersistOptions<T, P>,
devtoolsOptions?: DevtoolsOptions,
) => {
return create<T>()(
subscribeWithSelector(
immer(devtools(persist(creator as unknown as any, persistOptions), devtoolsOptions)),
),
);
};
/**
* 创建包含订阅,immer以及devtoools功能的reducer状态商店
* 同时支持自动存储到客户端,默认存储到localstorage
* @param reducer
* @param initialState
* @param devtoolsOptions
*/
export const createReduxStore = <
T extends object,
A extends {
type: string;
},
>(
reducer: (state: T, action: A) => T,
initialState: T,
devtoolsOptions?: DevtoolsOptions,
) => create(subscribeWithSelector(immer(devtools(redux(reducer, initialState), devtoolsOptions))));
/**
* 创建包含订阅,immer以及devtoools功能的reducer状态商店
* @param reducer
* @param initialState
* @param persistOptions
* @param devtoolsOptions
*/
export const createPersistReduxStore = <
T extends object,
A extends {
type: string;
},
P = T,
>(
reducer: (state: T, action: A) => T,
initialState: T,
persistOptions: PersistOptions<T, P>,
devtoolsOptions?: DevtoolsOptions,
) =>
create(
subscribeWithSelector(
immer(
devtools(
persist(redux(reducer, initialState), persistOptions as any),
devtoolsOptions,
),
),
),
);
/**
* 直接通过getters获取状态值,比如store.getters.xxx()
* @param store
*/
export function createStoreGetters<T extends object>(
store: UseBoundStore<
Mutate<
StoreApi<T>,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>
>,
) {
const storeIn = store as any;
storeIn.getters = {};
Object.keys(storeIn.getState()).forEach((key) => {
const selector = (state: T) => state[key as keyof T];
storeIn.getters[key] = () => storeIn(selector);
});
return storeIn as typeof store & ZustandGetterSelectors<T>;
}
/**
* 直接通过类似hooks的方法获取状态值,比如store.useXxx()
* @param store
*/
export function createStoreHooks<T extends Record<string, any>>(
store: UseBoundStore<
Mutate<
StoreApi<T>,
[
['zustand/subscribeWithSelector', never],
['zustand/immer', never],
['zustand/devtools', never],
]
>
>,
) {
const storeIn = store as any;
Object.keys(storeIn.getState()).forEach((key) => {
const selector = (state: T) => state[key as keyof T];
storeIn[`use${capitalize(key)}`] = () => storeIn(selector);
});
return storeIn as typeof store & ZustandHookSelectors<T>;
}
@@ -0,0 +1,6 @@
import { ThemeState } from './types';
export const defaultThemeConfig: ThemeState = {
mode: 'light',
compact: false,
};
+48
View File
@@ -0,0 +1,48 @@
import { Calendar, Switch } from 'antd';
import { FC } from 'react';
import { useTheme, useThemeActions } from './hooks';
import $styles from './style.module.css';
const Setting: FC = () => {
const { mode, compact } = useTheme();
const { toggleMode, toggleCompact } = useThemeActions();
return (
<>
<Switch
checkedChildren="🌛"
unCheckedChildren="☀️"
onChange={toggleMode}
checked={mode === 'dark'}
defaultChecked={mode === 'dark'}
/>
<Switch
checkedChildren="紧凑"
unCheckedChildren="正常"
onChange={toggleCompact}
checked={compact}
defaultChecked={compact}
/>
</>
);
};
const ThemeDemo: FC = () => {
const { mode, compact } = useTheme();
return (
<div className={$styles.container}>
<h2 className="tw-text-center">Theme Setting Demo</h2>
<div className="tw-flex tw-items-center tw-flex-col">
<p>: {mode === 'dark' ? '暗黑' : '明亮'}</p>
<p>: {compact ? '是' : '否'}</p>
<div className="tw-flex-auto tw-mb-5">
<Setting />
</div>
<div className="tw-max-w-md">
<Calendar fullscreen={false} />
</div>
</div>
</div>
);
};
export default ThemeDemo;
+46
View File
@@ -0,0 +1,46 @@
import { theme } from 'antd';
import { omit } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { useThemeStore } from './store';
import { ThemeMode } from './types';
/**
* 获取主题状态
*/
export const useTheme = () => useThemeStore(useShallow((state) => omit(state, ['dispatch'])));
/**
* 获取Antd主题算法
*/
export const useAntdAlgorithm = () => {
const { mode, compact } = useTheme();
return useMemo(() => {
const result = [compact ? theme.compactAlgorithm : theme.defaultAlgorithm];
if (mode === 'dark') result.push(theme.darkAlgorithm);
return result;
}, [mode, compact]);
};
/**
* 主题操作函数
*/
export const useThemeActions = () => {
const dispatch = useThemeStore((state) => state.dispatch);
return {
changeMode: useCallback(
(v: `${ThemeMode}`) => dispatch({ type: 'change_mode', value: v }),
[],
),
toggleMode: useCallback(() => dispatch({ type: 'toggle_mode' }), []),
changeCompact: useCallback(
(v: boolean) => dispatch({ type: 'change_compact', value: v }),
[],
),
toggleCompact: useCallback(
useCallback(() => dispatch({ type: 'toggle_compact' }), []),
[],
),
};
};
+50
View File
@@ -0,0 +1,50 @@
import { debounce, isNil } from 'lodash';
import { FC, ReactNode } from 'react';
import { useLifecycles } from 'react-use';
import { useThemeStore } from './store';
const Theme: FC<{ children?: ReactNode }> = ({ children }) => {
let unSub: () => void;
useLifecycles(
() => {
useThemeStore.subscribe(
(state) => state.mode,
(mode) => {
debounce(() => {
console.log(mode);
});
const body = document.getElementsByTagName('body');
if (body.length) {
body[0].classList.remove('light');
body[0].classList.remove('dark');
body[0].classList.add(mode === 'dark' ? 'dark' : 'light');
}
},
// debounce(
// () => {
// const body = document.getElementsByTagName('body');
// if (body.length) {
// body[0].classList.remove('light');
// body[0].classList.remove('dark');
// body[0].classList.add(mode === 'dark' ? 'dark' : 'light');
// }
// },
// 10,
// {},
// ),
{
fireImmediately: true,
},
);
},
() => {
if (!isNil(unSub)) unSub();
},
);
return <>{children}</>;
};
export default Theme;
+32
View File
@@ -0,0 +1,32 @@
import { produce } from 'immer';
import { Reducer } from 'react';
import { ThemeAction } from '../demo/types';
import { createPersistReduxStore } from '../store';
import { defaultThemeConfig } from './constants';
import { ThemeState } from './types';
const ThemeReducer: Reducer<ThemeState, ThemeAction> = produce((draft, action) => {
switch (action.type) {
case 'change_mode':
draft.mode = action.value;
break;
case 'toggle_mode':
draft.mode = draft.mode === 'dark' ? 'light' : 'dark';
break;
case 'change_compact':
draft.compact = action.value;
break;
case 'toggle_compact':
draft.compact = !draft.compact;
break;
default:
break;
}
});
export const useThemeStore = createPersistReduxStore(ThemeReducer, defaultThemeConfig, {
name: 'theme',
partialize: (state) => ({ mode: state.mode, compact: state.compact }),
});
@@ -0,0 +1,9 @@
.container {
@apply tw-bg-neutral-100/40 tw-shadow-black/20 tw-backdrop-blur-sm tw-shadow-md tw-rounded-md tw-p-5 tw-m-5 tw-min-w-[20rem];
}
body(:global(.dark)) {
.container {
@apply tw-bg-neutral-800/40 tw-shadow-slate-100/30;
}
}
+9
View File
@@ -0,0 +1,9 @@
export enum ThemeMode {
LIGHT = 'light',
DARK = 'dark',
}
export type ThemeState = {
mode: `${ThemeMode}`;
compact: boolean;
};