refactor: tree support cssinjs (#34258)

* chore: part of tree

* chore: tree cssinjs

* chore: fix lint

* refactor: RTL support

* fix: checkbox hover logic

* chore: rtl use same style

* fix: check style priority
This commit is contained in:
二货机器人 2022-03-02 20:32:25 +08:00 committed by GitHub
parent 20ae690b14
commit 989a27ada8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 489 additions and 26 deletions

View File

@ -20,6 +20,8 @@ const defaultDesignToken: DesignToken = {
textColor: new TinyColor('#000').setAlpha(0.85).toRgbString(), textColor: new TinyColor('#000').setAlpha(0.85).toRgbString(),
textColorDisabled: new TinyColor('#000').setAlpha(0.25).toRgbString(), textColorDisabled: new TinyColor('#000').setAlpha(0.25).toRgbString(),
itemHoverBackground: '#f5f5f5',
height: 32, height: 32,
padding: 16, padding: 16,

View File

@ -22,6 +22,8 @@ export interface DesignToken {
textColor: string; textColor: string;
textColorDisabled: string; textColorDisabled: string;
itemHoverBackground: string;
height: number; height: number;
padding: number; padding: number;
@ -40,6 +42,7 @@ export interface DerivativeToken extends Omit<DesignToken, 'duration'> {
primaryActiveColor: string; primaryActiveColor: string;
errorHoverColor: string; errorHoverColor: string;
errorActiveColor: string; errorActiveColor: string;
itemActiveBackground: string;
linkColor: string; linkColor: string;
fontSizeSM: number; fontSizeSM: number;
@ -51,6 +54,9 @@ export interface DerivativeToken extends Omit<DesignToken, 'duration'> {
duration: string; duration: string;
durationFast: string; durationFast: string;
// TMP
tmpPrimaryHoverColorWeak: string;
} }
export { useStyleRegister }; export { useStyleRegister };
@ -63,12 +69,15 @@ function derivative(designToken: DesignToken): DerivativeToken {
return { return {
...designToken, ...designToken,
tmpPrimaryHoverColorWeak: primaryColors[0],
primaryHoverColor: primaryColors[4], primaryHoverColor: primaryColors[4],
primaryActiveColor: primaryColors[6], primaryActiveColor: primaryColors[6],
errorHoverColor: errorColors[4], errorHoverColor: errorColors[4],
errorActiveColor: errorColors[6], errorActiveColor: errorColors[6],
itemActiveBackground: primaryColors[1],
linkColor: designToken.primaryColor, linkColor: designToken.primaryColor,
fontSizeSM: designToken.fontSize - 2, fontSizeSM: designToken.fontSize - 2,
fontSizeLG: designToken.fontSize + 2, fontSizeLG: designToken.fontSize + 2,

View File

@ -1,6 +1,3 @@
// import '../../style/index.less';
// import './index.less';
// deps-lint-skip-all // deps-lint-skip-all
import { CSSObject, Keyframes } from '@ant-design/cssinjs'; import { CSSObject, Keyframes } from '@ant-design/cssinjs';
import { DerivativeToken, useStyleRegister, useToken, resetComponent } from '../../_util/theme'; import { DerivativeToken, useStyleRegister, useToken, resetComponent } from '../../_util/theme';
@ -66,6 +63,7 @@ export const genCheckboxStyle = (
position: 'relative', position: 'relative',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
lineHeight: 1, lineHeight: 1,
cursor: 'pointer',
// Wrapper > Checkbox > input // Wrapper > Checkbox > input
'&-input': { '&-input': {
@ -135,12 +133,25 @@ export const genCheckboxStyle = (
}, },
}, },
// ==================== Checked ==================== // ===================== Hover =====================
// Wrapper // Wrapper
[`${wrapperCls}:hover ${checkboxCls}:after`]: { [`${wrapperCls}:hover ${checkboxCls}:after`]: {
visibility: 'visible', visibility: 'visible',
}, },
// Wrapper & Wrapper > Checkbox
[`
${wrapperCls}:hover:not(${wrapperCls}-disabled),
${checkboxCls}:hover:not(${checkboxCls}-disabled),
${checkboxCls}-input:focus +
`]: {
[`${checkboxCls}-inner`]: {
borderColor: token.primaryColor,
},
},
// ==================== Checked ====================
// Wrapper > Checkbox // Wrapper > Checkbox
[`${checkboxCls}-checked`]: { [`${checkboxCls}-checked`]: {
[`${checkboxCls}-inner`]: { [`${checkboxCls}-inner`]: {
@ -172,7 +183,7 @@ export const genCheckboxStyle = (
// ==================== Disable ==================== // ==================== Disable ====================
// Wrapper // Wrapper
[`${wrapperCls}:disabled`]: { [`${wrapperCls}-disabled`]: {
cursor: 'not-allowed', cursor: 'not-allowed',
}, },
@ -181,7 +192,6 @@ export const genCheckboxStyle = (
// Wrapper > Checkbox > input // Wrapper > Checkbox > input
[`&, ${checkboxCls}-input`]: { [`&, ${checkboxCls}-input`]: {
cursor: 'not-allowed', cursor: 'not-allowed',
pointerEvents: 'none',
}, },
// Wrapper > Checkbox > inner // Wrapper > Checkbox > inner
@ -206,11 +216,14 @@ export const genCheckboxStyle = (
}; };
// ============================== Export ============================== // ============================== Export ==============================
export function getStyle(prefixCls: string, token: DerivativeToken, hashId: string) {
return [genCheckboxStyle(prefixCls, token, hashId), antCheckboxEffect];
}
export default function useStyle(prefixCls: string) { export default function useStyle(prefixCls: string) {
const [theme, token, hashId] = useToken(); const [theme, token, hashId] = useToken();
return useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [ return useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () =>
genCheckboxStyle(prefixCls, token, hashId), getStyle(prefixCls, token, hashId),
antCheckboxEffect, );
]);
} }

View File

@ -8,6 +8,7 @@ import { ConfigContext } from '../config-provider';
import collapseMotion from '../_util/motion'; import collapseMotion from '../_util/motion';
import renderSwitcherIcon from './utils/iconUtil'; import renderSwitcherIcon from './utils/iconUtil';
import dropIndicatorRender from './utils/dropIndicator'; import dropIndicatorRender from './utils/dropIndicator';
import useStyle from './style';
export interface AntdTreeNodeAttribute { export interface AntdTreeNodeAttribute {
eventKey: string; eventKey: string;
@ -172,6 +173,8 @@ const Tree = React.forwardRef<RcTree, TreeProps>((props, ref) => {
dropIndicatorRender, dropIndicatorRender,
}; };
const wrapSSR = useStyle(prefixCls);
const draggableConfig = React.useMemo(() => { const draggableConfig = React.useMemo(() => {
if (!draggable) { if (!draggable) {
return false; return false;
@ -198,7 +201,7 @@ const Tree = React.forwardRef<RcTree, TreeProps>((props, ref) => {
return mergedDraggable; return mergedDraggable;
}, [draggable]); }, [draggable]);
return ( return wrapSSR(
<RcTree <RcTree
itemHeight={20} itemHeight={20}
ref={ref} ref={ref}
@ -223,7 +226,7 @@ const Tree = React.forwardRef<RcTree, TreeProps>((props, ref) => {
draggable={draggableConfig as any} draggable={draggableConfig as any}
> >
{children} {children}
</RcTree> </RcTree>,
); );
}) as unknown as CompoundedComponent; }) as unknown as CompoundedComponent;

View File

@ -1,16 +1,16 @@
@import '../../style/themes/index'; // @import '../../style/themes/index';
@import '../../style/mixins/index'; // @import '../../style/mixins/index';
@import '../../checkbox/style/mixin'; // @import '../../checkbox/style/mixin';
@import './mixin'; // @import './mixin';
@import './directory'; // @import './directory';
@tree-prefix-cls: ~'@{ant-prefix}-tree'; // @tree-prefix-cls: ~'@{ant-prefix}-tree';
@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode'; // @tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
.antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox'); // .antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox');
.@{tree-prefix-cls} { // .@{tree-prefix-cls} {
.antTreeFn(@tree-prefix-cls); // .antTreeFn(@tree-prefix-cls);
} // }
@import './rtl'; // @import './rtl';

View File

@ -1,2 +1,438 @@
import '../../style/index.less'; // import '../../style/index.less';
import './index.less'; // import './index.less';
// deps-lint-skip-all
import { CSSObject, CSSInterpolation, Keyframes } from '@ant-design/cssinjs';
import { DerivativeToken, useStyleRegister, useToken, resetComponent } from '../../_util/theme';
import { getStyle as getCheckboxStyle } from '../../checkbox/style';
// ============================ Keyframes =============================
const treeNodeFX = new Keyframes('ant-tree-node-fx-do-not-use', {
'0%': {
opacity: 0,
},
'100%': {
opacity: 1,
},
});
// ============================== Switch ==============================
const getSwitchStyle = (prefixCls: string, token: DerivativeToken): CSSObject => ({
[`.${prefixCls}-switcher-icon`]: {
display: 'inline-block',
fontSize: 10, // FIXME: missing token
verticalAlign: 'baseline',
svg: {
transition: `transform ${token.duration}`,
},
},
});
// =============================== Drop ===============================
const getDropIndicatorStyle = (prefixCls: string, token: DerivativeToken) => ({
[`.${prefixCls}-drop-indicator`]: {
position: 'absolute',
// it should displayed over the following node
zIndex: 1,
height: 2,
backgroundColor: token.primaryColor,
borderRadius: 1,
pointerEvents: 'none',
'&:after': {
position: 'absolute',
top: -3,
left: -6,
width: 8,
height: 8,
backgroundColor: 'transparent',
border: `2px solid ${token.primaryColor}`,
borderRadius: '50%',
content: '""',
},
},
});
// =============================== Base ===============================
type TreeToken = DerivativeToken & {
treeCls: string;
treeNodeCls: string;
treeNodePadding: number;
treeTitleHeight: number;
};
export const genBaseStyle = (prefixCls: string, token: TreeToken, hashId: string): CSSObject => {
const { treeCls, treeNodeCls, treeNodePadding, treeTitleHeight } = token;
const treeCheckBoxMarginVertical = (treeTitleHeight - token.fontSizeLG) / 2;
const treeCheckBoxMarginHorizontal = token.paddingXS;
return {
[treeCls]: {
...resetComponent(token),
background: token.componentBackground,
borderRadius: token.borderRadius,
transition: `background-color ${token.duration}`,
'&-focused:not(:hover):not(&-active-focused)': {
background: token.tmpPrimaryHoverColorWeak,
},
// =================== Virtual List ===================
'&-list-holder-inner': {
alignItems: 'flex-start',
},
[`&${treeCls}-block-node`]: {
[`${treeCls}-list-holder-inner`]: {
alignItems: 'stretch',
// >>> Title
[`${treeCls}-node-content-wrapper`]: {
flex: 'auto',
display: 'flex',
flexWrap: 'nowrap',
},
// >>> Drag
[`${treeNodeCls}.dragging`]: {
position: 'relative',
'&:after': {
position: 'absolute',
top: 0,
right: 0,
bottom: treeNodePadding,
left: 0,
border: `1px solid ${token.primaryColor}`,
opacity: 0,
animation: `${treeNodeFX.getName(hashId)} ${token.duration}`,
animationPlayState: 'running',
animationFillMode: 'forwards',
content: '""',
pointerEvents: 'none',
},
},
},
},
// ===================== TreeNode =====================
[`${treeNodeCls}`]: {
display: 'flex',
alignItems: 'flex-start',
padding: `0 0 ${treeNodePadding}px 0`,
outline: 'none',
// Disabled
'&-disabled': {
// >>> Title
[`${treeCls}-node-content-wrapper`]: {
color: token.textColorDisabled,
cursor: 'not-allowed',
'&:hover': {
background: 'transparent',
},
},
},
[`&-active ${treeCls}-node-content-wrapper`]: {
background: token.itemHoverBackground,
},
[`&:not(&-disabled).filter-node ${treeCls}-title`]: {
color: 'inherit',
fontWeight: 500,
},
},
// >>> Indent
'&-indent': {
alignSelf: 'stretch',
whiteSpace: 'nowrap',
userSelect: 'none',
'&-unit': {
display: 'inline-block',
width: treeTitleHeight,
},
},
// >>> Drag Handler
'&-draggable-icon': {
width: treeTitleHeight,
lineHeight: `${treeTitleHeight}px`,
textAlign: 'center',
opacity: 0.2,
transition: `opacity ${token.duration}`,
[`${treeNodeCls}:hover &`]: {
opacity: 0.45,
},
},
// >>> Switcher
'&-switcher': {
...getSwitchStyle(prefixCls, token),
position: 'relative',
flex: 'none',
alignSelf: 'stretch',
width: treeTitleHeight,
margin: 0,
lineHeight: `${treeTitleHeight}px`,
textAlign: 'center',
cursor: 'pointer',
userSelect: 'none',
'&-noop': {
cursor: 'default',
},
'&_close': {
[`${treeCls}-switcher-icon`]: {
svg: {
transform: 'rotate(-90deg)',
},
},
},
'&-loading-icon': {
color: token.primaryColor,
},
'&-leaf-line': {
position: 'relative',
zIndex: 1,
display: 'inline-block',
width: '100%',
height: '100%',
// https://github.com/ant-design/ant-design/issues/31884
'&:before': {
position: 'absolute',
top: 0,
right: treeTitleHeight / 2,
bottom: -treeNodePadding,
marginLeft: -1,
borderRight: `1px solid ${token.borderColor}`,
content: '""',
},
'&:after': {
position: 'absolute',
width: (treeTitleHeight / 2) * 0.8,
height: treeTitleHeight / 2,
borderBottom: `1px solid ${token.borderColor}`,
content: '""',
},
},
},
// >>> Checkbox
'&-checkbox': {
top: 'initial',
marginInlineEnd: treeCheckBoxMarginHorizontal,
marginBlockStart: treeCheckBoxMarginVertical,
},
// >>> Title
'& &-node-content-wrapper': {
position: 'relative',
zIndex: 'auto',
minHeight: treeTitleHeight,
margin: 0,
padding: `0 ${token.paddingXS / 2}px`,
color: 'inherit',
lineHeight: `${treeTitleHeight}px`,
background: 'transparent',
borderRadius: token.borderRadius,
cursor: 'pointer',
transition: `all ${token.duration}, border 0s, line-height 0s, box-shadow 0s`,
'&:hover': {
backgroundColor: token.itemHoverBackground,
},
[`&${treeCls}-node-selected`]: {
backgroundColor: token.tmpPrimaryHoverColorWeak,
},
// Icon
[`${treeCls}-iconEle`]: {
display: 'inline-block',
width: treeTitleHeight,
height: treeTitleHeight,
lineHeight: `${treeTitleHeight}px`,
textAlign: 'center',
verticalAlign: 'top',
'&:empty': {
display: 'none',
},
},
},
// https://github.com/ant-design/ant-design/issues/28217
'&-unselectable &-node-content-wrapper:hover': {
backgroundColor: 'transparent',
},
// ==================== Draggable =====================
'&-node-content-wrapper': {
lineHeight: `${treeTitleHeight}px`,
userSelect: 'none',
...getDropIndicatorStyle(prefixCls, token),
},
[`${treeNodeCls}.drop-container`]: {
'> [draggable]': {
boxShadow: `0 0 0 2px ${token.primaryColor}`,
},
},
// ==================== Show Line =====================
'&-show-line': {
// ================ Indent lines ================
[`${treeCls}-indent`]: {
'&-unit': {
position: 'relative',
height: '100%',
'&:before': {
position: 'absolute',
top: 0,
right: treeTitleHeight / 2,
bottom: -treeNodePadding,
borderRight: `1px solid ${token.borderColor}`,
content: '""',
},
'&-end': {
'&:before': {
display: 'none',
},
},
},
},
// ============== Cover Background ==============
[`${treeCls}-switcher`]: {
background: token.componentBackground,
'&-line-icon': {
// https://github.com/ant-design/ant-design/issues/32813
verticalAlign: '-0.15em',
},
},
},
[`${treeNodeCls}-leaf-last`]: {
[`${treeCls}-switcher`]: {
'&-leaf-line': {
'&:before': {
top: 'auto !important',
bottom: 'auto !important',
height: `${treeTitleHeight / 2}px !important`,
},
},
},
},
},
};
};
// =============================== RTL ================================
export const genRTLStyle = (token: TreeToken): CSSObject => {
const { treeCls, treeNodeCls, treeTitleHeight } = token;
return {
[treeCls]: {
'&-rtl': {
direction: 'rtl',
[`${treeCls}-node-content-wrapper[draggable='true']`]: {
[`${treeCls}-drop-indicator`]: {
'&:after': {
right: -6,
left: 'unset',
},
},
},
},
// ===================== TreeNode =====================
[`${treeNodeCls}`]: {
'&-rtl': {
direction: 'rtl',
},
},
// >>> Switcher
'&-switcher': {
'&_close': {
[`${treeCls}-switcher-icon`]: {
svg: {
[`${treeCls}-rtl &`]: {
transform: 'rotate(90deg)',
},
},
},
},
},
// ==================== Show Line =====================
'&-show-line': {
// ================ Indent lines ================
[`${treeCls}-indent`]: {
'&-unit': {
'&:before': {
[`${treeCls}-rtl&`]: {
right: 'auto',
left: -(treeTitleHeight / 2) - 1,
borderRight: 'none',
borderLeft: `1px solid ${token.borderColor}`,
},
},
},
},
},
},
};
};
// ============================== Merged ==============================
export const genTreeStyle = (
prefixCls: string,
token: DerivativeToken,
hashId: string,
): CSSInterpolation => {
const treeCls = `.${prefixCls}`;
const treeNodeCls = `${treeCls}-treenode`;
const treeNodePadding = token.paddingXS / 2;
const treeTitleHeight = token.heightSM;
const treeToken = {
...token,
treeCls,
treeNodeCls,
treeNodePadding,
treeTitleHeight,
};
return [
// Basic
genBaseStyle(prefixCls, treeToken, hashId),
// RTL
genRTLStyle(treeToken),
];
};
// ============================== Export ==============================
export default function useStyle(prefixCls: string) {
const [theme, token, hashId] = useToken();
return useStyleRegister({ theme, token, hashId, path: [prefixCls] }, () => [
getCheckboxStyle(`${prefixCls}-checkbox`, token, hashId),
genTreeStyle(prefixCls, token, hashId),
treeNodeFX,
]);
}