Skip to content

Commit

Permalink
✨ feat: 初步支持响应式工具函数
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Jan 25, 2023
1 parent cccb96b commit 244162d
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,18 @@ import type {
EmotionCX,
FullStylish,
FullToken,
ResponsiveStyleUtil,
ReturnStyleToUse,
StyleInputType,
Theme,
ThemeAppearance,
} from '@/types';
import { isReactCssResult } from '@/utils';

import { type CSSObject } from './css';
import { reactCss } from './react';
import { convertResponsiveStyleToString, useResponsiveMap } from './response';

import { type CSSObject } from '../css';
import { reactCss } from '../react';

export interface CreateStylesTheme extends CommonStyleUtils {
token: FullToken;
Expand Down Expand Up @@ -51,56 +54,73 @@ export type StyleOrGetStyleFn<Input extends StyleInputType, Props> =
*/
export const createStyles =
<Props, Input extends StyleInputType = StyleInputType>(
styleOrGetStyleFn: StyleOrGetStyleFn<Input, Props>,
styleOrGetStyle: StyleOrGetStyleFn<Input, Props>,
) =>
(props?: Props): ReturnStyles<Input> => {
const theme = useTheme();
const responsiveMap = useResponsiveMap();
const { css, cx } = useEmotion();

const styles = useMemo(() => {
let tempStyles: ReturnStyleToUse<Input>;

// 函数场景
if (styleOrGetStyleFn instanceof Function) {
if (styleOrGetStyle instanceof Function) {
const { stylish, appearance, isDarkMode, prefixCls, ...token } = theme;

// 由于使用了 reactCss 作为基础样式工具,因此在使用 cx 级联 className 时需要使用特殊处理的 cx,要将 reactCss 的产出转为 css 产物
const reactCx: EmotionCX = (...classNames) =>
cx(...classNames.map((c) => (isReactCssResult(c) ? css(c) : c)));

tempStyles = styleOrGetStyleFn(
{ token, stylish, appearance, cx: reactCx, css: reactCss, isDarkMode, prefixCls },
// 创建响应式断点选择器的工具函数
const responsive: ResponsiveStyleUtil = (styles) =>
convertResponsiveStyleToString(styles, responsiveMap);

tempStyles = styleOrGetStyle(
{
token,
stylish,
appearance,
isDarkMode,
prefixCls,
// 工具函数们
cx: reactCx,
css: reactCss,
r: responsive,
},
props!,
) as any;
}

// 没有函数时直接就是 object 或者 string
else {
tempStyles = styleOrGetStyleFn as any;
tempStyles = styleOrGetStyle as any;
}

if (typeof tempStyles === 'object') {
// 判断是否是直接用 reactCSS 生成的
// 判断是否是用 reactCSS 生成的
if (isReactCssResult(tempStyles)) {
// 如果是用 reactCss 生成的话,需要用 className 的 css 做一层转换
tempStyles = css(tempStyles) as any;
} else {
// 不是的话就是直接是 css object,需要转换了
// 不是的话就是直接是 复合的 css object
tempStyles = Object.fromEntries(
Object.entries(tempStyles).map(([key, value]) => {
// 这里有可能是 x:{ color:red } 也可能是 c:reactCss`color:red`;
// 但无论哪种,都可以直接用 css 包一下转换掉
// 但无论哪种,都可以直接用 css 包一下转换成 className
if (typeof value === 'object') {
return [key, css(value as CSSObject)];
}

// 这里只可能是 c: css`color:red`; css 直接来自 antd-style
// 这里只可能是 c: css`color:red`; css 直接来自 antd-style,因此啥也不用处理
return [key, value];
}),
) as any;
}
}

return tempStyles;
}, [styleOrGetStyleFn, props, theme]);
}, [styleOrGetStyle, props, theme]);

return useMemo(() => {
const { prefixCls, ...res } = theme;
Expand Down
55 changes: 55 additions & 0 deletions src/functions/createStyles/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { reactCss } from '@/functions/react';
import { useAntdToken } from '@/hooks';
import type { Breakpoint, ResponsiveKey, ResponsiveMap } from '@/types';
import { isReactCssResult } from '@/utils';
import type { SerializedStyles } from '@emotion/react';
import type { CSSObject } from '@emotion/serialize';
import { useMemo } from 'react';

export const useResponsiveMap = (): ResponsiveMap => {
const token = useAntdToken();

const breakpoints: Record<Breakpoint, string> = {
xs: `@media (max-width: ${token.screenXSMax}px)`,
sm: `@media (max-width: ${token.screenSMMax}px)`,
md: `@media (max-width: ${token.screenMDMax}px)`,

lg: `@media (max-width: ${token.screenLGMax}px)`,
xl: `@media (max-width: ${token.screenXLMax}px)`,
xxl: `@media (min-width: ${token.screenXXLMin}px)`,
};

return useMemo(
() => ({
...breakpoints,
mobile: breakpoints.xs,
tablet: breakpoints.md,
laptop: breakpoints.lg,
desktop: breakpoints.xxl,
}),
[token],
);
};

/**
* 将响应式对象转换为
* @param obj
* @param map
*/
export const convertResponsiveStyleToString = (
obj: Partial<Record<ResponsiveKey, SerializedStyles | CSSObject>>,
map: ResponsiveMap,
): any => {
return Object.entries(obj)
.map(([key, value]) => {
let str: SerializedStyles | CSSObject = value;

if (!isReactCssResult(value)) {
str = reactCss(value);
}

// @ts-ignore
return map[key] ? `${map[key]} {${str.styles}}` : '';
})
.join('');
};
11 changes: 10 additions & 1 deletion src/types/function.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ClassNamesArg } from '@emotion/css/create-instance';
import { CSSInterpolation, SerializedStyles } from '@emotion/serialize';
import { CSSInterpolation, CSSObject, SerializedStyles } from '@emotion/serialize';
import { ThemeConfig } from 'antd/es/config-provider/context';

import { ThemeAppearance } from './appearance';
import { ResponsiveKey } from './response';
import type { AntdStylish, AntdToken, AppearanceState, FullToken } from './theme';

export interface EmotionReactCss {
Expand All @@ -11,9 +12,17 @@ export interface EmotionReactCss {
}
export type EmotionCX = (...classNames: ClassNamesArg[]) => string;

export type ResponsiveStyleUtil = (
breakpoints: Partial<Record<ResponsiveKey, CSSObject | SerializedStyles>>,
) => any;

export interface CommonStyleUtils {
cx: EmotionCX;
css: EmotionReactCss;
/**
* 可以快速创建响应式样式的工具函数
*/
r: ResponsiveStyleUtil;
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from './appearance';
export * from './css';
export * from './function';
export * from './genericUtils';
export * from './response';
export * from './styled';
export * from './theme';
19 changes: 19 additions & 0 deletions src/types/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type Breakpoint =
| 'xxl'
| 'xl'
| 'lg'
| 'md'
| 'sm'
/**
* 最小断点,可以作为移动端的判断断点
*/
| 'xs';

export type DeviceScreen = 'mobile' | 'tablet' | 'laptop' | 'desktop';

export type ResponsiveKey = Breakpoint | DeviceScreen;

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ResponsiveMap extends Record<ResponsiveKey, string> {
// 在此处扩展响应式映射表
}

0 comments on commit 244162d

Please sign in to comment.