-
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.
feat: ✨implement dropdown-menu, weired
- Loading branch information
1 parent
ff61ad0
commit 098be0e
Showing
11 changed files
with
807 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,24 @@ | ||
.van-dropdown-item { | ||
position: fixed; | ||
right: 0; | ||
left: 0; | ||
overflow: hidden; | ||
} | ||
.van-dropdown-item__option { | ||
text-align: left; | ||
} | ||
.van-dropdown-item__option--active .van-dropdown-item__title, | ||
.van-dropdown-item__option--active .van-dropdown-item__icon { | ||
color: #1989fa; | ||
color: var(--dropdown-menu-option-active-color, #1989fa); | ||
} | ||
.van-dropdown-item--up { | ||
top: 0; | ||
} | ||
.van-dropdown-item--down { | ||
bottom: 0; | ||
} | ||
.van-dropdown-item__icon { | ||
display: block; | ||
line-height: inherit; | ||
} |
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,198 @@ | ||
// packages | ||
import React, { | ||
FunctionComponent, | ||
useContext, | ||
CSSProperties, | ||
useEffect, | ||
useRef, | ||
} from 'react'; | ||
import clsx from 'clsx'; | ||
import { View } from 'remax/wechat'; | ||
// internal | ||
import Popup from '../Popup'; | ||
import Cell from '../Cell'; | ||
import Icon from '../Icon'; | ||
import { Select } from '../tools/Switch'; | ||
import pickStyle from '../tools/pick-style'; | ||
import withDefaultProps from '../tools/with-default-props-advance'; | ||
import { | ||
DropdownItemOption, | ||
DropdownItemOptionEvent, | ||
} from '../DropdownMenu/DropdownMenu.constant'; | ||
import { DropdownMenuContext } from '../DropdownMenu/DropdownMenu.context'; | ||
import { DropdownActionTypes } from '../DropdownMenu/DropdownMenu.redux'; | ||
import DropdownItemDimension from './DropdownItemDimension'; | ||
import './DropdownItem.css'; | ||
|
||
// 默认值填充属性 | ||
interface NeutralDropdownItemProps { | ||
disabled: boolean; | ||
} | ||
|
||
interface ExogenousDropdownItemProps { | ||
// 容器类名,用以覆盖内部 | ||
className?: string; | ||
// popup 样式,透传 | ||
popupStyle?: CSSProperties; | ||
// 选项分类 | ||
category: string; | ||
// 选项列表 | ||
options: DropdownItemOption[]; | ||
// 标准受控组件 | ||
value: string; | ||
onChange: (value: string) => void; | ||
} | ||
|
||
type DropdownItemProps = NeutralDropdownItemProps & ExogenousDropdownItemProps; | ||
|
||
// scope | ||
const DefaultDropdownItemProps: NeutralDropdownItemProps = { | ||
disabled: false, | ||
}; | ||
|
||
const DropdownItem: FunctionComponent<DropdownItemProps> = (props) => { | ||
const { | ||
direction, | ||
duration, | ||
overlay, | ||
activeCategory, | ||
activeColor, | ||
id, | ||
zIndex, | ||
dispatch, | ||
} = useContext(DropdownMenuContext); | ||
const { | ||
className, | ||
options, | ||
category, | ||
value, | ||
disabled, | ||
popupStyle = {}, | ||
onChange, | ||
} = props; | ||
const stylesheets: Record<string, CSSProperties> = { | ||
dimensiton: { | ||
zIndex, | ||
}, | ||
popup: { | ||
...popupStyle, | ||
position: 'absolute', | ||
}, | ||
overlay: { | ||
position: 'absolute', | ||
}, | ||
}; | ||
|
||
const visible = category === activeCategory; | ||
// dispatch 初始化后不会变化 | ||
const $dispatch$ = useRef(dispatch); | ||
const $category$ = useRef(category); | ||
|
||
// 事件回调 | ||
const onOptionClick = (event: DropdownItemOptionEvent) => { | ||
// 标准受控组件处理 | ||
onChange(event.currentTarget.dataset.value); | ||
|
||
// 关闭 dropdown | ||
dispatch({ | ||
type: DropdownActionTypes.SwitchCategory, | ||
payload: { | ||
category, | ||
}, | ||
}); | ||
}; | ||
|
||
// 注册类目 | ||
useEffect(() => { | ||
const { current: _dispatch } = $dispatch$; | ||
const { current: _category } = $category$; | ||
|
||
// 更新引用 | ||
$category$.current = category; | ||
|
||
// 剔除 legacy category,特殊变更,排除初次变更 | ||
if (_category !== category && _category !== '') { | ||
_dispatch({ | ||
type: DropdownActionTypes.ExileCategory, | ||
payload: { | ||
category, | ||
}, | ||
}); | ||
} | ||
|
||
// 类目注册信息变更 | ||
_dispatch({ | ||
type: DropdownActionTypes.SettleCategory, | ||
payload: { | ||
category, | ||
disabled, | ||
text: options.find((option) => value === option.value)?.text, | ||
}, | ||
}); | ||
}, [category, disabled, value, options]); | ||
|
||
return ( | ||
<DropdownItemDimension | ||
id={id} | ||
direction={direction} | ||
duration={duration} | ||
visible={visible} | ||
style={stylesheets.dimensiton} | ||
className={className} | ||
> | ||
<Popup | ||
visible={visible} | ||
overlay={overlay} | ||
style={stylesheets.popup} | ||
overlayStyle={stylesheets.overlay} | ||
duration={duration} | ||
position={direction === 'down' ? 'top' : 'bottom'} | ||
> | ||
{options.map((option) => { | ||
const active = option.value === value; | ||
const styleOption = { | ||
'--dropdown-menu-option-active-color': activeColor, | ||
}; | ||
const classNameOption = clsx('van-dropdown-item__option', { | ||
'van-dropdown-item__option--active': active, | ||
}); | ||
|
||
const title = ( | ||
<View className="van-dropdown-item__title">{option.text}</View> | ||
); | ||
const rightIcon = ( | ||
<Select in={active}> | ||
<Icon | ||
name="success" | ||
className="van-dropdown-item__icon" | ||
color={activeColor} | ||
/> | ||
</Select> | ||
); | ||
|
||
return ( | ||
<View | ||
data-value={option.value} | ||
key={option.value} | ||
className={classNameOption} | ||
style={styleOption as CSSProperties} | ||
onClick={onOptionClick} | ||
> | ||
<Cell | ||
clickable | ||
title={title} | ||
icon={option.icon} | ||
rightIcon={rightIcon} | ||
/> | ||
</View> | ||
); | ||
})} | ||
</Popup> | ||
</DropdownItemDimension> | ||
); | ||
}; | ||
|
||
export default withDefaultProps< | ||
ExogenousDropdownItemProps, | ||
NeutralDropdownItemProps | ||
>(DefaultDropdownItemProps)(DropdownItem); |
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,89 @@ | ||
// packages | ||
import React, { | ||
FunctionComponent, | ||
CSSProperties, | ||
useState, | ||
useEffect, | ||
} from 'react'; | ||
import { useNativeEffect } from 'remax'; | ||
import clsx from 'clsx'; | ||
import { View } from 'remax/wechat'; | ||
// internal | ||
import './DropdownItem.css'; | ||
|
||
// 默认值填充属性 | ||
interface DropdownItemDimensionProps { | ||
// 选择器 | ||
id: string; | ||
// 容器可见标记 | ||
visible: boolean; | ||
// 透传 | ||
direction: 'up' | 'down'; | ||
duration: number; | ||
// 容器类名,用以覆盖内部 | ||
className?: string; | ||
// 覆盖内部样式,此处特定用途,处理 zIndex | ||
style: CSSProperties; | ||
} | ||
|
||
const DropdownItemDimension: FunctionComponent<DropdownItemDimensionProps> = ( | ||
props | ||
) => { | ||
const { | ||
id, | ||
direction, | ||
duration, | ||
visible, | ||
className, | ||
style, | ||
children, | ||
} = props; | ||
const classnames = { | ||
container: clsx( | ||
className, | ||
'van-dropdown-item', | ||
`van-dropdown-item--${direction}` | ||
), | ||
}; | ||
const [proxyStyle, setProxyStyle] = useState<CSSProperties>({}); | ||
const bindingStyle = { ...style, ...proxyStyle }; | ||
|
||
useEffect(() => { | ||
if (visible) { | ||
setProxyStyle((acc) => ({ ...acc, display: 'block' })); | ||
|
||
return () => {}; | ||
} | ||
|
||
const timer = setTimeout(() => { | ||
setProxyStyle((acc) => ({ ...acc, display: 'none' })); | ||
}, duration); | ||
|
||
return () => clearTimeout(timer); | ||
}, [visible, duration]); | ||
|
||
useNativeEffect(() => { | ||
wx.createSelectorQuery() | ||
.select(`#${id}`) | ||
.boundingClientRect() | ||
.exec(([rect]: [WechatMiniprogram.BoundingClientRectCallbackResult]) => { | ||
const { bottom, top } = rect; | ||
const { windowHeight } = wx.getSystemInfoSync(); | ||
const patch = | ||
direction === 'down' | ||
? { top: `${bottom}px` } | ||
: { bottom: `${windowHeight - top}px` }; | ||
|
||
// popup 定位为 fixed | ||
setProxyStyle((acc) => ({ ...acc, ...patch })); | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<View style={bindingStyle} className={classnames.container}> | ||
{children} | ||
</View> | ||
); | ||
}; | ||
|
||
export default DropdownItemDimension; |
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 './DropdownItem'; |
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,31 @@ | ||
export interface DropdownItemOption { | ||
text: string; | ||
value: string; | ||
icon?: string; | ||
} | ||
|
||
// 头部 dropdown bar,数据来源为子组件上报 | ||
export interface DropdownMenuUnit { | ||
category: string; | ||
text?: string; | ||
disabled?: boolean; | ||
} | ||
|
||
// 点击回调事件 | ||
export interface DropdownMenuUnitEvent { | ||
currentTarget: { | ||
dataset: { | ||
category: string; | ||
disabled: boolean; | ||
}; | ||
}; | ||
} | ||
|
||
export interface DropdownItemOptionEvent { | ||
currentTarget: { | ||
dataset: { | ||
value: string; | ||
category: string; | ||
}; | ||
}; | ||
} |
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,30 @@ | ||
// packages | ||
import { createContext } from 'react'; | ||
// internal | ||
import { DropdownMenuActions } from './DropdownMenu.redux'; | ||
|
||
export interface DropdownMenuContextPayload { | ||
// DropdownItem 属性透传 | ||
activeColor: string; | ||
overlay: boolean; | ||
zIndex: number; | ||
direction: 'up' | 'down'; | ||
duration: number; | ||
// DropdownMenu 内部逻辑 | ||
id: string; // 限定查询范围 | ||
activeCategory: string; | ||
dispatch: (action: DropdownMenuActions) => void; | ||
} | ||
|
||
export const DropdownMenuContext = createContext<DropdownMenuContextPayload>({ | ||
activeColor: '#1989fa', | ||
overlay: true, | ||
zIndex: 10, | ||
direction: 'down', | ||
duration: 200, | ||
id: 'placeholder', | ||
activeCategory: 'placeholder', | ||
dispatch: () => { | ||
throw new Error('Default dispatch function should never have been called'); | ||
}, | ||
}); |
Oops, something went wrong.