@fjyueke/arco-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +244 -0
- package/metaData/components/action-sheet/doc.md +102 -0
- package/metaData/components/action-sheet/example/index.tsx +199 -0
- package/metaData/components/action-sheet/example/methods.tsx +6 -0
- package/metaData/components/avatar/doc.md +57 -0
- package/metaData/components/avatar/example/group.tsx +69 -0
- package/metaData/components/avatar/example/index.tsx +196 -0
- package/metaData/components/badge/doc.md +28 -0
- package/metaData/components/badge/example/index.tsx +128 -0
- package/metaData/components/button/doc.md +45 -0
- package/metaData/components/button/example/index.tsx +264 -0
- package/metaData/components/carousel/doc.md +89 -0
- package/metaData/components/carousel/example/index.tsx +1481 -0
- package/metaData/components/cell/doc.md +68 -0
- package/metaData/components/cell/example/arrow.tsx +13 -0
- package/metaData/components/cell/example/cell.tsx +71 -0
- package/metaData/components/cell/example/group.tsx +63 -0
- package/metaData/components/cell/example/index.tsx +19 -0
- package/metaData/components/checkbox/doc.md +91 -0
- package/metaData/components/checkbox/example/checkbox.tsx +158 -0
- package/metaData/components/checkbox/example/group.tsx +103 -0
- package/metaData/components/checkbox/example/index.tsx +25 -0
- package/metaData/components/circle-progress/doc.md +40 -0
- package/metaData/components/circle-progress/example/index.tsx +323 -0
- package/metaData/components/collapse/doc.md +74 -0
- package/metaData/components/collapse/example/collapse.tsx +183 -0
- package/metaData/components/collapse/example/group.tsx +136 -0
- package/metaData/components/collapse/example/index.tsx +16 -0
- package/metaData/components/collapse/example/utils.tsx +20 -0
- package/metaData/components/context-provider/doc.md +44 -0
- package/metaData/components/context-provider/example/index.tsx +208 -0
- package/metaData/components/count-down/doc.md +40 -0
- package/metaData/components/count-down/example/hooks.tsx +118 -0
- package/metaData/components/count-down/example/index.tsx +63 -0
- package/metaData/components/date-picker/doc.md +102 -0
- package/metaData/components/date-picker/example/index.tsx +545 -0
- package/metaData/components/dialog/doc.md +200 -0
- package/metaData/components/dialog/example/index.tsx +328 -0
- package/metaData/components/dialog/example/methods.tsx +56 -0
- package/metaData/components/divider/doc.md +25 -0
- package/metaData/components/divider/example/index.tsx +87 -0
- package/metaData/components/dropdown/doc.md +80 -0
- package/metaData/components/dropdown/example/dropdown.tsx +308 -0
- package/metaData/components/dropdown/example/index.tsx +19 -0
- package/metaData/components/dropdown/example/options.tsx +103 -0
- package/metaData/components/dropdown-menu/doc.md +91 -0
- package/metaData/components/dropdown-menu/example/dropdown-menu.tsx +270 -0
- package/metaData/components/dropdown-menu/example/index.tsx +16 -0
- package/metaData/components/ellipsis/doc.md +33 -0
- package/metaData/components/ellipsis/example/index.tsx +93 -0
- package/metaData/components/ellipsis/example/js-ellipsis.tsx +229 -0
- package/metaData/components/ellipsis/example/native-ellipsis.tsx +48 -0
- package/metaData/components/form/doc.md +90 -0
- package/metaData/components/form/example/form-item-context.tsx +8 -0
- package/metaData/components/form/example/form-item.tsx +378 -0
- package/metaData/components/form/example/index.tsx +87 -0
- package/metaData/components/form/example/linked-container.tsx +66 -0
- package/metaData/components/grid/doc.md +39 -0
- package/metaData/components/grid/example/index.tsx +252 -0
- package/metaData/components/icon/doc.md +15 -0
- package/metaData/components/icon/example/index.tsx +32 -0
- package/metaData/components/image/doc.md +62 -0
- package/metaData/components/image/example/index.tsx +528 -0
- package/metaData/components/image-picker/doc.md +116 -0
- package/metaData/components/image-picker/example/add-icon.tsx +14 -0
- package/metaData/components/image-picker/example/index.tsx +175 -0
- package/metaData/components/image-preview/doc.md +119 -0
- package/metaData/components/image-preview/example/index.tsx +1290 -0
- package/metaData/components/image-preview/example/methods.tsx +72 -0
- package/metaData/components/index-bar/doc.md +83 -0
- package/metaData/components/index-bar/example/group.tsx +93 -0
- package/metaData/components/index-bar/example/index.tsx +271 -0
- package/metaData/components/index-bar/example/side-bar.tsx +82 -0
- package/metaData/components/input/doc.md +60 -0
- package/metaData/components/input/example/hooks.tsx +259 -0
- package/metaData/components/input/example/index.tsx +134 -0
- package/metaData/components/input/example/props.tsx +183 -0
- package/metaData/components/keyboard/doc.md +73 -0
- package/metaData/components/keyboard/example/index.tsx +238 -0
- package/metaData/components/load-more/doc.md +48 -0
- package/metaData/components/load-more/example/index.tsx +395 -0
- package/metaData/components/loading/doc.md +34 -0
- package/metaData/components/loading/example/index.tsx +312 -0
- package/metaData/components/masking/doc.md +93 -0
- package/metaData/components/masking/example/index.tsx +410 -0
- package/metaData/components/masking/example/methods.tsx +88 -0
- package/metaData/components/nav-bar/doc.md +40 -0
- package/metaData/components/nav-bar/example/back-icon.tsx +28 -0
- package/metaData/components/nav-bar/example/index.tsx +305 -0
- package/metaData/components/notice-bar/doc.md +34 -0
- package/metaData/components/notice-bar/example/index.tsx +294 -0
- package/metaData/components/notify/doc.md +81 -0
- package/metaData/components/notify/example/index.tsx +171 -0
- package/metaData/components/notify/example/methods.tsx +83 -0
- package/metaData/components/pagination/doc.md +41 -0
- package/metaData/components/pagination/example/arrow.tsx +14 -0
- package/metaData/components/pagination/example/index.tsx +292 -0
- package/metaData/components/picker/doc.md +89 -0
- package/metaData/components/picker/example/index.tsx +217 -0
- package/metaData/components/picker-view/doc.md +57 -0
- package/metaData/components/picker-view/example/cascader.tsx +141 -0
- package/metaData/components/picker-view/example/index.tsx +297 -0
- package/metaData/components/picker-view/example/multi-picker.tsx +66 -0
- package/metaData/components/picker-view/example/picker-cell.tsx +416 -0
- package/metaData/components/popover/doc.md +144 -0
- package/metaData/components/popover/example/index.tsx +23 -0
- package/metaData/components/popover/example/menu.tsx +145 -0
- package/metaData/components/popover/example/popover-inner.tsx +147 -0
- package/metaData/components/popover/example/popover.tsx +268 -0
- package/metaData/components/popup/doc.md +101 -0
- package/metaData/components/popup/example/index.tsx +125 -0
- package/metaData/components/popup/example/methods.tsx +6 -0
- package/metaData/components/popup-swiper/doc.md +108 -0
- package/metaData/components/popup-swiper/example/index.tsx +332 -0
- package/metaData/components/popup-swiper/example/methods.tsx +6 -0
- package/metaData/components/portal/doc.md +14 -0
- package/metaData/components/portal/example/index.tsx +35 -0
- package/metaData/components/progress/doc.md +42 -0
- package/metaData/components/progress/example/index.tsx +256 -0
- package/metaData/components/pull-refresh/doc.md +37 -0
- package/metaData/components/pull-refresh/example/android-pull-refresh.tsx +260 -0
- package/metaData/components/pull-refresh/example/index.tsx +44 -0
- package/metaData/components/pull-refresh/example/ios-pull-refresh.tsx +216 -0
- package/metaData/components/radio/doc.md +89 -0
- package/metaData/components/radio/example/group.tsx +92 -0
- package/metaData/components/radio/example/index.tsx +25 -0
- package/metaData/components/radio/example/radio.tsx +22 -0
- package/metaData/components/rate/doc.md +33 -0
- package/metaData/components/rate/example/index.tsx +281 -0
- package/metaData/components/search-bar/doc.md +97 -0
- package/metaData/components/search-bar/example/association.tsx +89 -0
- package/metaData/components/search-bar/example/cancel-button.tsx +34 -0
- package/metaData/components/search-bar/example/highlight.tsx +66 -0
- package/metaData/components/search-bar/example/index.tsx +251 -0
- package/metaData/components/show-monitor/doc.md +34 -0
- package/metaData/components/show-monitor/example/index.tsx +646 -0
- package/metaData/components/skeleton/doc.md +101 -0
- package/metaData/components/skeleton/example/elements.tsx +290 -0
- package/metaData/components/skeleton/example/index.tsx +96 -0
- package/metaData/components/skeleton/example/skeleton-context.tsx +7 -0
- package/metaData/components/slider/doc.md +42 -0
- package/metaData/components/slider/example/index.tsx +326 -0
- package/metaData/components/slider/example/marks.tsx +50 -0
- package/metaData/components/slider/example/popover.tsx +38 -0
- package/metaData/components/slider/example/thumb.tsx +48 -0
- package/metaData/components/slider/example/useSliderIcon.tsx +38 -0
- package/metaData/components/slider/example/useSliderInit.tsx +170 -0
- package/metaData/components/stepper/doc.md +46 -0
- package/metaData/components/stepper/example/index.tsx +166 -0
- package/metaData/components/stepper/example/useButtonClick.tsx +74 -0
- package/metaData/components/stepper/example/useInputEvent.tsx +65 -0
- package/metaData/components/stepper/example/useValue.tsx +43 -0
- package/metaData/components/steps/doc.md +57 -0
- package/metaData/components/steps/example/index.tsx +123 -0
- package/metaData/components/steps/example/step.tsx +126 -0
- package/metaData/components/sticky/doc.md +45 -0
- package/metaData/components/sticky/example/index.tsx +430 -0
- package/metaData/components/swipe-action/doc.md +44 -0
- package/metaData/components/swipe-action/example/index.tsx +338 -0
- package/metaData/components/swipe-action/example/item.tsx +40 -0
- package/metaData/components/swipe-load/doc.md +43 -0
- package/metaData/components/swipe-load/example/index.tsx +339 -0
- package/metaData/components/swipe-load/example/type.tsx +153 -0
- package/metaData/components/switch/doc.md +38 -0
- package/metaData/components/switch/example/index.tsx +237 -0
- package/metaData/components/tab-bar/doc.md +57 -0
- package/metaData/components/tab-bar/example/index.tsx +18 -0
- package/metaData/components/tab-bar/example/item.tsx +74 -0
- package/metaData/components/tab-bar/example/tab-bar.tsx +105 -0
- package/metaData/components/tabs/doc.md +187 -0
- package/metaData/components/tabs/example/index.tsx +514 -0
- package/metaData/components/tabs/example/tab-cell-underline.tsx +320 -0
- package/metaData/components/tabs/example/tab-cell.tsx +433 -0
- package/metaData/components/tabs/example/tab-pane.tsx +349 -0
- package/metaData/components/tag/doc.md +80 -0
- package/metaData/components/tag/example/index.tsx +23 -0
- package/metaData/components/tag/example/list.tsx +97 -0
- package/metaData/components/tag/example/tag.tsx +85 -0
- package/metaData/components/textarea/doc.md +67 -0
- package/metaData/components/textarea/example/index.tsx +258 -0
- package/metaData/components/toast/doc.md +90 -0
- package/metaData/components/toast/example/index.tsx +335 -0
- package/metaData/components/toast/example/methods.tsx +87 -0
- package/metaData/components/transition/doc.md +19 -0
- package/metaData/components/transition/example/index.tsx +113 -0
- package/metaData/components/uploader/doc.md +82 -0
- package/metaData/components/uploader/example/index.tsx +208 -0
- package/metaData/components-index.json +242 -0
- package/package.json +56 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import React, { useRef, forwardRef, Ref, useImperativeHandle, useContext } from 'react';
|
|
2
|
+
import { cls } from '@arco-design/mobile-utils';
|
|
3
|
+
import { ContextLayout, GlobalContext } from '../context-provider';
|
|
4
|
+
import { BaseProps, getStyleWithVendor } from '../_helpers';
|
|
5
|
+
|
|
6
|
+
export interface DividerProps extends BaseProps {
|
|
7
|
+
/**
|
|
8
|
+
* 是否使用 0.5px 线
|
|
9
|
+
* @en Whether to use hairline
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
hairline?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* 文字
|
|
15
|
+
* @en Divider text
|
|
16
|
+
*/
|
|
17
|
+
content?: React.ReactNode;
|
|
18
|
+
/**
|
|
19
|
+
* 文字位置
|
|
20
|
+
* @en The position of text inside divider
|
|
21
|
+
* @default 'center'
|
|
22
|
+
*/
|
|
23
|
+
align?: 'left' | 'center' | 'right';
|
|
24
|
+
/**
|
|
25
|
+
* 分割线宽度(单边),仅在`align`为`center`时生效
|
|
26
|
+
* @en The width of divider, only take effect when `align` is `center`.
|
|
27
|
+
*/
|
|
28
|
+
width?: string | number;
|
|
29
|
+
/**
|
|
30
|
+
* 子元素,同 content,优先级低于 content
|
|
31
|
+
* @en Children element, same as `content`, with the priority lower than `content`
|
|
32
|
+
* @default null
|
|
33
|
+
*/
|
|
34
|
+
children?: React.ReactNode;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DividerRef {
|
|
38
|
+
/**
|
|
39
|
+
* 最外层 DOM 元素
|
|
40
|
+
* @en The outer DOM element of the component
|
|
41
|
+
*/
|
|
42
|
+
dom: HTMLDivElement | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 划分内容的装饰线
|
|
47
|
+
* @en A partitioning line element
|
|
48
|
+
* @type 布局
|
|
49
|
+
* @type_en Layout
|
|
50
|
+
* @name 分割线
|
|
51
|
+
* @name_en Divider
|
|
52
|
+
*/
|
|
53
|
+
const Divider = forwardRef((props: DividerProps, ref: Ref<DividerRef>) => {
|
|
54
|
+
const { className = '', style, hairline = true, align = 'center', width } = props;
|
|
55
|
+
const domRef = useRef<HTMLDivElement | null>(null);
|
|
56
|
+
const { prefixCls } = useContext(GlobalContext);
|
|
57
|
+
const prefix = `${prefixCls}-divider`;
|
|
58
|
+
const children = props.content || props.children;
|
|
59
|
+
const hasCustomWidth = children && width !== undefined && align === 'center';
|
|
60
|
+
|
|
61
|
+
useImperativeHandle(ref, () => ({
|
|
62
|
+
dom: domRef.current,
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ContextLayout>
|
|
67
|
+
{() => (
|
|
68
|
+
<div
|
|
69
|
+
className={cls(prefix, className, {
|
|
70
|
+
[`${prefix}--hairline`]: hairline,
|
|
71
|
+
[`${prefix}--with-text ${prefix}--text-${align}`]: children,
|
|
72
|
+
[`${prefix}--with-width`]: hasCustomWidth,
|
|
73
|
+
})}
|
|
74
|
+
style={{
|
|
75
|
+
...style,
|
|
76
|
+
...(hasCustomWidth ? getStyleWithVendor({ flexBasis: width }) : {}),
|
|
77
|
+
}}
|
|
78
|
+
ref={domRef}
|
|
79
|
+
>
|
|
80
|
+
{children}
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</ContextLayout>
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
export default Divider;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
### 导航
|
|
2
|
+
|
|
3
|
+
# 下拉面板 Dropdown
|
|
4
|
+
|
|
5
|
+
下拉面板,展示供选择的选择项
|
|
6
|
+
|
|
7
|
+
======
|
|
8
|
+
|
|
9
|
+
> 属性/Props
|
|
10
|
+
|
|
11
|
+
|参数|描述|类型|默认值|
|
|
12
|
+
|----------|-------------|------|------|
|
|
13
|
+
|showDropdown|是否展开下拉框|boolean|必填|
|
|
14
|
+
|options|展示的选项,优先级低于dropdownNode|OptionsItem\[\]|[]|
|
|
15
|
+
|children|自定义下拉框元素|ReactNode|-|
|
|
16
|
+
|onCancel|取消选择|() =\> void|必填|
|
|
17
|
+
|className|自定义类名|string|-|
|
|
18
|
+
|style|自定义样式|CSSProperties|-|
|
|
19
|
+
|extraNode|下拉框底部元素|ReactNode|-|
|
|
20
|
+
|top|定位下拉框出现的位置|number|-|
|
|
21
|
+
|bottom|向上展开时下拉框出现的位置|number|-|
|
|
22
|
+
|direction|展开方向|"up" \| "down"|"down"|
|
|
23
|
+
|height|受控模式,下拉框高度|number|-|
|
|
24
|
+
|maxHeight|下拉框最大的高度|number|500|
|
|
25
|
+
|clickOtherToClose|点击其他区域是否取消选择|boolean|true|
|
|
26
|
+
|touchToClose|是否在触发touchstart时就取消选择,否则在click之后再取消选择|boolean|true|
|
|
27
|
+
|dropdownAnimationTimeout|展开收起动画的时间|number|300|
|
|
28
|
+
|dropdownAnimationFunction|展开收起动画曲线函数|string|"cubic-bezier(0.32, 0.96, 0.6, 1)"|
|
|
29
|
+
|showMask|是否展示遮罩层|boolean|true|
|
|
30
|
+
|maskAnimationTimeout|蒙层动画时长|number|500|
|
|
31
|
+
|maskAnimationFunction|蒙层动画函数|string|"cubic-bezier(0.32, 0.96, 0.6, 1)"|
|
|
32
|
+
|useColumn|使用多列标签样式,传 true 为 4 列,传数字为指定 n 列|number \| boolean|false|
|
|
33
|
+
|optionIcon|选项附带图标|ReactNode|-|
|
|
34
|
+
|mountOnEnter|是否在打开下拉框时再加载内容|boolean|true|
|
|
35
|
+
|unmountOnExit|是否在退出时卸载内容|boolean|true|
|
|
36
|
+
|preventBodyScroll|弹窗打开时是否禁止body的滚动|boolean|true|
|
|
37
|
+
|initialBodyOverflow|页面初始 overflow 状态,即关闭弹窗时 overflow 应该还原的状态|string|第一个全屏组件(弹窗、toast等)打开时页面overflow值|
|
|
38
|
+
|getAnchorElement|用于定位的元素,优先级低于top/bottom|() =\> HTMLElement|当前DOM所在位置的父元素|
|
|
39
|
+
|isStopTouchEl|点击某元素时是否阻止关闭面板, 优先级高于\`getStopTouchElement\`,当 clickOtherToClose=true 时有效|(el: HTMLElement) =\> boolean|-|
|
|
40
|
+
|getStopTouchElements|可阻止关闭面板的元素,当 clickOtherToClose=true 时有效|() =\> HTMLElement\[\]|当前组件的父元素|
|
|
41
|
+
|getScrollContainer|内容内部滚动区域容器,在该容器中未滚动到顶部或底部时会释放滚动|() =\> HTMLElement \| HTMLElement\[\]|-|
|
|
42
|
+
|getPortalContainer|获取挂载容器|() =\> HTMLElement|-|
|
|
43
|
+
|multiple|是否支持多选|boolean|false|
|
|
44
|
+
|defaultSelectedValue|默认选中值|string \| number \| ReactText\[\]|-|
|
|
45
|
+
|selectedValue|当前选择的选项标识|string \| number \| ReactText\[\]|-|
|
|
46
|
+
|onOptionClick|点击选项时触发的回调函数|((val: ReactText, op: OptionsItem) =\> void) \| ((selected: boolean, val: ReactText, op: OptionsItem) =\> void)|-|
|
|
47
|
+
|onOptionChange|当选项改变时触发的回调函数|((val: ReactText, op: OptionsItem) =\> void) \| ((vals: ReactText\[\], op: OptionsItem) =\> void)|-|
|
|
48
|
+
|
|
49
|
+
> 引用/Refs
|
|
50
|
+
|
|
51
|
+
|参数|描述|类型|
|
|
52
|
+
|----------|-------------|------|
|
|
53
|
+
|dom|最外层元素 DOM|HTMLDivElement|
|
|
54
|
+
|
|
55
|
+
> Dropdown.Options
|
|
56
|
+
|
|
57
|
+
|参数|描述|类型|默认值|
|
|
58
|
+
|----------|-------------|------|------|
|
|
59
|
+
|options|展示的选项列表|OptionsItem\[\]|必填|
|
|
60
|
+
|useColumn|使用多列标签样式,传 true 为 4 列,传数字为指定 n 列|number \| boolean|false|
|
|
61
|
+
|icon|选项列表右侧图标|ReactNode|-|
|
|
62
|
+
|multiple|是否支持多选|boolean|false|
|
|
63
|
+
|defaultSelectedValue|默认选中值|string \| number \| ReactText\[\]|-|
|
|
64
|
+
|selectedValue|当前选择的选项标识|string \| number \| ReactText\[\]|-|
|
|
65
|
+
|onOptionClick|点击选项时触发的回调函数|((val: ReactText, op: OptionsItem) =\> void) \| ((selected: boolean, val: ReactText, op: OptionsItem) =\> void)|-|
|
|
66
|
+
|onOptionChange|当选项改变时触发的回调函数|((val: ReactText, op: OptionsItem) =\> void) \| ((vals: ReactText\[\], op: OptionsItem) =\> void)|-|
|
|
67
|
+
|
|
68
|
+
> OptionsItem
|
|
69
|
+
|
|
70
|
+
|参数|描述|类型|
|
|
71
|
+
|----------|-------------|------|
|
|
72
|
+
|label|选项名称|ReactNode|
|
|
73
|
+
|value|选项标识|ReactText|
|
|
74
|
+
|disabled|选项是否可用,默认false表示可用|boolean|
|
|
75
|
+
|
|
76
|
+
> DropdownOptionsRef
|
|
77
|
+
|
|
78
|
+
|参数|描述|类型|
|
|
79
|
+
|----------|-------------|------|
|
|
80
|
+
|dom|最外层元素 DOM|HTMLDivElement|
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
useRef,
|
|
3
|
+
useEffect,
|
|
4
|
+
useCallback,
|
|
5
|
+
forwardRef,
|
|
6
|
+
Ref,
|
|
7
|
+
useImperativeHandle,
|
|
8
|
+
useState,
|
|
9
|
+
CSSProperties,
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { cls, nextTick } from '@arco-design/mobile-utils';
|
|
12
|
+
import { CSSTransition } from 'react-transition-group';
|
|
13
|
+
import { ContextLayout } from '../context-provider';
|
|
14
|
+
import {
|
|
15
|
+
getStyleWithVendor,
|
|
16
|
+
usePopupScroll,
|
|
17
|
+
usePreventBodyScroll,
|
|
18
|
+
useSystem,
|
|
19
|
+
useWindowSize,
|
|
20
|
+
} from '../_helpers';
|
|
21
|
+
import Portal from '../portal';
|
|
22
|
+
import {
|
|
23
|
+
DropdownOptionsProps,
|
|
24
|
+
DropdownOptionsRef,
|
|
25
|
+
DropdownProps,
|
|
26
|
+
DropdownRef,
|
|
27
|
+
ValueType,
|
|
28
|
+
} from './type';
|
|
29
|
+
|
|
30
|
+
export function componentGenerator<
|
|
31
|
+
P extends DropdownOptionsProps = DropdownOptionsProps,
|
|
32
|
+
R extends DropdownOptionsRef = DropdownOptionsRef,
|
|
33
|
+
>(Comp: React.ForwardRefExoticComponent<P & React.RefAttributes<R>>) {
|
|
34
|
+
return forwardRef((props: DropdownProps, ref: Ref<DropdownRef>) => {
|
|
35
|
+
const optionsWrapper = useRef<HTMLDivElement>(null);
|
|
36
|
+
const domRef = useRef<HTMLDivElement | null>(null);
|
|
37
|
+
const {
|
|
38
|
+
className = '',
|
|
39
|
+
style = {},
|
|
40
|
+
options = [],
|
|
41
|
+
showDropdown = false,
|
|
42
|
+
showMask = true,
|
|
43
|
+
direction = 'down',
|
|
44
|
+
height,
|
|
45
|
+
maxHeight = 500,
|
|
46
|
+
touchToClose = true,
|
|
47
|
+
useColumn,
|
|
48
|
+
optionIcon,
|
|
49
|
+
multiple,
|
|
50
|
+
defaultSelectedValue,
|
|
51
|
+
onCancel,
|
|
52
|
+
getAnchorElement,
|
|
53
|
+
isStopTouchEl,
|
|
54
|
+
getStopTouchElements,
|
|
55
|
+
getScrollContainer = () => optionsWrapper.current,
|
|
56
|
+
getPortalContainer,
|
|
57
|
+
clickOtherToClose = true,
|
|
58
|
+
dropdownAnimationTimeout = 300,
|
|
59
|
+
dropdownAnimationFunction = 'cubic-bezier(0.32, 0.96, 0.6, 1)',
|
|
60
|
+
// 坑:ios touchstart会停留300ms 导致点击关闭蒙层后出现点击穿透 触发底部可点区域事件 这里设置更长的timeout规避
|
|
61
|
+
// @en In iOS, touchstart will stay for 300ms, resulting in click penetration after clicking to close the mask, triggering the bottom clickable area event, and setting a longer timeout to avoid it
|
|
62
|
+
maskAnimationTimeout = 500,
|
|
63
|
+
maskAnimationFunction = 'cubic-bezier(0.32, 0.96, 0.6, 1)',
|
|
64
|
+
mountOnEnter = true,
|
|
65
|
+
unmountOnExit = true,
|
|
66
|
+
preventBodyScroll = true,
|
|
67
|
+
initialBodyOverflow,
|
|
68
|
+
} = props;
|
|
69
|
+
const system = useSystem();
|
|
70
|
+
const { windowHeight, windowWidth } = useWindowSize();
|
|
71
|
+
const [positionStyle, setPositionStyle] = useState({ top: '', bottom: '' });
|
|
72
|
+
const [innerValue, setInnerValue] = useState<ValueType>(() => {
|
|
73
|
+
const defaultValue = multiple ? [] : 0;
|
|
74
|
+
return defaultSelectedValue !== void 0 ? defaultSelectedValue : defaultValue;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// down=0为向上展开,1为向下展开
|
|
78
|
+
// @en down=0 is to expand upwards, 1 is to expand downwards
|
|
79
|
+
const [down, setDown] = useState<boolean | null>(null);
|
|
80
|
+
const maskHeight = useRef(0);
|
|
81
|
+
const optionsContainer = useRef<HTMLDivElement>(null);
|
|
82
|
+
const dropdownContainer = useRef<HTMLDivElement>(null);
|
|
83
|
+
const [optionsWrapperHeight, setOptionsWrapperHeight] = useState(0);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 判断是否阻止touch事件
|
|
87
|
+
* @en Determine whether to block touch events
|
|
88
|
+
* */
|
|
89
|
+
|
|
90
|
+
const ifStopTouch = (el: HTMLElement) => {
|
|
91
|
+
if (isStopTouchEl) return isStopTouchEl(el);
|
|
92
|
+
let tempEl: HTMLElement | null = el;
|
|
93
|
+
const elements = getStopTouchElements
|
|
94
|
+
? getStopTouchElements()
|
|
95
|
+
: [dropdownContainer.current?.parentElement];
|
|
96
|
+
for (let i = 0; i < elements.length; i++) {
|
|
97
|
+
while (tempEl && elements[i]) {
|
|
98
|
+
if (tempEl === elements[i]) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
tempEl = tempEl.parentElement;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return false;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
usePopupScroll(showDropdown, domRef.current, getScrollContainer);
|
|
108
|
+
|
|
109
|
+
usePreventBodyScroll(showDropdown, preventBodyScroll, initialBodyOverflow);
|
|
110
|
+
|
|
111
|
+
const updateDown = useCallback(
|
|
112
|
+
(wrapperHeight: number) => {
|
|
113
|
+
// 为0的时候不改变
|
|
114
|
+
// @en Does not change when it is 0
|
|
115
|
+
if (wrapperHeight === 0) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const tempEl = getAnchorElement
|
|
119
|
+
? getAnchorElement()
|
|
120
|
+
: dropdownContainer.current?.parentElement;
|
|
121
|
+
const { bottom, top } = tempEl?.getBoundingClientRect() || {
|
|
122
|
+
bottom: 0,
|
|
123
|
+
top: 0,
|
|
124
|
+
};
|
|
125
|
+
const tempTop = props.top || bottom;
|
|
126
|
+
const tempBottom = props.bottom || windowHeight - top;
|
|
127
|
+
const tempMaskHeight = windowHeight - tempTop;
|
|
128
|
+
// 没有指定方向,且空间足够时,或向上展开的空间不够,向下展开
|
|
129
|
+
// @en If there is no specified direction and there is enough space, or there is not enough space to expand upward, expand downward
|
|
130
|
+
const tempDown =
|
|
131
|
+
(direction !== 'up' && wrapperHeight < tempMaskHeight) ||
|
|
132
|
+
wrapperHeight > tempTop;
|
|
133
|
+
if (tempDown) {
|
|
134
|
+
maskHeight.current = tempMaskHeight;
|
|
135
|
+
setPositionStyle({ top: `${tempTop}px`, bottom: '' });
|
|
136
|
+
} else {
|
|
137
|
+
maskHeight.current = windowHeight - tempBottom;
|
|
138
|
+
setPositionStyle({ top: '', bottom: `${tempBottom}px` });
|
|
139
|
+
}
|
|
140
|
+
setDown(tempDown);
|
|
141
|
+
},
|
|
142
|
+
[props.top, props.bottom, Boolean(getAnchorElement), direction, windowHeight],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 取消选择
|
|
147
|
+
* @en Cancel selection
|
|
148
|
+
* */
|
|
149
|
+
const handleCancel = useCallback(
|
|
150
|
+
e => {
|
|
151
|
+
const optionsWrapperDom = optionsWrapper.current;
|
|
152
|
+
if (!e || !optionsWrapperDom) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const target = e.target as HTMLElement;
|
|
156
|
+
if (!optionsWrapperDom.contains(target) && !ifStopTouch(target)) {
|
|
157
|
+
onCancel();
|
|
158
|
+
}
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
},
|
|
161
|
+
[ifStopTouch, onCancel],
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 当面板放下时,给body添加事件监听,面板收起时,移除
|
|
166
|
+
* @en When the panel is down, add an event listener to the body, and when the panel is retracted, remove it
|
|
167
|
+
* */
|
|
168
|
+
useEffect(() => {
|
|
169
|
+
const needBind = showDropdown && clickOtherToClose;
|
|
170
|
+
const event = touchToClose ? 'touchstart' : 'click';
|
|
171
|
+
if (needBind) {
|
|
172
|
+
document.addEventListener(event, handleCancel);
|
|
173
|
+
}
|
|
174
|
+
return () => {
|
|
175
|
+
if (needBind) {
|
|
176
|
+
document.removeEventListener(event, handleCancel);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
}, [showDropdown, clickOtherToClose, handleCancel]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
let wrapperHeight = 0;
|
|
183
|
+
if (height !== void 0) {
|
|
184
|
+
// 受控模式下,完全交由外层控制
|
|
185
|
+
// @en In controlled mode, it is completely controlled by the outer layer
|
|
186
|
+
wrapperHeight = height;
|
|
187
|
+
} else {
|
|
188
|
+
if (!optionsContainer.current) return;
|
|
189
|
+
if (!showDropdown) {
|
|
190
|
+
wrapperHeight = 0;
|
|
191
|
+
} else {
|
|
192
|
+
wrapperHeight = optionsContainer.current.getBoundingClientRect().height;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
updateDown(wrapperHeight);
|
|
196
|
+
nextTick(() => {
|
|
197
|
+
setOptionsWrapperHeight(wrapperHeight);
|
|
198
|
+
});
|
|
199
|
+
}, [showDropdown, options.length, height, updateDown]);
|
|
200
|
+
|
|
201
|
+
useImperativeHandle(ref, () => ({
|
|
202
|
+
dom: domRef.current,
|
|
203
|
+
}));
|
|
204
|
+
|
|
205
|
+
function getOptionsStyle() {
|
|
206
|
+
const trans = down ? 'translateY(-100%)' : 'translateY(100%)';
|
|
207
|
+
const transStyle: CSSProperties =
|
|
208
|
+
down === null
|
|
209
|
+
? {
|
|
210
|
+
opacity: 0,
|
|
211
|
+
}
|
|
212
|
+
: {
|
|
213
|
+
opacity: 1,
|
|
214
|
+
transform: optionsWrapperHeight ? 'translateY(0)' : trans,
|
|
215
|
+
transition: `all ${dropdownAnimationTimeout}ms ${dropdownAnimationFunction}`,
|
|
216
|
+
};
|
|
217
|
+
return getStyleWithVendor({
|
|
218
|
+
// height: `${optionsWrapperHeight}px`,
|
|
219
|
+
...transStyle,
|
|
220
|
+
overflow: optionsWrapperHeight > maxHeight || height ? 'auto' : 'hidden',
|
|
221
|
+
maxHeight: `${maxHeight}px`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function renderInnerOptions() {
|
|
226
|
+
const value = props.selectedValue !== void 0 ? props.selectedValue : innerValue;
|
|
227
|
+
const optionProps = {
|
|
228
|
+
options,
|
|
229
|
+
useColumn,
|
|
230
|
+
icon: optionIcon,
|
|
231
|
+
selectedValue: value,
|
|
232
|
+
multiple: props.multiple,
|
|
233
|
+
onOptionClick: props.onOptionClick,
|
|
234
|
+
onOptionChange: (vals, op) => {
|
|
235
|
+
setInnerValue(vals);
|
|
236
|
+
props.onOptionChange?.(vals, op);
|
|
237
|
+
},
|
|
238
|
+
} as P;
|
|
239
|
+
return <Comp {...optionProps} />;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return (
|
|
243
|
+
<ContextLayout>
|
|
244
|
+
{({ prefixCls }) => (
|
|
245
|
+
<div ref={dropdownContainer}>
|
|
246
|
+
<Portal getContainer={getPortalContainer}>
|
|
247
|
+
<CSSTransition
|
|
248
|
+
in={showDropdown}
|
|
249
|
+
timeout={Math.max(dropdownAnimationTimeout, maskAnimationTimeout)}
|
|
250
|
+
onEnter={el => {
|
|
251
|
+
el.style.visibility = 'visible';
|
|
252
|
+
}}
|
|
253
|
+
onExited={el => {
|
|
254
|
+
el.style.visibility = 'hidden';
|
|
255
|
+
setDown(null);
|
|
256
|
+
}}
|
|
257
|
+
mountOnEnter={mountOnEnter}
|
|
258
|
+
unmountOnExit={unmountOnExit}
|
|
259
|
+
>
|
|
260
|
+
<div
|
|
261
|
+
className={cls(
|
|
262
|
+
`${prefixCls}-dropdown all-border-box`,
|
|
263
|
+
system,
|
|
264
|
+
className,
|
|
265
|
+
{ 'drop-up': !down },
|
|
266
|
+
)}
|
|
267
|
+
ref={domRef}
|
|
268
|
+
style={{
|
|
269
|
+
height: `${maskHeight.current}px`,
|
|
270
|
+
maxWidth: `${windowWidth}px`,
|
|
271
|
+
...style,
|
|
272
|
+
...positionStyle,
|
|
273
|
+
}}
|
|
274
|
+
>
|
|
275
|
+
<div
|
|
276
|
+
className={cls(`${prefixCls}-dropdown-options`)}
|
|
277
|
+
ref={optionsWrapper}
|
|
278
|
+
style={getOptionsStyle()}
|
|
279
|
+
>
|
|
280
|
+
<div
|
|
281
|
+
className={cls(
|
|
282
|
+
`${prefixCls}-dropdown-options-container`,
|
|
283
|
+
)}
|
|
284
|
+
ref={optionsContainer}
|
|
285
|
+
>
|
|
286
|
+
{props.children || renderInnerOptions()}
|
|
287
|
+
{props.extraNode}
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
{showMask && (
|
|
291
|
+
<div
|
|
292
|
+
className={cls(`${prefixCls}-dropdown-mask`, {
|
|
293
|
+
'is-show': optionsWrapperHeight,
|
|
294
|
+
})}
|
|
295
|
+
style={{
|
|
296
|
+
transition: `opacity ${maskAnimationTimeout}ms ${maskAnimationFunction}`,
|
|
297
|
+
}}
|
|
298
|
+
/>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
</CSSTransition>
|
|
302
|
+
</Portal>
|
|
303
|
+
</div>
|
|
304
|
+
)}
|
|
305
|
+
</ContextLayout>
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { componentWrapper } from '@arco-design/mobile-utils';
|
|
2
|
+
import Options from './options';
|
|
3
|
+
import { componentGenerator } from './dropdown';
|
|
4
|
+
|
|
5
|
+
export * from './type';
|
|
6
|
+
|
|
7
|
+
const Dropdown = componentGenerator(Options);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 下拉面板,展示供选择的选择项
|
|
11
|
+
* @en Dropdown panel, showing options to choose from
|
|
12
|
+
* @type 导航
|
|
13
|
+
* @type_en Navigation
|
|
14
|
+
* @name 下拉面板
|
|
15
|
+
* @name_en Dropdown
|
|
16
|
+
*/
|
|
17
|
+
export default componentWrapper(Dropdown, {
|
|
18
|
+
Options,
|
|
19
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
forwardRef,
|
|
3
|
+
Ref,
|
|
4
|
+
useContext,
|
|
5
|
+
useImperativeHandle,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import { cls } from '@arco-design/mobile-utils';
|
|
11
|
+
import { GlobalContext } from '../context-provider';
|
|
12
|
+
import IconCheck from '../icon/IconCheck';
|
|
13
|
+
import { OptionsItem, DropdownOptionsProps, DropdownOptionsRef, ValueType } from './type';
|
|
14
|
+
|
|
15
|
+
const Options = forwardRef((props: DropdownOptionsProps, ref: Ref<DropdownOptionsRef | null>) => {
|
|
16
|
+
const { options, useColumn, icon, selectedValue, defaultSelectedValue, multiple } = props;
|
|
17
|
+
const { prefixCls } = useContext(GlobalContext);
|
|
18
|
+
const [innerValue, setInnerValue] = useState<ValueType>(() => {
|
|
19
|
+
const defaultValue = multiple ? [] : 0;
|
|
20
|
+
return defaultSelectedValue !== void 0 ? defaultSelectedValue : defaultValue;
|
|
21
|
+
});
|
|
22
|
+
const value = useMemo<ValueType>(
|
|
23
|
+
() => (selectedValue === void 0 ? innerValue : selectedValue),
|
|
24
|
+
[selectedValue, innerValue],
|
|
25
|
+
);
|
|
26
|
+
const colCount = useColumn === true ? 4 : Number(useColumn) || 0;
|
|
27
|
+
const fakeOpts = new Array(
|
|
28
|
+
colCount && options.length % colCount ? colCount - (options.length % colCount) : 0,
|
|
29
|
+
).fill('');
|
|
30
|
+
const minWidthStyle = useColumn ? { minWidth: `${(1 / (colCount + 1)) * 100}%` } : {};
|
|
31
|
+
const domRef = useRef<HTMLDivElement | null>(null);
|
|
32
|
+
|
|
33
|
+
useImperativeHandle(ref, () => ({
|
|
34
|
+
dom: domRef.current,
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 当选项改变时触发的回调函数
|
|
39
|
+
* @en callback when options change
|
|
40
|
+
* */
|
|
41
|
+
function handleOptionChange(e, op: OptionsItem) {
|
|
42
|
+
if (op.disabled) return;
|
|
43
|
+
if (props.multiple === true) {
|
|
44
|
+
if (!Array.isArray(value)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const index = value.indexOf(op.value);
|
|
48
|
+
const checked = index >= 0;
|
|
49
|
+
props.onOptionClick?.(checked, op.value, op);
|
|
50
|
+
const newValue = [...value];
|
|
51
|
+
checked ? newValue.splice(index, 1) : newValue.push(op.value);
|
|
52
|
+
setInnerValue(newValue);
|
|
53
|
+
props.onOptionChange?.(newValue, op);
|
|
54
|
+
} else {
|
|
55
|
+
const newValue = op.value;
|
|
56
|
+
props.onOptionClick?.(newValue, op);
|
|
57
|
+
setInnerValue(newValue);
|
|
58
|
+
newValue !== value && props.onOptionChange?.(newValue, op);
|
|
59
|
+
}
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function checkSelected(op: OptionsItem) {
|
|
64
|
+
return multiple && Array.isArray(value) ? value.includes(op.value) : op.value === value;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
ref={domRef}
|
|
70
|
+
className={cls(`${prefixCls}-dropdown-options-wrap`, {
|
|
71
|
+
'use-column': useColumn,
|
|
72
|
+
})}
|
|
73
|
+
>
|
|
74
|
+
{options.map(op => (
|
|
75
|
+
<div
|
|
76
|
+
className={cls(`${prefixCls}-dropdown-options-item${useColumn ? '-col' : ''}`, {
|
|
77
|
+
disabled: op.disabled,
|
|
78
|
+
selected: checkSelected(op),
|
|
79
|
+
})}
|
|
80
|
+
key={op.value}
|
|
81
|
+
onClick={e => handleOptionChange(e, op)}
|
|
82
|
+
style={minWidthStyle}
|
|
83
|
+
>
|
|
84
|
+
{op.label}
|
|
85
|
+
{icon !== void 0 ? (
|
|
86
|
+
icon
|
|
87
|
+
) : (
|
|
88
|
+
<IconCheck className={`${prefixCls}-dropdown-options-item-icon`} />
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
))}
|
|
92
|
+
{fakeOpts.map((_, i) => (
|
|
93
|
+
<div
|
|
94
|
+
className={cls(`${prefixCls}-dropdown-options-item-fake-col`)}
|
|
95
|
+
key={i}
|
|
96
|
+
style={minWidthStyle}
|
|
97
|
+
/>
|
|
98
|
+
))}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export default Options;
|