This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
import * as React from 'react';
import type { SegmentedLabeledOption as RcSegmentedLabeledOption, SegmentedProps as RCSegmentedProps, SegmentedValue as RcSegmentedValue, SegmentedRawOption } from '@rc-component/segmented';
import type { Orientation, SemanticClassNamesType, SemanticStylesType } from '../_util/hooks';
import type { SizeType } from '../config-provider/SizeContext';
import type { TooltipProps } from '../tooltip';
export type { SegmentedValue } from '@rc-component/segmented';
export type SegmentedSemanticName = keyof SegmentedSemanticClassNames & keyof SegmentedSemanticStyles;
export type SegmentedSemanticClassNames = {
root?: string;
icon?: string;
label?: string;
item?: string;
};
export type SegmentedSemanticStyles = {
root?: React.CSSProperties;
icon?: React.CSSProperties;
label?: React.CSSProperties;
item?: React.CSSProperties;
};
interface SegmentedLabeledOptionWithoutIcon<ValueType = RcSegmentedValue> extends RcSegmentedLabeledOption<ValueType> {
label: RcSegmentedLabeledOption['label'];
tooltip?: string | Omit<TooltipProps, 'children'>;
}
interface SegmentedLabeledOptionWithIcon<ValueType = RcSegmentedValue> extends Omit<RcSegmentedLabeledOption<ValueType>, 'label'> {
label?: RcSegmentedLabeledOption['label'];
/** Set icon for Segmented item */
icon: React.ReactNode;
tooltip?: string | Omit<TooltipProps, 'children'>;
}
export type SegmentedLabeledOption<ValueType = RcSegmentedValue> = SegmentedLabeledOptionWithIcon<ValueType> | SegmentedLabeledOptionWithoutIcon<ValueType>;
export type SegmentedOptions<T = SegmentedRawOption> = (T | SegmentedLabeledOption<T>)[];
export type SegmentedClassNamesType = SemanticClassNamesType<SegmentedProps, SegmentedSemanticClassNames>;
export type SegmentedStylesType = SemanticStylesType<SegmentedProps, SegmentedSemanticStyles>;
export interface SegmentedProps<ValueType = RcSegmentedValue> extends Omit<RCSegmentedProps<ValueType>, 'size' | 'options' | 'itemRender' | 'styles' | 'classNames'> {
rootClassName?: string;
options: SegmentedOptions<ValueType>;
/** Option to fit width to its parent's width */
block?: boolean;
/** Option to control the display size */
size?: SizeType;
vertical?: boolean;
orientation?: Orientation;
classNames?: SegmentedClassNamesType;
styles?: SegmentedStylesType;
shape?: 'default' | 'round';
}
declare const InternalSegmented: React.ForwardRefExoticComponent<Omit<SegmentedProps<RcSegmentedValue>, "ref"> & React.RefAttributes<HTMLDivElement>>;
declare const Segmented: (<ValueType>(props: SegmentedProps<ValueType> & React.RefAttributes<HTMLDivElement>) => ReturnType<typeof InternalSegmented>) & Pick<React.FC, "displayName">;
export default Segmented;
+119
View File
@@ -0,0 +1,119 @@
"use client";
import * as React from 'react';
import RcSegmented from '@rc-component/segmented';
import useId from "@rc-component/util/es/hooks/useId";
import { clsx } from 'clsx';
import { useMergeSemantic, useOrientation } from '../_util/hooks';
import { isPlainObject } from '../_util/is';
import { useComponentConfig } from '../config-provider/context';
import useSize from '../config-provider/hooks/useSize';
import Tooltip from '../tooltip';
import useStyle from './style';
function isSegmentedLabeledOptionWithIcon(option) {
return isPlainObject(option) && !!option?.icon;
}
const InternalSegmented = /*#__PURE__*/React.forwardRef((props, ref) => {
const defaultName = useId();
const {
prefixCls: customizePrefixCls,
className,
rootClassName,
block,
options = [],
size: customSize,
style,
vertical,
orientation,
shape = 'default',
name = defaultName,
styles,
classNames,
...restProps
} = props;
const {
getPrefixCls,
direction,
className: contextClassName,
style: contextStyle,
classNames: contextClassNames,
styles: contextStyles
} = useComponentConfig('segmented');
const mergedProps = {
...props,
options,
size: customSize,
shape
};
const [mergedClassNames, mergedStyles] = useMergeSemantic([contextClassNames, classNames], [contextStyles, styles], {
props: mergedProps
});
const prefixCls = getPrefixCls('segmented', customizePrefixCls);
// Style
const [hashId, cssVarCls] = useStyle(prefixCls);
// ===================== Size =====================
const mergedSize = useSize(customSize);
// syntactic sugar to support `icon` for Segmented Item
const extendedOptions = React.useMemo(() => options.map(option => {
if (isSegmentedLabeledOptionWithIcon(option)) {
const {
icon,
label,
...restOption
} = option;
return {
...restOption,
label: (/*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("span", {
className: clsx(`${prefixCls}-item-icon`, mergedClassNames.icon),
style: mergedStyles.icon
}, icon), label && /*#__PURE__*/React.createElement("span", null, label)))
};
}
return option;
}), [options, prefixCls, mergedClassNames.icon, mergedStyles.icon]);
const [, mergedVertical] = useOrientation(orientation, vertical);
const cls = clsx(className, rootClassName, contextClassName, mergedClassNames.root, {
[`${prefixCls}-block`]: block,
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-vertical`]: mergedVertical,
[`${prefixCls}-shape-${shape}`]: shape === 'round'
}, hashId, cssVarCls);
const mergedStyle = {
...mergedStyles.root,
...contextStyle,
...style
};
const itemRender = (node, {
item
}) => {
if (!item.tooltip) {
return node;
}
const tooltipProps = isPlainObject(item.tooltip) ? item.tooltip : {
title: item.tooltip
};
return /*#__PURE__*/React.createElement(Tooltip, {
...tooltipProps
}, node);
};
return /*#__PURE__*/React.createElement(RcSegmented, {
...restProps,
name: name,
className: cls,
style: mergedStyle,
classNames: mergedClassNames,
styles: mergedStyles,
itemRender: itemRender,
options: extendedOptions,
ref: ref,
prefixCls: prefixCls,
direction: direction,
vertical: mergedVertical
});
});
const Segmented = InternalSegmented;
if (process.env.NODE_ENV !== 'production') {
Segmented.displayName = 'Segmented';
}
export default Segmented;
+46
View File
@@ -0,0 +1,46 @@
import type { GetDefaultToken } from '../../theme/internal';
export interface ComponentToken {
/**
* @desc 选项文本颜色
* @descEN Text color of item
*/
itemColor: string;
/**
* @desc 选项悬浮态文本颜色
* @descEN Text color of item when hover
*/
itemHoverColor: string;
/**
* @desc 选项悬浮态背景颜色
* @descEN Background color of item when hover
*/
itemHoverBg: string;
/**
* @desc 选项激活态背景颜色
* @descEN Background color of item when active
*/
itemActiveBg: string;
/**
* @desc 选项选中时背景颜色
* @descEN Background color of item when selected
*/
itemSelectedBg: string;
/**
* @desc 选项选中时文字颜色
* @descEN Text color of item when selected
*/
itemSelectedColor: string;
/**
* @desc Segmented 控件容器的 padding
* @descEN Padding of Segmented container
*/
trackPadding: string | number;
/**
* @desc Segmented 控件容器背景色
* @descEN Background of Segmented container
*/
trackBg: string;
}
export declare const prepareComponentToken: GetDefaultToken<'Segmented'>;
declare const _default: (prefixCls: string, rootCls?: string) => readonly [string, string];
export default _default;
+225
View File
@@ -0,0 +1,225 @@
import { unit } from '@ant-design/cssinjs';
import { genFocusOutline, genFocusStyle, resetComponent, textEllipsis } from '../../style';
import { genStyleHooks, mergeToken } from '../../theme/internal';
// ============================== Mixins ==============================
function getItemDisabledStyle(cls, token) {
return {
[`${cls}, ${cls}:hover, ${cls}:focus`]: {
color: token.colorTextDisabled,
cursor: 'not-allowed'
}
};
}
const getItemSelectedStyle = token => {
return {
background: token.itemSelectedBg,
boxShadow: token.boxShadowTertiary
};
};
const segmentedTextEllipsisCss = {
overflow: 'hidden',
// handle text ellipsis
...textEllipsis
};
// ============================== Styles ==============================
const genSegmentedStyle = token => {
const {
componentCls,
motionDurationSlow,
motionEaseInOut,
motionDurationMid
} = token;
const labelHeight = token.calc(token.controlHeight).sub(token.calc(token.trackPadding).mul(2)).equal();
const labelHeightLG = token.calc(token.controlHeightLG).sub(token.calc(token.trackPadding).mul(2)).equal();
const labelHeightSM = token.calc(token.controlHeightSM).sub(token.calc(token.trackPadding).mul(2)).equal();
return {
[componentCls]: {
...resetComponent(token),
display: 'inline-block',
padding: token.trackPadding,
color: token.itemColor,
background: token.trackBg,
borderRadius: token.borderRadius,
transition: `all ${motionDurationMid}`,
...genFocusStyle(token),
[`${componentCls}-group`]: {
position: 'relative',
display: 'flex',
alignItems: 'stretch',
justifyItems: 'flex-start',
flexDirection: 'row',
width: '100%'
},
// RTL styles
[`&${componentCls}-rtl`]: {
direction: 'rtl'
},
[`&${componentCls}-vertical`]: {
[`${componentCls}-group`]: {
flexDirection: 'column'
},
[`${componentCls}-thumb`]: {
width: '100%',
height: 0,
padding: `0 ${unit(token.paddingXXS)}`
}
},
// block styles
[`&${componentCls}-block`]: {
display: 'flex'
},
[`&${componentCls}-block ${componentCls}-item`]: {
flex: 1,
minWidth: 0
},
// item styles
[`${componentCls}-item`]: {
position: 'relative',
textAlign: 'center',
cursor: 'pointer',
transition: `color ${motionDurationMid}`,
borderRadius: token.borderRadiusSM,
// Fix Safari render bug
// https://github.com/ant-design/ant-design/issues/45250
transform: 'translateZ(0)',
'&-selected': {
...getItemSelectedStyle(token),
color: token.itemSelectedColor
},
'&-focused': genFocusOutline(token),
'&::after': {
content: '""',
position: 'absolute',
zIndex: -1,
width: '100%',
height: '100%',
top: 0,
insetInlineStart: 0,
borderRadius: 'inherit',
opacity: 0,
// This is mandatory to make it not clickable or hoverable
// Ref: https://github.com/ant-design/ant-design/issues/40888
pointerEvents: 'none',
transition: ['opacity', 'background-color'].map(prop => `${prop} ${motionDurationMid}`).join(', ')
},
[`&:not(${componentCls}-item-selected):not(${componentCls}-item-disabled)`]: {
'&:hover, &:active': {
color: token.itemHoverColor
},
'&:hover::after': {
opacity: 1,
backgroundColor: token.itemHoverBg
},
'&:active::after': {
opacity: 1,
backgroundColor: token.itemActiveBg
}
},
'&-label': {
minHeight: labelHeight,
lineHeight: unit(labelHeight),
padding: `0 ${unit(token.segmentedPaddingHorizontal)}`,
...segmentedTextEllipsisCss
},
// syntactic sugar to add `icon` for Segmented Item
'&-icon + *': {
marginInlineStart: token.calc(token.marginSM).div(2).equal()
},
'&-input': {
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
width: 0,
height: 0,
opacity: 0,
pointerEvents: 'none'
}
},
// thumb styles
[`${componentCls}-thumb`]: {
...getItemSelectedStyle(token),
position: 'absolute',
insetBlockStart: 0,
insetInlineStart: 0,
width: 0,
height: '100%',
padding: `${unit(token.paddingXXS)} 0`,
borderRadius: token.borderRadiusSM,
[`& ~ ${componentCls}-item:not(${componentCls}-item-selected):not(${componentCls}-item-disabled)::after`]: {
backgroundColor: 'transparent'
}
},
// size styles
[`&${componentCls}-lg`]: {
borderRadius: token.borderRadiusLG,
[`${componentCls}-item-label`]: {
minHeight: labelHeightLG,
lineHeight: unit(labelHeightLG),
padding: `0 ${unit(token.segmentedPaddingHorizontal)}`,
fontSize: token.fontSizeLG
},
[`${componentCls}-item, ${componentCls}-thumb`]: {
borderRadius: token.borderRadius
}
},
[`&${componentCls}-sm`]: {
borderRadius: token.borderRadiusSM,
[`${componentCls}-item-label`]: {
minHeight: labelHeightSM,
lineHeight: unit(labelHeightSM),
padding: `0 ${unit(token.segmentedPaddingHorizontalSM)}`
},
[`${componentCls}-item, ${componentCls}-thumb`]: {
borderRadius: token.borderRadiusXS
}
},
// disabled styles
...getItemDisabledStyle(`&-disabled ${componentCls}-item`, token),
...getItemDisabledStyle(`${componentCls}-item-disabled`, token),
// transition effect when `appear-active`
[`${componentCls}-thumb-motion-appear-active`]: {
willChange: 'transform, width',
transition: [`transform`, `width`].map(prop => `${prop} ${motionDurationSlow} ${motionEaseInOut}`).join(', ')
},
[`&${componentCls}-shape-round`]: {
borderRadius: 9999,
[`${componentCls}-item, ${componentCls}-thumb`]: {
borderRadius: 9999
}
}
}
};
};
// ============================== Export ==============================
export const prepareComponentToken = token => {
const {
colorTextLabel,
colorText,
colorFillSecondary,
colorBgElevated,
colorFill,
lineWidthBold,
colorBgLayout
} = token;
return {
trackPadding: lineWidthBold,
trackBg: colorBgLayout,
itemColor: colorTextLabel,
itemHoverColor: colorText,
itemHoverBg: colorFillSecondary,
itemSelectedBg: colorBgElevated,
itemActiveBg: colorFill,
itemSelectedColor: colorText
};
};
export default genStyleHooks('Segmented', token => {
const {
lineWidth,
calc
} = token;
const segmentedToken = mergeToken(token, {
segmentedPaddingHorizontal: calc(token.controlPaddingHorizontal).sub(lineWidth).equal(),
segmentedPaddingHorizontalSM: calc(token.controlPaddingHorizontalSM).sub(lineWidth).equal()
});
return genSegmentedStyle(segmentedToken);
}, prepareComponentToken);