2019-04-30 11:11:25 +08:00
|
|
|
// 主题管理
|
2019-09-09 13:48:08 +08:00
|
|
|
import cx from 'classnames';
|
2019-06-10 22:53:21 +08:00
|
|
|
import React from 'react';
|
2020-02-29 09:59:17 +08:00
|
|
|
import hoistNonReactStatic from 'hoist-non-react-statics';
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
export type ClassValue =
|
|
|
|
| string
|
|
|
|
| number
|
|
|
|
| ClassDictionary
|
|
|
|
| ClassArray
|
|
|
|
| undefined
|
|
|
|
| null
|
|
|
|
| boolean;
|
2019-04-30 11:11:25 +08:00
|
|
|
|
|
|
|
interface ClassDictionary {
|
2019-11-07 10:41:14 +08:00
|
|
|
[id: string]: any;
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is the only way I found to break circular references between ClassArray and ClassValue
|
|
|
|
// https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540
|
2019-09-09 13:48:08 +08:00
|
|
|
interface ClassArray extends Array<ClassValue> {} // tslint:disable-line no-empty-interface
|
2019-04-30 11:11:25 +08:00
|
|
|
|
|
|
|
export type ClassNamesFn = (...classes: ClassValue[]) => string;
|
|
|
|
|
|
|
|
interface ThemeConfig {
|
2019-11-07 10:41:14 +08:00
|
|
|
classPrefix?: string;
|
|
|
|
renderers?: {
|
|
|
|
[propName: string]: any;
|
|
|
|
};
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
[propsName: string]: any;
|
2019-09-09 13:48:08 +08:00
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
const themes: {
|
2019-11-07 10:41:14 +08:00
|
|
|
[propName: string]: ThemeConfig;
|
2019-04-30 11:11:25 +08:00
|
|
|
} = {
|
2019-11-07 10:41:14 +08:00
|
|
|
default: {}
|
2019-04-30 11:11:25 +08:00
|
|
|
};
|
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
export function theme(name: string, config: Partial<ThemeConfig>) {
|
2019-11-07 10:41:14 +08:00
|
|
|
themes[name] = {
|
|
|
|
...config
|
|
|
|
};
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
const fns: {
|
2019-11-07 10:41:14 +08:00
|
|
|
[propName: string]: (...classes: ClassValue[]) => string;
|
2019-04-30 11:11:25 +08:00
|
|
|
} = {};
|
2019-09-09 13:48:08 +08:00
|
|
|
export function makeClassnames(ns?: string) {
|
2019-11-07 10:41:14 +08:00
|
|
|
if (ns && fns[ns]) {
|
|
|
|
return fns[ns];
|
|
|
|
}
|
|
|
|
|
|
|
|
const fn = (...classes: ClassValue[]) => {
|
|
|
|
const str = cx(...(classes as any));
|
|
|
|
return str && ns
|
|
|
|
? str
|
|
|
|
.replace(/(^|\s)([A-Z])/g, '$1' + ns + '$2')
|
|
|
|
.replace(/(^|\s)\:/g, '$1')
|
|
|
|
: str || '';
|
|
|
|
};
|
|
|
|
|
|
|
|
ns && (fns[ns] = fn);
|
|
|
|
return fn;
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export type ThemeInstance = ThemeConfig & {
|
2019-11-07 10:41:14 +08:00
|
|
|
getRendererConfig: (name?: string) => any;
|
|
|
|
classnames: ClassNamesFn;
|
2019-04-30 11:11:25 +08:00
|
|
|
};
|
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
export function hasTheme(theme: string): boolean {
|
2019-11-07 10:41:14 +08:00
|
|
|
return !!themes[theme];
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
export function setDefaultTheme(theme: string) {
|
2019-11-07 10:41:14 +08:00
|
|
|
if (hasTheme(theme)) {
|
|
|
|
defaultTheme = theme;
|
|
|
|
}
|
2019-08-22 19:54:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function classnames(...classes: ClassValue[]) {
|
2019-11-07 10:41:14 +08:00
|
|
|
return getTheme(defaultTheme).classnames(...classes);
|
2019-08-22 19:54:03 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getClassPrefix() {
|
2019-11-07 10:41:14 +08:00
|
|
|
return getTheme(defaultTheme).classPrefix;
|
2019-08-22 19:54:03 +08:00
|
|
|
}
|
|
|
|
|
2019-09-09 13:48:08 +08:00
|
|
|
export function getTheme(theme: string): ThemeInstance {
|
2019-11-07 10:41:14 +08:00
|
|
|
if (!themes[theme]) {
|
|
|
|
throw new Error(`Theme with name "${theme}" does not exist!`);
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
const config = themes[theme];
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
if (!config.getRendererConfig) {
|
|
|
|
config.getRendererConfig = (name?: string) =>
|
|
|
|
config.renderers && name ? config.renderers[name] : null;
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
if (!config.classnames) {
|
|
|
|
const ns = config.classPrefix;
|
|
|
|
config.classnames = config.classnames || makeClassnames(ns);
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return config as ThemeInstance;
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface ThemeProps {
|
2019-11-07 10:41:14 +08:00
|
|
|
classPrefix: string;
|
|
|
|
classnames: ClassNamesFn;
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export const ThemeContext = React.createContext('theme');
|
2019-09-09 13:48:08 +08:00
|
|
|
export let defaultTheme: string = 'default';
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
export function themeable<
|
2020-06-02 20:41:51 +08:00
|
|
|
T extends React.ComponentType<React.ComponentProps<T> & ThemeProps>
|
2019-11-07 10:41:14 +08:00
|
|
|
>(ComposedComponent: T) {
|
2020-06-02 20:41:51 +08:00
|
|
|
type OuterProps = JSX.LibraryManagedAttributes<
|
|
|
|
T,
|
|
|
|
Omit<React.ComponentProps<T>, keyof ThemeProps>
|
|
|
|
> & {
|
2019-11-07 10:41:14 +08:00
|
|
|
theme?: string;
|
|
|
|
classPrefix?: string;
|
|
|
|
classnames?: ClassNamesFn;
|
|
|
|
};
|
|
|
|
|
2020-05-27 19:31:50 +08:00
|
|
|
const result = hoistNonReactStatic(
|
2020-06-02 20:41:51 +08:00
|
|
|
class extends React.Component<OuterProps> {
|
2020-05-27 19:31:50 +08:00
|
|
|
static displayName = `Themeable(${
|
|
|
|
ComposedComponent.displayName || ComposedComponent.name
|
|
|
|
})`;
|
|
|
|
static contextType = ThemeContext;
|
|
|
|
static ComposedComponent = ComposedComponent;
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const theme: string = this.props.theme || this.context || defaultTheme;
|
|
|
|
const config = hasTheme(theme)
|
|
|
|
? getTheme(theme)
|
|
|
|
: getTheme(defaultTheme);
|
|
|
|
const injectedProps: {
|
|
|
|
classPrefix: string;
|
|
|
|
classnames: ClassNamesFn;
|
|
|
|
} = {
|
|
|
|
classPrefix: config.classPrefix as string,
|
|
|
|
classnames: config.classnames
|
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ThemeContext.Provider value={theme}>
|
|
|
|
<ComposedComponent
|
2020-06-02 20:41:51 +08:00
|
|
|
{...(this.props as JSX.LibraryManagedAttributes<
|
|
|
|
T,
|
|
|
|
React.ComponentProps<T>
|
|
|
|
>)}
|
2020-05-27 19:31:50 +08:00
|
|
|
{...injectedProps}
|
|
|
|
/>
|
|
|
|
</ThemeContext.Provider>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
ComposedComponent
|
|
|
|
);
|
2020-05-26 20:28:09 +08:00
|
|
|
|
|
|
|
return result as typeof result & {
|
2019-11-07 10:41:14 +08:00
|
|
|
ComposedComponent: T;
|
|
|
|
};
|
2019-09-09 13:48:08 +08:00
|
|
|
}
|