Skip to content

Commit

Permalink
feat: ✨implement dropdown-menu, weired
Browse files Browse the repository at this point in the history
  • Loading branch information
huang-xiao-jian committed Jul 7, 2020
1 parent ff61ad0 commit 098be0e
Show file tree
Hide file tree
Showing 11 changed files with 807 additions and 0 deletions.
24 changes: 24 additions & 0 deletions packages/DropdownItem/DropdownItem.css
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;
}
198 changes: 198 additions & 0 deletions packages/DropdownItem/DropdownItem.tsx
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);
89 changes: 89 additions & 0 deletions packages/DropdownItem/DropdownItemDimension.tsx
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;
4 changes: 4 additions & 0 deletions packages/DropdownItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @description - nothing but export component
*/
export { default } from './DropdownItem';
31 changes: 31 additions & 0 deletions packages/DropdownMenu/DropdownMenu.constant.ts
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;
};
};
}
30 changes: 30 additions & 0 deletions packages/DropdownMenu/DropdownMenu.context.ts
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');
},
});
Loading

0 comments on commit 098be0e

Please sign in to comment.