Skip to content

Commit

Permalink
Merge pull request #1195 from Tencent/feat/rate/component
Browse files Browse the repository at this point in the history
feat: 优化 rate 组件
  • Loading branch information
honkinglin authored Aug 1, 2022
2 parents c6a0f0c + dea4f5e commit 8e1a574
Show file tree
Hide file tree
Showing 11 changed files with 2,338 additions and 926 deletions.
161 changes: 67 additions & 94 deletions src/rate/Rate.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,17 @@
import React, { MouseEvent, useState, useCallback } from 'react';
import React, { MouseEvent, useState, forwardRef } from 'react';
import classNames from 'classnames';
import { StarFilledIcon as TdStarFilledIcon } from 'tdesign-icons-react';
import Tooltip from '../tooltip';
import { TdRateProps } from './type';
import { StyledProps } from '../common';
import useConfig from '../hooks/useConfig';
import useGlobalIcon from '../hooks/useGlobalIcon';
import useControlled from '../hooks/useControlled';
import { rateDefaultProps } from './defaultProps';

const getFilledStarSvg = (size) => (
<svg fill="none" viewBox={`0 0 16 16`} width={`${size}`} height={`${size}`}>
<path
fill="currentColor"
d="M7.73 1.55a.3.3 0 01.54 0l1.94 3.92 4.32.62a.3.3 0 01.17.52l-3.13 3.05.74 4.3a.3.3 0 01-.44.32L8 12.25l-3.87 2.03a.3.3 0 01-.43-.31l.73-4.31L1.3 6.6a.3.3 0 01.17-.52l4.33-.62 1.93-3.92z"
fillOpacity="0.9"
></path>
</svg>
);
export interface RateProps extends TdRateProps, StyledProps {}

const getFilledHalfStarSvg = (size) => (
<svg fill="none" viewBox={`0 0 16 16`} width={`${size}`} height={`${size}`}>
<defs>
<linearGradient id="half_grad">
<stop offset="50%" stopColor="currentColor" />
<stop offset="50%" stopColor="white" stopOpacity="1" />
</linearGradient>
</defs>
<path
fill="url(#half_grad)"
d="M7.73 1.55a.3.3 0 01.54 0l1.94 3.92 4.32.62a.3.3 0 01.17.52l-3.13 3.05.74 4.3a.3.3 0 01-.44.32L8 12.25l-3.87 2.03a.3.3 0 01-.43-.31l.73-4.31L1.3 6.6a.3.3 0 01.17-.52l4.33-.62 1.93-3.92z"
fillOpacity="0.9"
></path>
</svg>
);

export type RateProps = TdRateProps;
const Rate = (props: RateProps) => {
const Rate = forwardRef((props: RateProps, ref: React.Ref<HTMLDivElement>) => {
const {
allowHalf, // 是否允许半选
color, // 评分图标的颜色,样式中默认为 #ED7B2F。一个值表示设置选中高亮的五角星颜色,两个值表示分别设置 选中高亮的五角星颜色 和 未选中暗灰的五角星颜色。示例:['#ED7B2F', '#999999']
Expand All @@ -42,105 +21,99 @@ const Rate = (props: RateProps) => {
showText, // 是否显示对应的辅助文字
size, // 评分图标的大小,示例:`20`
texts, // 自定义评分等级对应的辅助文字。组件内置默认值为:['极差', '失望', '一般', '满意', '惊喜']。自定义值示例:['1分', '2分', '3分', '4分', '5分']
className,
style,
onChange,
} = props;

const { classPrefix } = useConfig();
const { StarFilledIcon } = useGlobalIcon({ StarFilledIcon: TdStarFilledIcon });
const [starValue = 0, setStarValue] = useControlled(props, 'value', onChange);

const [hoverValue = undefined, setHoverValue] = useState<number | undefined>(undefined);
const rootRef = React.useRef<HTMLDivElement>(null);
const { classPrefix } = useConfig();
const displayValue = hoverValue || starValue;
const rootRef = React.useRef(null);

const activeColor = Array.isArray(color) ? color[0] : color;
const defaultColor = Array.isArray(color) ? color[1] : 'var(--td-bg-color-component)';

const getStarValue = (event: MouseEvent<HTMLDivElement>, index: number) => {
const getStarValue = (event: MouseEvent<HTMLElement>, index: number) => {
if (allowHalf) {
const rootNode = rootRef.current;
const { left } = rootNode.getBoundingClientRect();
const firstStar = rootNode.firstChild as HTMLElement;
const { width } = firstStar.getBoundingClientRect();
const { clientX } = event;
const starMiddle = width * (index - 0.5) + gap * (index - 1);
if (clientX - left < starMiddle) {
return index - 0.5;
}
if (clientX - left >= starMiddle) {
return index;
}
} else {
return index;
if (clientX - left >= starMiddle) return index;
if (clientX - left < starMiddle) return index - 0.5;
}

return index;
};

const mouseEnterHandler = (event: MouseEvent<HTMLDivElement>, index: number) => {
const mouseEnterHandler = (event: MouseEvent<HTMLElement>, index: number) => {
if (disabled) return;
setHoverValue(getStarValue(event, index));
};

const mouseLeaveHandler = () => {
if (disabled) return;
setHoverValue(undefined);
};

const clickHandler = (event: MouseEvent<HTMLDivElement>, index: number) => {
const clickHandler = (event: MouseEvent<HTMLElement>, index: number) => {
if (disabled) return;
setStarValue(getStarValue(event, index));
};

const getStarStyle = useCallback(
(index: number, count: number, displayValue: number): React.CSSProperties => {
const filledColor = Array.isArray(color) ? color[0] : color || '#ED7B2F';
const defaultColor = Array.isArray(color) ? color[1] : '#E7E7E7';
return {
marginRight: index < count - 1 ? gap : '',
width: size,
height: size,
color: index < displayValue ? filledColor : defaultColor,
};
},
[size, color, gap],
);
const getStarCls = (index: number) => {
if (allowHalf && index + 0.5 === displayValue) return `${classPrefix}-rate__item--half`;
if (index >= displayValue) return '';
if (index < displayValue) return `${classPrefix}-rate__item--full`;
};

const getStar = useCallback(
(allowHalf: boolean, index: number, displayValue: number) => {
if (allowHalf && index + 0.5 === displayValue) {
return getFilledHalfStarSvg(size);
}
if (index >= displayValue) {
return getFilledStarSvg(size);
}
if (index < displayValue) {
return getFilledStarSvg(size);
}
},
[size],
);
const displayValue = hoverValue || starValue;
return (
<div className={`${classPrefix}-rate`} ref={rootRef} onMouseLeave={() => !disabled && mouseLeaveHandler()}>
{[...Array(count)].map((_, index) =>
showText ? (
<Tooltip key={index} content={texts[displayValue - 1]} className={`${classPrefix}-rate__wrapper`}>
<div
key={index}
onMouseMove={(event) => !disabled && mouseEnterHandler(event, index + 1)}
onClick={(event) => !disabled && clickHandler(event, index + 1)}
style={getStarStyle(index, count, displayValue)}
className={`${classPrefix}-rate__wrapper`}
>
{getStar(allowHalf, index, displayValue)}
</div>
</Tooltip>
) : (
<div
<div
ref={ref}
style={style}
className={classNames(`${classPrefix}-rate`, className)}
onMouseLeave={mouseLeaveHandler}
>
<ul className={`${classPrefix}-rate__list`} style={{ gap }} ref={rootRef}>
{[...Array(count)].map((_, index) => (
<li
key={index}
onMouseMove={(event) => !disabled && mouseEnterHandler(event, index + 1)}
onClick={(event) => !disabled && clickHandler(event, index + 1)}
style={getStarStyle(index, count, displayValue)}
className={`${classPrefix}-rate__wrapper`}
className={classNames(`${classPrefix}-rate__item`, getStarCls(index))}
onClick={(event) => clickHandler(event, index + 1)}
onMouseMove={(event) => mouseEnterHandler(event, index + 1)}
>
{getStar(allowHalf, index, displayValue)}
</div>
),
)}
{showText ? (
<Tooltip key={index} content={texts[displayValue - 1]}>
<div className={`${classPrefix}-rate__star-top`}>
<StarFilledIcon size={size} color={activeColor} />
</div>
<div className={`${classPrefix}-rate__star-bottom`}>
<StarFilledIcon size={size} color={defaultColor} />
</div>
</Tooltip>
) : (
<>
<div className={`${classPrefix}-rate__star-top`}>
<StarFilledIcon size={size} color={activeColor} />
</div>
<div className={`${classPrefix}-rate__star-bottom`}>
<StarFilledIcon size={size} color={defaultColor} />
</div>
</>
)}
</li>
))}
</ul>
{showText && <div className={`${classPrefix}-rate__text`}>{texts[displayValue - 1]}</div>}
</div>
);
};
});

Rate.displayName = 'Rate';
Rate.defaultProps = rateDefaultProps;
Expand Down
Loading

0 comments on commit 8e1a574

Please sign in to comment.