ant-design/components/tree/style/index.ts

479 lines
13 KiB
TypeScript
Raw Normal View History

import type { CSSInterpolation, CSSObject } from '@ant-design/cssinjs';
import { Keyframes, unit } from '@ant-design/cssinjs';
import { getStyle as getCheckboxStyle } from '../../checkbox/style';
import { genFocusOutline, resetComponent } from '../../style';
import { genCollapseMotion } from '../../style/motion';
import type { AliasToken, CSSUtil, FullToken, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks, mergeToken } from '../../theme/internal';
2024-11-05 16:18:53 +08:00
import { genDirectoryStyle } from './directory';
2023-08-18 10:23:57 +08:00
export interface TreeSharedToken {
/**
* @desc
* @descEN Node title height
*/
titleHeight: number;
/**
* @desc
* @descEN Indent width of tree
*/
indentSize?: number;
2023-08-18 10:23:57 +08:00
/**
* @desc
* @descEN Background color of hovered node
*/
nodeHoverBg: string;
/**
* @desc
* @descEN Text color of hovered node
*/
nodeHoverColor: string;
2023-08-18 10:23:57 +08:00
/**
* @desc
* @descEN Background color of selected node
*/
nodeSelectedBg: string;
/**
* @desc
* @descEN Text color of selected node
*/
nodeSelectedColor: string;
2023-08-18 10:23:57 +08:00
}
export interface ComponentToken extends TreeSharedToken {
/**
* @desc
* @descEN Text color of selected directory node
*/
directoryNodeSelectedColor: string;
/**
* @desc
* @descEN Background color of selected directory node
*/
directoryNodeSelectedBg: string;
}
// ============================ Keyframes =============================
const treeNodeFX = new Keyframes('ant-tree-node-fx-do-not-use', {
'0%': {
opacity: 0,
},
'100%': {
opacity: 1,
},
});
// ============================== Switch ==============================
const getSwitchStyle = (prefixCls: string, token: AliasToken): CSSObject => ({
[`.${prefixCls}-switcher-icon`]: {
display: 'inline-block',
fontSize: 10,
verticalAlign: 'baseline',
svg: {
transition: `transform ${token.motionDurationSlow}`,
},
},
});
// =============================== Drop ===============================
const getDropIndicatorStyle = (prefixCls: string, token: AliasToken) => ({
[`.${prefixCls}-drop-indicator`]: {
position: 'absolute',
// it should displayed over the following node
zIndex: 1,
height: 2,
backgroundColor: token.colorPrimary,
borderRadius: 1,
pointerEvents: 'none',
'&:after': {
position: 'absolute',
top: -3,
insetInlineStart: -6,
width: 8,
height: 8,
backgroundColor: 'transparent',
border: `${unit(token.lineWidthBold)} solid ${token.colorPrimary}`,
borderRadius: '50%',
content: '""',
},
},
});
// =============================== Base ===============================
export type TreeToken = FullToken<'Tree'> & {
treeCls: string;
treeNodeCls: string;
treeNodePadding: number | string;
};
export const genBaseStyle = (prefixCls: string, token: TreeToken): CSSObject => {
const {
treeCls,
treeNodeCls,
treeNodePadding,
titleHeight,
indentSize,
nodeSelectedBg,
nodeHoverBg,
colorTextQuaternary,
} = token;
return {
[treeCls]: {
...resetComponent(token),
background: token.colorBgContainer,
borderRadius: token.borderRadius,
transition: `background-color ${token.motionDurationSlow}`,
'&-rtl': {
direction: 'rtl',
},
[`&${treeCls}-rtl ${treeCls}-switcher_close ${treeCls}-switcher-icon svg`]: {
transform: 'rotate(90deg)',
},
[`&-focused:not(:hover):not(${treeCls}-active-focused)`]: {
...genFocusOutline(token),
},
// =================== Virtual List ===================
[`${treeCls}-list-holder-inner`]: {
alignItems: 'flex-start',
},
[`&${treeCls}-block-node`]: {
[`${treeCls}-list-holder-inner`]: {
alignItems: 'stretch',
// >>> Title
[`${treeCls}-node-content-wrapper`]: {
flex: 'auto',
},
// >>> Drag
[`${treeNodeCls}.dragging:after`]: {
position: 'absolute',
inset: 0,
border: `1px solid ${token.colorPrimary}`,
opacity: 0,
animationName: treeNodeFX,
animationDuration: token.motionDurationSlow,
animationPlayState: 'running',
animationFillMode: 'forwards',
content: '""',
pointerEvents: 'none',
borderRadius: token.borderRadius,
},
},
},
// ===================== TreeNode =====================
[treeNodeCls]: {
display: 'flex',
alignItems: 'flex-start',
marginBottom: treeNodePadding,
lineHeight: unit(titleHeight),
position: 'relative',
// 非常重要,避免 drop-indicator 在拖拽过程中闪烁
'&:before': {
content: '""',
position: 'absolute',
zIndex: 1,
insetInlineStart: 0,
width: '100%',
top: '100%',
height: treeNodePadding,
},
// Disabled
[`&-disabled ${treeCls}-node-content-wrapper`]: {
color: token.colorTextDisabled,
cursor: 'not-allowed',
'&:hover': {
background: 'transparent',
},
},
// not disable
[`&:not(${treeNodeCls}-disabled)`]: {
// >>> Title
[`${treeCls}-node-content-wrapper`]: {
'&:hover': {
color: token.nodeHoverColor,
},
},
},
[`&-active ${treeCls}-node-content-wrapper`]: {
background: token.controlItemBgHover,
},
[`&:not(${treeNodeCls}-disabled).filter-node ${treeCls}-title`]: {
color: token.colorPrimary,
fontWeight: 500,
},
2022-07-15 11:23:50 +08:00
'&-draggable': {
cursor: 'grab',
2022-07-15 11:23:50 +08:00
[`${treeCls}-draggable-icon`]: {
// https://github.com/ant-design/ant-design/issues/41915
flexShrink: 0,
2023-08-18 10:23:57 +08:00
width: titleHeight,
2022-07-15 11:23:50 +08:00
textAlign: 'center',
visibility: 'visible',
color: colorTextQuaternary,
2022-07-15 11:23:50 +08:00
},
[`&${treeNodeCls}-disabled ${treeCls}-draggable-icon`]: {
visibility: 'hidden',
},
2022-07-15 11:23:50 +08:00
},
},
// >>> Indent
[`${treeCls}-indent`]: {
alignSelf: 'stretch',
whiteSpace: 'nowrap',
userSelect: 'none',
'&-unit': {
display: 'inline-block',
width: indentSize,
},
},
// >>> Drag Handler
[`${treeCls}-draggable-icon`]: {
2022-07-15 11:23:50 +08:00
visibility: 'hidden',
},
// Switcher / Checkbox
[`${treeCls}-switcher, ${treeCls}-checkbox`]: {
marginInlineEnd: token
.calc(token.calc(titleHeight).sub(token.controlInteractiveSize))
.div(2)
.equal(),
},
// >>> Switcher
[`${treeCls}-switcher`]: {
...getSwitchStyle(prefixCls, token),
position: 'relative',
flex: 'none',
alignSelf: 'stretch',
2023-08-18 10:23:57 +08:00
width: titleHeight,
textAlign: 'center',
cursor: 'pointer',
userSelect: 'none',
transition: `all ${token.motionDurationSlow}`,
'&-noop': {
cursor: 'unset',
},
'&:before': {
pointerEvents: 'none',
content: '""',
width: titleHeight,
height: titleHeight,
position: 'absolute',
left: {
_skip_check_: true,
value: 0,
},
top: 0,
borderRadius: token.borderRadius,
transition: `all ${token.motionDurationSlow}`,
},
[`&:not(${treeCls}-switcher-noop):hover:before`]: {
backgroundColor: token.colorBgTextHover,
},
[`&_close ${treeCls}-switcher-icon svg`]: {
transform: 'rotate(-90deg)',
},
'&-loading-icon': {
color: token.colorPrimary,
},
'&-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,
insetInlineEnd: token.calc(titleHeight).div(2).equal(),
bottom: token.calc(treeNodePadding).mul(-1).equal(),
marginInlineStart: -1,
borderInlineEnd: `1px solid ${token.colorBorder}`,
content: '""',
},
'&:after': {
position: 'absolute',
width: token.calc(token.calc(titleHeight).div(2).equal()).mul(0.8).equal(),
height: token.calc(titleHeight).div(2).equal(),
borderBottom: `1px solid ${token.colorBorder}`,
content: '""',
},
},
},
// >>> Title
// add `${treeCls}-checkbox + span` to cover checkbox `${checkboxCls} + span`
[`${treeCls}-node-content-wrapper`]: {
position: 'relative',
2023-08-18 10:23:57 +08:00
minHeight: titleHeight,
2024-11-05 16:18:53 +08:00
paddingBlock: 0,
paddingInline: token.paddingXS,
background: 'transparent',
borderRadius: token.borderRadius,
cursor: 'pointer',
transition: `all ${token.motionDurationMid}, border 0s, line-height 0s, box-shadow 0s`,
...getDropIndicatorStyle(prefixCls, token),
'&:hover': {
2023-08-18 10:23:57 +08:00
backgroundColor: nodeHoverBg,
},
[`&${treeCls}-node-selected`]: {
color: token.nodeSelectedColor,
2023-08-18 10:23:57 +08:00
backgroundColor: nodeSelectedBg,
},
// Icon
[`${treeCls}-iconEle`]: {
display: 'inline-block',
2023-08-18 10:23:57 +08:00
width: titleHeight,
height: titleHeight,
textAlign: 'center',
verticalAlign: 'top',
'&:empty': {
display: 'none',
},
},
},
// https://github.com/ant-design/ant-design/issues/28217
[`${treeCls}-unselectable ${treeCls}-node-content-wrapper:hover`]: {
backgroundColor: 'transparent',
},
[`${treeNodeCls}.drop-container > [draggable]`]: {
boxShadow: `0 0 0 2px ${token.colorPrimary}`,
},
// ==================== Show Line =====================
'&-show-line': {
// ================ Indent lines ================
[`${treeCls}-indent-unit`]: {
position: 'relative',
height: '100%',
'&:before': {
position: 'absolute',
top: 0,
insetInlineEnd: token.calc(titleHeight).div(2).equal(),
bottom: token.calc(treeNodePadding).mul(-1).equal(),
borderInlineEnd: `1px solid ${token.colorBorder}`,
content: '""',
},
'&-end:before': {
display: 'none',
},
},
// ============== Cover Background ==============
[`${treeCls}-switcher`]: {
background: 'transparent',
'&-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: `${unit(token.calc(titleHeight).div(2).equal())} !important`,
},
},
};
};
// ============================== Merged ==============================
2023-08-18 10:23:57 +08:00
export const genTreeStyle = (
prefixCls: string,
token: AliasToken & TreeSharedToken & CSSUtil,
2023-08-18 10:23:57 +08:00
): CSSInterpolation => {
const treeCls = `.${prefixCls}`;
const treeNodeCls = `${treeCls}-treenode`;
const treeNodePadding = token.calc(token.paddingXS).div(2).equal();
const treeToken = mergeToken<TreeToken>(token, {
treeCls,
treeNodeCls,
treeNodePadding,
});
return [
// Basic
genBaseStyle(prefixCls, treeToken),
// Directory
genDirectoryStyle(treeToken),
];
};
2023-08-18 10:23:57 +08:00
export const initComponentToken = (token: AliasToken): TreeSharedToken => {
const { controlHeightSM, controlItemBgHover, controlItemBgActive } = token;
const titleHeight = controlHeightSM;
2023-08-18 10:23:57 +08:00
return {
titleHeight,
indentSize: titleHeight,
nodeHoverBg: controlItemBgHover,
nodeHoverColor: token.colorText,
nodeSelectedBg: controlItemBgActive,
nodeSelectedColor: token.colorText,
2023-08-18 10:23:57 +08:00
};
};
export const prepareComponentToken: GetDefaultToken<'Tree'> = (token) => {
const { colorTextLightSolid, colorPrimary } = token;
return {
...initComponentToken(token),
directoryNodeSelectedColor: colorTextLightSolid,
directoryNodeSelectedBg: colorPrimary,
};
};
export default genStyleHooks(
2023-08-18 10:23:57 +08:00
'Tree',
(token, { prefixCls }) => [
{
[token.componentCls]: getCheckboxStyle(`${prefixCls}-checkbox`, token),
},
genTreeStyle(prefixCls, token),
genCollapseMotion(token),
],
prepareComponentToken,
2023-08-18 10:23:57 +08:00
);