1
This commit is contained in:
+49
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
Reference in New Issue
Block a user