-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
91df59f
commit 788e148
Showing
5 changed files
with
270 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
.van-rate { | ||
display: inline-flex; | ||
user-select: none; | ||
} | ||
.van-rate__item { | ||
position: relative; | ||
padding: 0 2px; | ||
padding: 0 var(--rate-horizontal-padding, 2px); | ||
} | ||
.van-rate__icon { | ||
display: block; | ||
height: 1em; | ||
font-size: 20px; | ||
font-size: var(--rate-icon-size, 20px); | ||
} | ||
.van-rate__icon--half { | ||
position: absolute; | ||
top: 0; | ||
width: 0.5em; | ||
overflow: hidden; | ||
left: 2px; | ||
left: var(--rate-horizontal-padding, 2px); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// packages | ||
import React, { FunctionComponent, useMemo, CSSProperties } from 'react'; | ||
import clsx from 'clsx'; | ||
import { View } from 'remax/wechat'; | ||
// internal | ||
import Icon from '../Icon'; | ||
import uuid from '../tools/uuid'; | ||
import withDefaultProps from '../tools/with-default-props-advance'; | ||
import pickStyle from '../tools/pick-style'; | ||
import { Select } from '../tools/Switch'; | ||
import './Rate.css'; | ||
|
||
// 默认值填充属性 | ||
interface NeutralRateProps { | ||
count: number; | ||
size: string; | ||
gutter: string; | ||
color: string; | ||
voidColor: string; | ||
icon: string; | ||
voidIcon: string; | ||
allowHalf: boolean; | ||
readonly: boolean; | ||
disabled: boolean; | ||
disabledColor: string; | ||
touchable: boolean; | ||
} | ||
|
||
interface ExogenousRateProps { | ||
// 受控组件处理 | ||
value: number; | ||
// 容器类名,用以覆盖内部 | ||
className?: string; | ||
// 事件回调 | ||
onChange?: (score: number) => void; | ||
} | ||
|
||
type RateProps = NeutralRateProps & ExogenousRateProps; | ||
|
||
const DefaultRateProps: NeutralRateProps = { | ||
count: 5, | ||
size: '20px', | ||
gutter: '4px', | ||
color: '#ffd21e', | ||
voidColor: '#c7c7c7', | ||
icon: 'star', | ||
voidIcon: 'star-o', | ||
allowHalf: false, | ||
readonly: false, | ||
disabled: false, | ||
disabledColor: '#bdbdbd', | ||
touchable: true, | ||
}; | ||
|
||
const Rate: FunctionComponent<RateProps> = (props) => { | ||
const { | ||
className, | ||
count, | ||
gutter, | ||
value, | ||
icon, | ||
voidIcon, | ||
size, | ||
disabledColor, | ||
disabled, | ||
color, | ||
voidColor, | ||
readonly, | ||
allowHalf, | ||
onChange, | ||
} = props; | ||
const classnames = { | ||
container: clsx(className, 'van-rate'), | ||
}; | ||
const stylesheets: Record<string, CSSProperties> = { | ||
iconWrap: { | ||
fontSize: size, | ||
}, | ||
}; | ||
// ID 限制范围,便捷查询,跳过匹配结果过滤 | ||
const id = useMemo(() => uuid(), []); | ||
const stars = useMemo( | ||
() => | ||
Array.from({ length: count }).map((_, index) => ({ | ||
// 序号 +1 转换为自然序 | ||
score: index + 1, | ||
style: pickStyle({ | ||
paddingRight: index === count - 1 ? '' : gutter, | ||
}), | ||
})), | ||
[count, gutter] | ||
); | ||
|
||
// icon 属性计算,不在 element 内部纠缠 | ||
const tellMeName = (score: number, half = false) => | ||
(half ? score - 0.5 : score) <= value ? icon : voidIcon; | ||
const tellMeColor = (score: number, half = false) => | ||
// eslint-disable-next-line no-nested-ternary | ||
disabled | ||
? disabledColor | ||
: (half ? score - 0.5 : score) <= value | ||
? color | ||
: voidColor; | ||
|
||
// 事件回调 | ||
const onSelect = (event: any) => { | ||
if (!disabled && !readonly) { | ||
if (typeof onChange === 'function') { | ||
onChange(event.currentTarget.dataset.score as number); | ||
} | ||
} | ||
}; | ||
|
||
const onTouchMove = (event: any) => { | ||
const clientX = event.touches[0].clientX as number; | ||
|
||
wx.createSelectorQuery() | ||
.selectAll(`#${id} .van-rate__icon`) | ||
.boundingClientRect() | ||
.exec( | ||
([rects]: [WechatMiniprogram.BoundingClientRectCallbackResult[]]) => { | ||
// 半星覆盖策略略有复杂,需要保障半星有限匹配 | ||
const currentTarget = rects | ||
.sort((prev, current) => prev.right - current.right) | ||
.find((rect) => clientX >= rect.left && clientX <= rect.right); | ||
|
||
// rect 结果包含 dataset,因而此处可以临时替代 | ||
if (currentTarget) { | ||
onSelect({ | ||
currentTarget, | ||
}); | ||
} | ||
} | ||
); | ||
}; | ||
|
||
return ( | ||
<View id={id} className={classnames.container} onTouchMove={onTouchMove}> | ||
{stars.map((star) => ( | ||
<View className="van-rate__item" key={star.score} style={star.style}> | ||
<View | ||
data-score={star.score} | ||
className="van-rate__icon" | ||
style={stylesheets.iconWrap} | ||
onClick={onSelect} | ||
> | ||
<Icon | ||
name={tellMeName(star.score)} | ||
color={tellMeColor(star.score)} | ||
/> | ||
</View> | ||
{/* half 与 full 共存,未严格匹配时与底层 icon 重叠,保持未选中状态,hack */} | ||
<Select in={allowHalf}> | ||
<View | ||
data-score={star.score - 0.5} | ||
className="van-rate__icon van-rate__icon--half" | ||
style={stylesheets.iconWrap} | ||
onClick={onSelect} | ||
> | ||
<Icon | ||
name={tellMeName(star.score, true)} | ||
color={tellMeColor(star.score, true)} | ||
/> | ||
</View> | ||
</Select> | ||
</View> | ||
))} | ||
</View> | ||
); | ||
}; | ||
|
||
export default withDefaultProps<ExogenousRateProps, NeutralRateProps>( | ||
DefaultRateProps | ||
)(Rate); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/** | ||
* @description - nothing but export component | ||
*/ | ||
export { default } from './Rate'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @description generate random identity | ||
* @see https://gist.github.com/gordonbrander/2230317 | ||
*/ | ||
|
||
function uuid(): string { | ||
return `_${Math.random().toString(36).substr(2, 9)}`; | ||
} | ||
|
||
export default uuid; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// packages | ||
import React, { useState } from 'react'; | ||
import { View, Text } from 'remax/wechat'; | ||
|
||
// internal | ||
import Rate from '../../../packages/Rate'; | ||
|
||
export default () => { | ||
const [value, setValue] = useState(4); | ||
const onChange = (score: number) => setValue(score); | ||
|
||
const [value1, setValue1] = useState(3.5); | ||
const onChange1 = (score: number) => setValue1(score); | ||
|
||
return ( | ||
<View className="demo-block"> | ||
<Text className="demo-block__title">基础用法</Text> | ||
<View className="demo-block__content"> | ||
<Rate value={value} onChange={onChange} /> | ||
</View> | ||
|
||
<Text className="demo-block__title">自定义图标</Text> | ||
<View className="demo-block__content"> | ||
<Rate icon="like" voidIcon="like-o" value={value} onChange={onChange} /> | ||
</View> | ||
|
||
<Text className="demo-block__title">自定义样式</Text> | ||
<View className="demo-block__content"> | ||
<Rate | ||
size="24px" | ||
color="#ee0a24" | ||
voidColor="#eee" | ||
value={value} | ||
onChange={onChange} | ||
/> | ||
</View> | ||
|
||
<Text className="demo-block__title">自定义数量</Text> | ||
<View className="demo-block__content"> | ||
<Rate count={6} value={value} onChange={onChange} /> | ||
</View> | ||
|
||
<Text className="demo-block__title">禁用状态</Text> | ||
<View className="demo-block__content"> | ||
<Rate disabled value={value} onChange={onChange} /> | ||
</View> | ||
|
||
<Text className="demo-block__title">只读状态</Text> | ||
<View className="demo-block__content"> | ||
<Rate readonly value={value} onChange={onChange} /> | ||
</View> | ||
|
||
<Text className="demo-block__title">半星</Text> | ||
<View className="demo-block__content"> | ||
<Rate value={value1} allowHalf onChange={onChange1} /> | ||
</View> | ||
</View> | ||
); | ||
}; |