From 1c1ef86988f87274067a055658a44849fd56460a Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 16 Jan 2025 15:39:08 +0800 Subject: [PATCH 01/24] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96picker?= =?UTF-8?q?view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.json | 13 +++++++ .../pickerview/__test__/pickerview.spec.tsx | 9 +++++ src/packages/pickerview/demo.taro.tsx | 31 ++++++++++++++++ src/packages/pickerview/demo.tsx | 25 +++++++++++++ src/packages/pickerview/demos/h5/demo1.tsx | 13 +++++++ src/packages/pickerview/demos/taro/demo1.tsx | 13 +++++++ src/packages/pickerview/doc.md | 36 +++++++++++++++++++ src/packages/pickerview/index.taro.ts | 4 +++ src/packages/pickerview/index.ts | 4 +++ src/packages/pickerview/pickerview.scss | 2 ++ src/packages/pickerview/pickerview.taro.tsx | 28 +++++++++++++++ src/packages/pickerview/pickerview.tsx | 26 ++++++++++++++ src/packages/pickerview/types.ts | 3 ++ 13 files changed, 207 insertions(+) create mode 100644 src/packages/pickerview/__test__/pickerview.spec.tsx create mode 100644 src/packages/pickerview/demo.taro.tsx create mode 100644 src/packages/pickerview/demo.tsx create mode 100644 src/packages/pickerview/demos/h5/demo1.tsx create mode 100644 src/packages/pickerview/demos/taro/demo1.tsx create mode 100644 src/packages/pickerview/doc.md create mode 100644 src/packages/pickerview/index.taro.ts create mode 100644 src/packages/pickerview/index.ts create mode 100644 src/packages/pickerview/pickerview.scss create mode 100644 src/packages/pickerview/pickerview.taro.tsx create mode 100644 src/packages/pickerview/pickerview.tsx create mode 100644 src/packages/pickerview/types.ts diff --git a/src/config.json b/src/config.json index 72a1f2e5d6..15d33f6f26 100644 --- a/src/config.json +++ b/src/config.json @@ -690,6 +690,19 @@ "author": "dsj", "dd": false }, + { + "version": "3.0.0", + "name": "PickerView", + "type": "component", + "cName": "选择器视图", + "desc": "PickerView 是 Picker 的内容区域。", + "sort": 15, + "show": true, + "taro": true, + "v15": false, + "dd": true, + "author": "songsong" + }, { "version": "3.0.0", "name": "Radio", diff --git a/src/packages/pickerview/__test__/pickerview.spec.tsx b/src/packages/pickerview/__test__/pickerview.spec.tsx new file mode 100644 index 0000000000..d63fdee45c --- /dev/null +++ b/src/packages/pickerview/__test__/pickerview.spec.tsx @@ -0,0 +1,9 @@ +import React from 'react' +import { render } from '@testing-library/react' +import '@testing-library/jest-dom' +import { PickerView } from '../pickerview' + +test('should match snapshot', () => { + const { container } = render() + expect(container).toMatchSnapshot() +}) diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx new file mode 100644 index 0000000000..14a5c1e209 --- /dev/null +++ b/src/packages/pickerview/demo.taro.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import Taro from '@tarojs/taro' +import { ScrollView, View } from '@tarojs/components' +import { useTranslate } from '@/sites/assets/locale/taro' +import Header from '@/sites/components/header' +import Demo1 from './demos/taro/demo1' + +const PickerViewDemo = () => { + const [translated] = useTranslate({ + 'zh-CN': { + title: '基础用法', + }, + 'en-US': { + title: 'Basic Usage', + }, + 'zh-TW': { + title: '基礎用法', + }, + }) + return ( + <> +
+ + {translated.title} + + + + ) +} + +export default PickerViewDemo diff --git a/src/packages/pickerview/demo.tsx b/src/packages/pickerview/demo.tsx new file mode 100644 index 0000000000..a9570d035f --- /dev/null +++ b/src/packages/pickerview/demo.tsx @@ -0,0 +1,25 @@ +import React from 'react' +import { useTranslate } from '@/sites/assets/locale' +import Demo1 from './demos/h5/demo1' + +const PickerViewDemo = () => { + const [translated] = useTranslate({ + 'zh-CN': { + title: '基础用法', + }, + 'en-US': { + title: 'Basic Usage', + }, + 'zh-TW': { + title: '基礎用法', + }, + }) + return ( +
+

{translated.title}

+ +
+ ) +} + +export default PickerViewDemo diff --git a/src/packages/pickerview/demos/h5/demo1.tsx b/src/packages/pickerview/demos/h5/demo1.tsx new file mode 100644 index 0000000000..22e86eaff9 --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo1.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { Cell, PickerView } from '@nutui/nutui-react' +// import { Dongdong } from '@nutui/icons-react' + +const Demo1 = () => { + return ( + + + + ) +} + +export default Demo1 diff --git a/src/packages/pickerview/demos/taro/demo1.tsx b/src/packages/pickerview/demos/taro/demo1.tsx new file mode 100644 index 0000000000..e0b0467aa2 --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo1.tsx @@ -0,0 +1,13 @@ +import React from 'react' +import { Cell, PickerView } from '@nutui/nutui-react-taro' +// import { Dongdong } from '@nutui/icons-react-taro' + +const Demo1 = () => { + return ( + + + + ) +} + +export default Demo1 diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md new file mode 100644 index 0000000000..d9b1436be8 --- /dev/null +++ b/src/packages/pickerview/doc.md @@ -0,0 +1,36 @@ +# PickerView 选择器视图 + +PickerView 是 Picker 的内容区域。 + +## 引入 + +```tsx +import { name } from '@nutui/nutui-react' +``` + +## 示例代码 + +### 基础用法 + +:::demo + + + +::: + +## PickerView + +### Props + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| name | 图标名 | String | - | + +## 主题定制 + +### 样式变量 + +组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/component/configprovider)。 +| 名称 | 说明 | 默认值 | +| --- | --- | --- | +| --nutui-pickerview-height | badge 的高度 | `14px` | diff --git a/src/packages/pickerview/index.taro.ts b/src/packages/pickerview/index.taro.ts new file mode 100644 index 0000000000..b7fd78006c --- /dev/null +++ b/src/packages/pickerview/index.taro.ts @@ -0,0 +1,4 @@ +import { PickerView } from './pickerview.taro' + +export type { PickerViewProps } from './types' +export default PickerView diff --git a/src/packages/pickerview/index.ts b/src/packages/pickerview/index.ts new file mode 100644 index 0000000000..1865a8b105 --- /dev/null +++ b/src/packages/pickerview/index.ts @@ -0,0 +1,4 @@ +import { PickerView } from './pickerview' + +export type { PickerViewProps } from './types' +export default PickerView diff --git a/src/packages/pickerview/pickerview.scss b/src/packages/pickerview/pickerview.scss new file mode 100644 index 0000000000..78d89b48d9 --- /dev/null +++ b/src/packages/pickerview/pickerview.scss @@ -0,0 +1,2 @@ +.nut-pickerview { +} diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx new file mode 100644 index 0000000000..5db2fd07ad --- /dev/null +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -0,0 +1,28 @@ +import React, { FunctionComponent } from 'react' +import classNames from 'classnames' +import { View } from '@tarojs/components' +import { ComponentDefaults } from '@/utils/typings' +import { PickerViewProps } from './types' + +// import { useConfig } from '@/packages/configprovider/configprovider.taro' +// import { useRtl } from '@/packages/configprovider/index.taro' + +const defaultProps = { + ...ComponentDefaults, +} as PickerViewProps +export const PickerView: FunctionComponent< + Partial & React.HTMLAttributes +> = (props) => { + // const { locale } = useConfig() + // const rtl = useRtl() + const { className, style } = { ...defaultProps, ...props } + const classPrefix = 'nut-pickerview' + const cls = classNames(classPrefix, className) + return ( + + PickerView + + ) +} + +PickerView.displayName = 'NutPickerView' diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx new file mode 100644 index 0000000000..0f33d583db --- /dev/null +++ b/src/packages/pickerview/pickerview.tsx @@ -0,0 +1,26 @@ +import React, { FunctionComponent } from 'react' +import classNames from 'classnames' +import { ComponentDefaults } from '@/utils/typings' +import { PickerViewProps } from './types' +// import { useConfig } from '@/packages/configprovider' +// import { useRtl } from '@/packages/configprovider' + +const defaultProps = { + ...ComponentDefaults, +} as PickerViewProps +export const PickerView: FunctionComponent< + Partial & React.HTMLAttributes +> = (props) => { + // const { locale } = useConfig() + // const rtl = useRtl() + const { className, style } = { ...defaultProps, ...props } + const classPrefix = 'nut-pickerview' + const cls = classNames(classPrefix, className) + return ( +
+ PickerView +
+ ) +} + +PickerView.displayName = 'NutPickerView' diff --git a/src/packages/pickerview/types.ts b/src/packages/pickerview/types.ts new file mode 100644 index 0000000000..a72b2c3b4e --- /dev/null +++ b/src/packages/pickerview/types.ts @@ -0,0 +1,3 @@ +import { BasicComponent } from '@/utils/typings' + +export interface PickerViewProps extends BasicComponent {} From 3acf368eb1f983ecdd7ae331d91ec6ff44773272 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 22 Jan 2025 18:01:14 +0800 Subject: [PATCH 02/24] feat: add pickerView --- .../pickerview/__test__/pickerview.spec.tsx | 2 +- src/packages/pickerview/demo.taro.tsx | 18 + src/packages/pickerview/demo.tsx | 18 + src/packages/pickerview/demos/h5/demo1.tsx | 32 +- src/packages/pickerview/demos/h5/demo2.tsx | 34 ++ src/packages/pickerview/demos/h5/demo3.tsx | 39 +++ src/packages/pickerview/demos/h5/demo4.tsx | 38 ++ src/packages/pickerview/demos/taro/demo1.tsx | 32 +- src/packages/pickerview/demos/taro/demo2.tsx | 34 ++ src/packages/pickerview/demos/taro/demo3.tsx | 39 +++ src/packages/pickerview/demos/taro/demo4.tsx | 38 ++ src/packages/pickerview/doc.md | 47 ++- src/packages/pickerview/index.taro.ts | 2 +- src/packages/pickerview/index.ts | 2 +- src/packages/pickerview/pickerroller.taro.tsx | 328 ++++++++++++++++++ src/packages/pickerview/pickerroller.tsx | 302 ++++++++++++++++ src/packages/pickerview/pickerview.scss | 78 +++++ src/packages/pickerview/pickerview.taro.tsx | 111 +++++- src/packages/pickerview/pickerview.tsx | 110 +++++- src/packages/pickerview/types.ts | 32 +- src/styles/variables.scss | 4 +- 21 files changed, 1295 insertions(+), 45 deletions(-) create mode 100644 src/packages/pickerview/demos/h5/demo2.tsx create mode 100644 src/packages/pickerview/demos/h5/demo3.tsx create mode 100644 src/packages/pickerview/demos/h5/demo4.tsx create mode 100644 src/packages/pickerview/demos/taro/demo2.tsx create mode 100644 src/packages/pickerview/demos/taro/demo3.tsx create mode 100644 src/packages/pickerview/demos/taro/demo4.tsx create mode 100644 src/packages/pickerview/pickerroller.taro.tsx create mode 100644 src/packages/pickerview/pickerroller.tsx diff --git a/src/packages/pickerview/__test__/pickerview.spec.tsx b/src/packages/pickerview/__test__/pickerview.spec.tsx index d63fdee45c..089e995187 100644 --- a/src/packages/pickerview/__test__/pickerview.spec.tsx +++ b/src/packages/pickerview/__test__/pickerview.spec.tsx @@ -1,7 +1,7 @@ import React from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' -import { PickerView } from '../pickerview' +import PickerView from '../pickerview' test('should match snapshot', () => { const { container } = render() diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx index 14a5c1e209..9773e2cae5 100644 --- a/src/packages/pickerview/demo.taro.tsx +++ b/src/packages/pickerview/demo.taro.tsx @@ -4,17 +4,29 @@ import { ScrollView, View } from '@tarojs/components' import { useTranslate } from '@/sites/assets/locale/taro' import Header from '@/sites/components/header' import Demo1 from './demos/taro/demo1' +import Demo2 from './demos/taro/demo2' +import Demo3 from './demos/taro/demo3' +import Demo4 from './demos/taro/demo4' const PickerViewDemo = () => { const [translated] = useTranslate({ 'zh-CN': { title: '基础用法', + adjustHeight: '自适应高度', + multiColumn: '多列', + controlled: '受控', }, 'en-US': { title: 'Basic Usage', + adjustHeight: 'Adjust Height', + multiColumn: 'Multi Column', + controlled: 'Controlled', }, 'zh-TW': { title: '基礎用法', + adjustHeight: '自適應高度', + multiColumn: '多列', + controlled: '受控', }, }) return ( @@ -23,6 +35,12 @@ const PickerViewDemo = () => { {translated.title} + {translated.controlled} + + {translated.adjustHeight} + + {translated.multiColumn} + ) diff --git a/src/packages/pickerview/demo.tsx b/src/packages/pickerview/demo.tsx index a9570d035f..b45f73be58 100644 --- a/src/packages/pickerview/demo.tsx +++ b/src/packages/pickerview/demo.tsx @@ -1,23 +1,41 @@ import React from 'react' import { useTranslate } from '@/sites/assets/locale' import Demo1 from './demos/h5/demo1' +import Demo2 from './demos/h5/demo2' +import Demo3 from './demos/h5/demo3' +import Demo4 from './demos/h5/demo4' const PickerViewDemo = () => { const [translated] = useTranslate({ 'zh-CN': { title: '基础用法', + adjustHeight: '自适应高度', + multiColumn: '多列', + controlled: '受控', }, 'en-US': { title: 'Basic Usage', + adjustHeight: 'Adjust Height', + multiColumn: 'Multi Column', + controlled: 'Controlled', }, 'zh-TW': { title: '基礎用法', + adjustHeight: '自適應高度', + multiColumn: '多列', + controlled: '受控', }, }) return (

{translated.title}

+

{translated.controlled}

+ +

{translated.adjustHeight}

+ +

{translated.multiColumn}

+
) } diff --git a/src/packages/pickerview/demos/h5/demo1.tsx b/src/packages/pickerview/demos/h5/demo1.tsx index 22e86eaff9..cacf341036 100644 --- a/src/packages/pickerview/demos/h5/demo1.tsx +++ b/src/packages/pickerview/demos/h5/demo1.tsx @@ -1,13 +1,33 @@ import React from 'react' -import { Cell, PickerView } from '@nutui/nutui-react' -// import { Dongdong } from '@nutui/icons-react' +import { PickerView, Cell } from '@nutui/nutui-react' const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + return ( - - - + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + ) } - export default Demo1 diff --git a/src/packages/pickerview/demos/h5/demo2.tsx b/src/packages/pickerview/demos/h5/demo2.tsx new file mode 100644 index 0000000000..dd16c8a92e --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo2.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/demos/h5/demo3.tsx b/src/packages/pickerview/demos/h5/demo3.tsx new file mode 100644 index 0000000000..555c655e39 --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo3.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react' +import { PickerView, Cell } from '@nutui/nutui-react' +import isEqual from 'react-fast-compare' + +const Demo3 = () => { + const listData = [ + [ + { label: '周一', value: 'Monday' }, + { label: '周二', value: 'Tuesday' }, + { label: '周三', value: 'Wednesday' }, + { label: '周四', value: 'Thursday' }, + { label: '周五', value: 'Friday' }, + ], + [ + { label: '上午', value: 'Morning' }, + { label: '下午', value: 'Afternoon' }, + { label: '晚上', value: 'Evening' }, + ], + ] + const [value, setValue] = useState(['Tuesday', 'Evening']) + + return ( + <> + + { + console.log('onChange', value, selectOptions) + if (isEqual(value, ['Tuesday', 'Afternoon'])) { + setValue(['Monday', 'Evening']) + } + }} + /> + + + ) +} +export default Demo3 diff --git a/src/packages/pickerview/demos/h5/demo4.tsx b/src/packages/pickerview/demos/h5/demo4.tsx new file mode 100644 index 0000000000..e205b5513a --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo4.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import { PickerView, Cell } from '@nutui/nutui-react' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + const [value, setValue] = useState([2]) + + return ( + <> + + { + console.log('onChange', value, selectOptions) + if (value[0] === 3) { + setValue([1]) + } + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/demos/taro/demo1.tsx b/src/packages/pickerview/demos/taro/demo1.tsx index e0b0467aa2..4d3d363bc2 100644 --- a/src/packages/pickerview/demos/taro/demo1.tsx +++ b/src/packages/pickerview/demos/taro/demo1.tsx @@ -1,13 +1,33 @@ import React from 'react' -import { Cell, PickerView } from '@nutui/nutui-react-taro' -// import { Dongdong } from '@nutui/icons-react-taro' +import { PickerView, Cell } from '@nutui/nutui-react-taro' const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + return ( - - - + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + ) } - export default Demo1 diff --git a/src/packages/pickerview/demos/taro/demo2.tsx b/src/packages/pickerview/demos/taro/demo2.tsx new file mode 100644 index 0000000000..f717632ce1 --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo2.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react-taro' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/demos/taro/demo3.tsx b/src/packages/pickerview/demos/taro/demo3.tsx new file mode 100644 index 0000000000..ae6954258d --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo3.tsx @@ -0,0 +1,39 @@ +import React, { useState } from 'react' +import { PickerView, Cell } from '@nutui/nutui-react-taro' +import isEqual from 'react-fast-compare' + +const Demo3 = () => { + const listData = [ + [ + { label: '周一', value: 'Monday' }, + { label: '周二', value: 'Tuesday' }, + { label: '周三', value: 'Wednesday' }, + { label: '周四', value: 'Thursday' }, + { label: '周五', value: 'Friday' }, + ], + [ + { label: '上午', value: 'Morning' }, + { label: '下午', value: 'Afternoon' }, + { label: '晚上', value: 'Evening' }, + ], + ] + const [value, setValue] = useState(['Tuesday', 'Evening']) + + return ( + <> + + { + console.log('onChange', value, selectOptions) + if (isEqual(value, ['Tuesday', 'Afternoon'])) { + setValue(['Monday', 'Evening']) + } + }} + /> + + + ) +} +export default Demo3 diff --git a/src/packages/pickerview/demos/taro/demo4.tsx b/src/packages/pickerview/demos/taro/demo4.tsx new file mode 100644 index 0000000000..c0234cdb03 --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo4.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react' +import { PickerView, Cell } from '@nutui/nutui-react-taro' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + const [value, setValue] = useState([2]) + + return ( + <> + + { + console.log('onChange', value, selectOptions) + if (value[0] === 3) { + setValue([1]) + } + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index d9b1436be8..0069376561 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -18,19 +18,60 @@ import { name } from '@nutui/nutui-react' ::: -## PickerView +### 受控 + +:::demo + + + +::: + +### 自定义高度 + +:::demo + + + +::: + +### 多列 + +:::demo + + + +::: + +## Picker ### Props | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| name | 图标名 | String | - | +| options | 列表数据 | `Array` | `[]` | +| value | 选中值,受控 | `Array` | `[]` | +| defaultValue | 默认选中 | `Array` | `[]` | +| threeDimensional | 是否开启3D效果 | `boolean` | `true` | +| duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | +| onChange | 每一列值变更时调用 | `(options, value) => void` | `-` | + +### options 数据结构 + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| label | 选项的文字内容 | `string` \| `number` | `-` | +| value | 选项对应的值,且唯一 | `string` \| `number` | `-` | ## 主题定制 ### 样式变量 组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/component/configprovider)。 + | 名称 | 说明 | 默认值 | | --- | --- | --- | -| --nutui-pickerview-height | badge 的高度 | `14px` | +| \--nutui-picker-item-height | 面板每条数据高度 | `36px` | +| \--nutui-picker-item-text-color | 面板每条数据的字色 | `$color-title` | +| \--nutui-picker-item-text-font-size | 面板每条数据的字号 | `$font-size-base` | +| \--nutui-picker-item-active-line-border | 面板当前选中的border值 | `1px solid $color-border` | +| \--nut-picker-mask-background | 面板遮挡区渐变值 | `linear-gradient(180deg, var(--nutui-white-12), var(--nutui-white-7)),linear-gradient(0deg, var(--nutui-white-12), var(--nutui-white-7))` | diff --git a/src/packages/pickerview/index.taro.ts b/src/packages/pickerview/index.taro.ts index b7fd78006c..b42ce6bc9a 100644 --- a/src/packages/pickerview/index.taro.ts +++ b/src/packages/pickerview/index.taro.ts @@ -1,4 +1,4 @@ -import { PickerView } from './pickerview.taro' +import PickerView from './pickerview.taro' export type { PickerViewProps } from './types' export default PickerView diff --git a/src/packages/pickerview/index.ts b/src/packages/pickerview/index.ts index 1865a8b105..47b8834afe 100644 --- a/src/packages/pickerview/index.ts +++ b/src/packages/pickerview/index.ts @@ -1,4 +1,4 @@ -import { PickerView } from './pickerview' +import PickerView from './pickerview' export type { PickerViewProps } from './types' export default PickerView diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx new file mode 100644 index 0000000000..4c5b1493bd --- /dev/null +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -0,0 +1,328 @@ +import React, { + useState, + useEffect, + useRef, + ForwardRefRenderFunction, + useImperativeHandle, +} from 'react' +import { View } from '@tarojs/components' +import { useTouch } from '@/utils/use-touch' +import { passiveSupported } from '@/utils/supports-passive' +import { PickerRollerProps, PickerOptionItem } from './types' + +const InternalPickerRoller: ForwardRefRenderFunction< + { stopMomentum: () => void; moving: boolean }, + Partial +> = (props, ref) => { + const { + keyIndex = 0, + options = [], + threeDimensional = true, + duration = 1000, + onSelect, + renderLabel = (item: PickerOptionItem) => { + return item.label + }, + } = props + + const touch = useTouch() + const DEFAULT_DURATION = 200 + // 触发惯性滑动条件: 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move, 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动 + const INERTIA_TIME = 300 + const INERTIA_DISTANCE = 15 + const [currIndex, setCurrIndex] = useState(1) + const lineSpacing = useRef(36) + const [touchTime, setTouchTime] = useState(0) + const [touchDeg, setTouchDeg] = useState('0deg') + const rotation = 20 + const moving = useRef(false) + + const rollerRef = useRef(null) + const pickerRollerRef = useRef(null) + + const [startTime, setStartTime] = useState(0) + const [startY, setStartY] = useState(0) + + const transformY = useRef(0) + const [scrollDistance, setScrollDistance] = useState(0) + + // 获取 lineSpacing.current CSS变量 + useEffect(() => { + const element = pickerRollerRef.current + if (element) { + const computedStyle = getComputedStyle(element) + const currentLineSpacing = computedStyle.getPropertyValue( + '--nutui-picker-item-height' + ) + console.log('currentLineSpacing', currentLineSpacing) + currentLineSpacing && + (lineSpacing.current = parseFloat(currentLineSpacing)) + } + }, [pickerRollerRef.current]) + + const isHidden = (index: number) => { + if (index >= currIndex + 8 || index <= currIndex - 8) { + return true + } + return false + } + + const setTransform = ( + type: string, + deg: string, + time = DEFAULT_DURATION, + translateY = 0 + ) => { + let nTime = time + if (type !== 'end') { + nTime = 0 + } + setTouchTime(nTime) + setTouchDeg(deg) + setScrollDistance(translateY) + } + + const setMove = (move: number, type?: string, time?: number) => { + let updateMove = move + transformY.current + if (type === 'end') { + // 限定滚动距离 + if (updateMove > 0) { + updateMove = 0 + } + if (updateMove < -(options.length - 1) * lineSpacing.current) { + updateMove = -(options.length - 1) * lineSpacing.current + } + + // 设置滚动距离为lineSpacing.current的倍数值 + const endMove = + Math.round(updateMove / lineSpacing.current) * lineSpacing.current + const deg = `${ + (Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * rotation + }deg` + + setTransform(type, deg, time, endMove) + setCurrIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) + } else { + let deg = 0 + const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation + + // picker 滚动的最大角度 + const maxDeg = (options.length + 1) * rotation + const minDeg = 0 + + deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) + + if (minDeg < deg && deg < maxDeg) { + setTransform('', `${deg}deg`, undefined, updateMove) + setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) + } + } + } + + const setChooseValue = (move: number) => { + onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) + } + + // 开始滚动 + const touchStart = (event: React.TouchEvent) => { + touch.start(event) + setStartY(touch.deltaY.current) + setStartTime(Date.now()) + transformY.current = scrollDistance + } + + const touchMove = (event: React.TouchEvent) => { + touch.move(event) + if ((touch as any).isVertical) { + moving.current = true + preventDefault(event, true) + } + const move = touch.deltaY.current - startY + setMove(move) + } + + const touchEnd = () => { + if (!moving.current) return + const move = touch.deltaY.current - startY + const moveTime = Date.now() - startTime + // 区分是否为惯性滚动 + if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { + // 惯性滚动 + const distance = momentum(move, moveTime) + setMove(distance, 'end', +duration) + } else { + setMove(move, 'end') + } + setTimeout(() => { + touch.reset() + }, 0) + } + + // 惯性滚动 距离 + const momentum = (distance: number, duration: number) => { + let nDistance = distance + // 惯性滚动的速度 + const speed = Math.abs(nDistance / duration) + // 惯性滚动的距离 + nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) + return nDistance + } + + const modifyStatus = (type?: boolean, val?: string | number) => { + const value = val || props.value + let index = -1 + if (value) { + options.some((item, idx) => { + if (item.value === value) { + index = idx + return true + } + return false + }) + } else { + options.forEach((item, i) => { + if (item.value === props.value) { + index = i + } + }) + } + + setCurrIndex(index === -1 ? 1 : index + 1) + const move = index * lineSpacing.current + type && setChooseValue(-move) + console.log(index, move, 'props.value.index') + setMove(-move) + } + + // 惯性滚动结束 + const stopMomentum = () => { + moving.current = false + setTouchTime(0) + setChooseValue(scrollDistance) + } + // 阻止默认事件 + const preventDefault = ( + event: React.TouchEvent, + isStopPropagation?: boolean + ) => { + event.preventDefault() + + if (isStopPropagation) { + event.stopPropagation() + } + } + + useEffect(() => { + setScrollDistance(0) + transformY.current = 0 + modifyStatus(false) + }, [options]) + + useImperativeHandle(ref, () => ({ + stopMomentum, + moving: moving.current, + })) + + useEffect(() => { + const eventOptions = passiveSupported + ? { passive: false, once: true } + : false + pickerRollerRef.current?.addEventListener( + 'touchstart', + touchStart, + eventOptions + ) + pickerRollerRef.current?.addEventListener( + 'touchmove', + touchMove, + eventOptions + ) + pickerRollerRef.current?.addEventListener( + 'touchend', + touchEnd, + eventOptions + ) + return () => { + pickerRollerRef.current?.removeEventListener( + 'touchstart', + touchStart, + eventOptions + ) + pickerRollerRef.current?.removeEventListener( + 'touchmove', + touchMove, + eventOptions + ) + pickerRollerRef.current?.removeEventListener( + 'touchend', + touchEnd, + eventOptions + ) + } + }) + + const touchRollerStyle = () => { + return { + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: `rotate3d(1, 0, 0, ${touchDeg})`, + } + } + const touchTileStyle = () => { + return { + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: `translate3d(0, ${scrollDistance}px, 0)`, + } + } + + const rollerStyle = (index: number) => { + return { + transform: `rotate3d(1, 0, 0, ${ + -rotation * (index + 1) + }deg) translate3d(0px, 0px, ${Math.round(lineSpacing.current * 3.2)}px)`, + } + } + + return ( + + + {/* 3D 效果 */} + {threeDimensional && + options.map((item, index) => ( + + {renderLabel(item)} + + ))} + {/* 平铺 */} + {!threeDimensional && + options.map((item, index) => { + return ( + + {renderLabel(item)} + + ) + })} + + + ) +} + +const PickerRoller = React.forwardRef< + { stopMomentum: () => void; moving: boolean }, + Partial +>(InternalPickerRoller) + +export default PickerRoller diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx new file mode 100644 index 0000000000..604cf97476 --- /dev/null +++ b/src/packages/pickerview/pickerroller.tsx @@ -0,0 +1,302 @@ +import React, { + useState, + useEffect, + useRef, + ForwardRefRenderFunction, + useImperativeHandle, +} from 'react' +import { useTouch } from '@/utils/use-touch' +import { passiveSupported } from '@/utils/supports-passive' +import { PickerRollerProps, PickerOptionItem } from './types' + +const InternalPickerRoller: ForwardRefRenderFunction< + { stopMomentum: () => void; moving: boolean }, + Partial +> = (props, ref) => { + const { + keyIndex = 0, + options = [], + threeDimensional = true, + duration = 1000, + onSelect, + renderLabel = (item: PickerOptionItem) => { + return item.label + }, + } = props + + const touch = useTouch() + const DEFAULT_DURATION = 200 + // 触发惯性滑动条件: 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move, 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动 + const INERTIA_TIME = 300 + const INERTIA_DISTANCE = 15 + const [currIndex, setCurrIndex] = useState(1) + const lineSpacing = useRef(36) + const [touchTime, setTouchTime] = useState(0) + const [touchDeg, setTouchDeg] = useState('0deg') + const rotation = 20 + const moving = useRef(false) + + const rollerRef = useRef(null) + const pickerRollerRef = useRef(null) + + const [startTime, setStartTime] = useState(0) + const [startY, setStartY] = useState(0) + + const transformY = useRef(0) + const [scrollDistance, setScrollDistance] = useState(0) + + // 获取 lineSpacing.current CSS变量 + useEffect(() => { + const element = pickerRollerRef.current + if (element) { + const computedStyle = getComputedStyle(element) + const currentLineSpacing = computedStyle.getPropertyValue( + '--nutui-picker-item-height' + ) + lineSpacing.current = parseFloat(currentLineSpacing) + } + }, []) + + const isHidden = (index: number) => { + if (index >= currIndex + 8 || index <= currIndex - 8) { + return true + } + return false + } + + const setTransform = ( + type: string, + deg: string, + time = DEFAULT_DURATION, + translateY = 0 + ) => { + let nTime = time + if (type !== 'end') { + nTime = 0 + } + setTouchTime(nTime) + setTouchDeg(deg) + setScrollDistance(translateY) + } + + const setMove = (move: number, type?: string, time?: number) => { + let updateMove = move + transformY.current + if (type === 'end') { + // 限定滚动距离 + if (updateMove > 0) { + updateMove = 0 + } + if (updateMove < -(options.length - 1) * lineSpacing.current) { + updateMove = -(options.length - 1) * lineSpacing.current + } + + // 设置滚动距离为lineSpacing.current的倍数值 + const endMove = + Math.round(updateMove / lineSpacing.current) * lineSpacing.current + const deg = `${ + (Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * rotation + }deg` + + setTransform(type, deg, time, endMove) + setCurrIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) + } else { + let deg = 0 + const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation + + // picker 滚动的最大角度 + const maxDeg = (options.length + 1) * rotation + const minDeg = 0 + + deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) + + if (minDeg < deg && deg < maxDeg) { + setTransform('', `${deg}deg`, undefined, updateMove) + setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) + } + } + } + + const setChooseValue = (move: number) => { + onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) + } + + // 开始滚动 + const touchStart = (event: React.TouchEvent) => { + touch.start(event) + setStartY(touch.deltaY.current) + setStartTime(Date.now()) + transformY.current = scrollDistance + } + + const touchMove = (event: React.TouchEvent) => { + touch.move(event) + if ((touch as any).isVertical) { + moving.current = true + preventDefault(event, true) + } + const move = touch.deltaY.current - startY + setMove(move) + } + + const touchEnd = () => { + if (!moving.current) return + const move = touch.deltaY.current - startY + const moveTime = Date.now() - startTime + // 区分是否为惯性滚动 + if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { + // 惯性滚动 + const distance = momentum(move, moveTime) + setMove(distance, 'end', +duration) + } else { + setMove(move, 'end') + } + setTimeout(() => { + touch.reset() + }, 0) + } + + // 惯性滚动 距离 + const momentum = (distance: number, duration: number) => { + let nDistance = distance + // 惯性滚动的速度 + const speed = Math.abs(nDistance / duration) + // 惯性滚动的距离 + nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) + return nDistance + } + + const modifyStatus = (type?: boolean, val?: string | number) => { + const value = val || props.value + let index = -1 + if (value) { + options.some((item, idx) => { + if (item.value === value) { + index = idx + return true + } + return false + }) + } else { + options.forEach((item, i) => { + if (item.value === props.value) { + index = i + } + }) + } + + setCurrIndex(index === -1 ? 1 : index + 1) + const move = index * lineSpacing.current + type && setChooseValue(-move) + console.log(index, move, 'props.value.index') + setMove(-move) + } + + // 惯性滚动结束 + const stopMomentum = () => { + moving.current = false + setTouchTime(0) + setChooseValue(scrollDistance) + } + // 阻止默认事件 + const preventDefault = ( + event: React.TouchEvent, + isStopPropagation?: boolean + ) => { + if (typeof event.cancelable !== 'boolean' || event.cancelable) { + event.preventDefault() + } + + if (isStopPropagation) { + event.stopPropagation() + } + } + + useEffect(() => { + setScrollDistance(0) + transformY.current = 0 + modifyStatus(false) + console.log('modifyStatus', props.value) + }, [options]) + + useImperativeHandle(ref, () => ({ + stopMomentum, + moving: moving.current, + })) + + useEffect(() => { + const options = passiveSupported ? { passive: false } : false + pickerRollerRef.current?.addEventListener('touchstart', touchStart, options) + pickerRollerRef.current?.addEventListener('touchmove', touchMove, options) + pickerRollerRef.current?.addEventListener('touchend', touchEnd, options) + return () => { + pickerRollerRef.current?.removeEventListener('touchstart', touchStart) + pickerRollerRef.current?.removeEventListener('touchmove', touchMove) + pickerRollerRef.current?.removeEventListener('touchend', touchEnd) + } + }) + + const touchRollerStyle = () => { + return { + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: `rotate3d(1, 0, 0, ${touchDeg})`, + } + } + const touchTileStyle = () => { + return { + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: `translate3d(0, ${scrollDistance}px, 0)`, + } + } + + const rollerStyle = (index: number) => { + return { + transform: `rotate3d(1, 0, 0, ${ + -rotation * (index + 1) + }deg) translate3d(0px, 0px, ${Math.round(lineSpacing.current * 3.2)}px)`, + } + } + + return ( +
+
+ {/* 3D 效果 */} + {threeDimensional && + options.map((item, index) => ( +
+ {renderLabel(item)} +
+ ))} + {/* 平铺 */} + {!threeDimensional && + options.map((item, index) => { + return ( +
+ {renderLabel(item)} +
+ ) + })} +
+
+ ) +} + +const PickerRoller = React.forwardRef< + { stopMomentum: () => void; moving: boolean }, + Partial +>(InternalPickerRoller) + +export default PickerRoller diff --git a/src/packages/pickerview/pickerview.scss b/src/packages/pickerview/pickerview.scss index 78d89b48d9..c4b3d1f29d 100644 --- a/src/packages/pickerview/pickerview.scss +++ b/src/packages/pickerview/pickerview.scss @@ -1,2 +1,80 @@ .nut-pickerview { + --nutui-picker-item-height: 36px; + position: relative; + display: flex; + width: 100%; + height: $picker-list-height; + border: 1px solid #000; + $pickerview-top: calc(($picker-list-height - $picker-item-height) / 2); + overflow: hidden; + + &-mask, + &-indicator { + position: absolute; + left: 0; + right: 0; + z-index: 3; + pointer-events: none; + } + + &-mask { + top: 0; + bottom: 0; + background-image: $picker-mask-background; + background-position: top, bottom; + background-size: 100% $pickerview-top; + background-repeat: no-repeat; + transform: translateZ(0); + } + + &-indicator { + top: $pickerview-top; + height: $picker-item-height; + border: $picker-item-active-line-border; + border-left: 0; + border-right: 0; + box-sizing: border-box; + } + + &-list { + position: relative; + flex: 1; + height: $picker-list-height; + overflow: hidden; + touch-action: none; + } + + &-roller { + position: absolute; + top: $pickerview-top; + width: 100%; + height: $picker-item-height; + z-index: 1; + transform-style: preserve-3d; + } + + &-roller-item { + position: absolute; + top: 0; + backface-visibility: hidden; + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + &-hidden { + visibility: hidden; + opacity: 0; + } + } + + &-roller-item, + &-roller-item-title { + width: 100%; + height: $picker-item-height; + line-height: $picker-item-height; + color: $picker-item-text-color; + font-size: $picker-item-text-font-size; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index 5db2fd07ad..d6f1540de1 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -1,28 +1,113 @@ -import React, { FunctionComponent } from 'react' +import React, { + ForwardRefRenderFunction, + useCallback, + useEffect, + useMemo, + useState, +} from 'react' import classNames from 'classnames' import { View } from '@tarojs/components' import { ComponentDefaults } from '@/utils/typings' -import { PickerViewProps } from './types' - -// import { useConfig } from '@/packages/configprovider/configprovider.taro' -// import { useRtl } from '@/packages/configprovider/index.taro' +import { PickerViewProps, PickerOptionItem, PickerValue } from './types' +import PickerRoller from './pickerroller.taro' +import { usePropsValue } from '@/utils/use-props-value' const defaultProps = { ...ComponentDefaults, + options: [], + defaultValue: [], + value: undefined, + renderLabel: (item: PickerOptionItem) => item.label, } as PickerViewProps -export const PickerView: FunctionComponent< - Partial & React.HTMLAttributes -> = (props) => { - // const { locale } = useConfig() - // const rtl = useRtl() - const { className, style } = { ...defaultProps, ...props } + +const InternalPickerView: ForwardRefRenderFunction< + unknown, + Partial +> = (props, ref) => { + const { + options, + defaultValue, + value, + duration, + threeDimensional, + renderLabel, + className, + style, + onChange, + } = { ...defaultProps, ...props } const classPrefix = 'nut-pickerview' const cls = classNames(classPrefix, className) + + const [selectedValue] = usePropsValue({ + value, + defaultValue, + finalValue: defaultValue, + }) + + const [innerValue, setInnerValue] = useState(selectedValue) + const [innerOptions, setInnerOptions] = useState(options) + + useEffect(() => { + if (selectedValue !== innerValue) { + setInnerValue(selectedValue) + } + }, [selectedValue]) + + useEffect(() => { + if (options !== innerOptions) { + setInnerOptions(options) + } + }, [options]) + + const handleSelect = useCallback( + (option: PickerOptionItem, index: number) => { + const newValue = option?.value + if (!newValue) return + setInnerValue((prev) => { + if (prev[index] === newValue) return prev + const next = [...prev] + next[index] = newValue + return next + }) + }, + [] + ) + + const selectedOptions = useMemo(() => { + return options.map((columnOptions, index) => { + const selectedOption = columnOptions.find( + (item) => item.value === innerValue[index] + ) + return selectedOption || columnOptions[0] // Fallback to the first option if not found + }) + }, [options, innerValue]) + + useEffect(() => { + onChange?.(innerValue, selectedOptions) + }, [innerValue, selectedOptions, onChange]) + return ( - PickerView + {innerOptions.map((item, index) => ( + + ))} + + ) } -PickerView.displayName = 'NutPickerView' +const PickerView = React.forwardRef>( + InternalPickerView +) + +export default PickerView diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index 0f33d583db..b94b9db13f 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -1,26 +1,112 @@ -import React, { FunctionComponent } from 'react' +import React, { + ForwardRefRenderFunction, + useCallback, + useEffect, + useMemo, + useState, +} from 'react' import classNames from 'classnames' import { ComponentDefaults } from '@/utils/typings' -import { PickerViewProps } from './types' -// import { useConfig } from '@/packages/configprovider' -// import { useRtl } from '@/packages/configprovider' +import { PickerViewProps, PickerOptionItem, PickerValue } from './types' +import PickerRoller from './pickerroller' +import { usePropsValue } from '@/utils/use-props-value' const defaultProps = { ...ComponentDefaults, + options: [], + defaultValue: [], + value: undefined, + renderLabel: (item: PickerOptionItem) => item.label, } as PickerViewProps -export const PickerView: FunctionComponent< - Partial & React.HTMLAttributes -> = (props) => { - // const { locale } = useConfig() - // const rtl = useRtl() - const { className, style } = { ...defaultProps, ...props } + +const InternalPickerView: ForwardRefRenderFunction< + unknown, + Partial +> = (props, ref) => { + const { + options, + defaultValue, + value, + duration, + threeDimensional, + renderLabel, + className, + style, + onChange, + } = { ...defaultProps, ...props } const classPrefix = 'nut-pickerview' const cls = classNames(classPrefix, className) + + const [selectedValue] = usePropsValue({ + value, + defaultValue, + finalValue: defaultValue, + }) + + const [innerValue, setInnerValue] = useState(selectedValue) + const [innerOptions, setInnerOptions] = useState(options) + + useEffect(() => { + if (selectedValue !== innerValue) { + setInnerValue(selectedValue) + } + }, [selectedValue]) + + useEffect(() => { + if (options !== innerOptions) { + setInnerOptions(options) + } + }, [options]) + + const handleSelect = useCallback( + (option: PickerOptionItem, index: number) => { + const newValue = option?.value + if (!newValue) return + setInnerValue((prev) => { + if (prev[index] === newValue) return prev + const next = [...prev] + next[index] = newValue + return next + }) + }, + [] + ) + + const selectedOptions = useMemo(() => { + return options.map((columnOptions, index) => { + const selectedOption = columnOptions.find( + (item) => item.value === innerValue[index] + ) + return selectedOption || columnOptions[0] // Fallback to the first option if not found + }) + }, [options, innerValue]) + + useEffect(() => { + onChange?.(innerValue, selectedOptions) + }, [innerValue, selectedOptions, onChange]) + return (
- PickerView + {innerOptions.map((item, index) => ( + + ))} +
+
) } -PickerView.displayName = 'NutPickerView' +const PickerView = React.forwardRef>( + InternalPickerView +) + +export default PickerView diff --git a/src/packages/pickerview/types.ts b/src/packages/pickerview/types.ts index a72b2c3b4e..a65deb939d 100644 --- a/src/packages/pickerview/types.ts +++ b/src/packages/pickerview/types.ts @@ -1,3 +1,33 @@ import { BasicComponent } from '@/utils/typings' -export interface PickerViewProps extends BasicComponent {} +export type PickerValue = string | number | null + +export interface PickerOptionItem { + label: string | number + value: string | number + disabled?: boolean + children?: PickerOptionItem[] + className?: string | number +} + +export type PickerOptions = PickerOptionItem[] + +export interface PickerRollerProps { + options: PickerOptionItem[] + keyIndex: number + value: PickerValue + threeDimensional?: boolean + duration?: number | string + onSelect: (option: PickerOptionItem, index: number) => void + renderLabel: (item: PickerOptionItem) => React.ReactNode +} + +export interface PickerViewProps extends BasicComponent { + options: PickerOptions[] + value?: PickerValue[] + defaultValue?: PickerValue[] + threeDimensional?: boolean + duration?: number | string + renderLabel: (item: PickerOptionItem) => React.ReactNode + onChange?: (value: PickerValue[], selectOptions: PickerOptionItem[]) => void +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 99b31976c7..51a884d520 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -559,8 +559,10 @@ $picker-title-ok-font-size: var( --nutui-picker-title-ok-font-size, $font-size-base ) !default; -$picker-list-height: var(--nutui-picker-list-height, 252px) !default; + +// picker-view(✅) $picker-item-height: var(--nutui-picker-item-height, 36px) !default; +$picker-list-height: calc($picker-item-height * 7) !default; $picker-item-text-color: var( --nutui-picker-item-text-color, $color-title From 2e3a00aea69e3a82775a7728a0218382cd0533f6 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 22 Jan 2025 18:35:06 +0800 Subject: [PATCH 03/24] feat: add tiled demo --- src/packages/pickerview/demo.taro.tsx | 6 ++++ src/packages/pickerview/demo.tsx | 6 ++++ src/packages/pickerview/demos/h5/demo5.tsx | 35 ++++++++++++++++++++ src/packages/pickerview/demos/taro/demo5.tsx | 35 ++++++++++++++++++++ src/packages/pickerview/doc.md | 10 +++++- src/packages/pickerview/pickerview.scss | 1 - src/packages/pickerview/pickerview.taro.tsx | 2 +- src/packages/pickerview/pickerview.tsx | 2 +- 8 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/packages/pickerview/demos/h5/demo5.tsx create mode 100644 src/packages/pickerview/demos/taro/demo5.tsx diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx index 9773e2cae5..d05584575b 100644 --- a/src/packages/pickerview/demo.taro.tsx +++ b/src/packages/pickerview/demo.taro.tsx @@ -7,6 +7,7 @@ import Demo1 from './demos/taro/demo1' import Demo2 from './demos/taro/demo2' import Demo3 from './demos/taro/demo3' import Demo4 from './demos/taro/demo4' +import Demo5 from './demos/taro/demo5' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -15,18 +16,21 @@ const PickerViewDemo = () => { adjustHeight: '自适应高度', multiColumn: '多列', controlled: '受控', + tiled: '平铺', }, 'en-US': { title: 'Basic Usage', adjustHeight: 'Adjust Height', multiColumn: 'Multi Column', controlled: 'Controlled', + tiled: 'Tiled', }, 'zh-TW': { title: '基礎用法', adjustHeight: '自適應高度', multiColumn: '多列', controlled: '受控', + tiled: '平鋪', }, }) return ( @@ -41,6 +45,8 @@ const PickerViewDemo = () => { {translated.multiColumn} + {translated.tiled} + ) diff --git a/src/packages/pickerview/demo.tsx b/src/packages/pickerview/demo.tsx index b45f73be58..99de463ecd 100644 --- a/src/packages/pickerview/demo.tsx +++ b/src/packages/pickerview/demo.tsx @@ -4,6 +4,7 @@ import Demo1 from './demos/h5/demo1' import Demo2 from './demos/h5/demo2' import Demo3 from './demos/h5/demo3' import Demo4 from './demos/h5/demo4' +import Demo5 from './demos/h5/demo5' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -12,18 +13,21 @@ const PickerViewDemo = () => { adjustHeight: '自适应高度', multiColumn: '多列', controlled: '受控', + tiled: '平铺', }, 'en-US': { title: 'Basic Usage', adjustHeight: 'Adjust Height', multiColumn: 'Multi Column', controlled: 'Controlled', + tiled: 'Tiled', }, 'zh-TW': { title: '基礎用法', adjustHeight: '自適應高度', multiColumn: '多列', controlled: '受控', + tiled: '平鋪', }, }) return ( @@ -36,6 +40,8 @@ const PickerViewDemo = () => {

{translated.multiColumn}

+

{translated.tiled}

+
) } diff --git a/src/packages/pickerview/demos/h5/demo5.tsx b/src/packages/pickerview/demos/h5/demo5.tsx new file mode 100644 index 0000000000..27741464db --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo5.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/demos/taro/demo5.tsx b/src/packages/pickerview/demos/taro/demo5.tsx new file mode 100644 index 0000000000..98cfbdbeca --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo5.tsx @@ -0,0 +1,35 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react-taro' + +const Demo1 = () => { + const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectOptions) + }} + /> + + + ) +} +export default Demo1 diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index 0069376561..bb230e0346 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -42,7 +42,15 @@ import { name } from '@nutui/nutui-react' ::: -## Picker +### 平铺 + +:::demo + + + +::: + +## PickerView ### Props diff --git a/src/packages/pickerview/pickerview.scss b/src/packages/pickerview/pickerview.scss index c4b3d1f29d..595e61d224 100644 --- a/src/packages/pickerview/pickerview.scss +++ b/src/packages/pickerview/pickerview.scss @@ -4,7 +4,6 @@ display: flex; width: 100%; height: $picker-list-height; - border: 1px solid #000; $pickerview-top: calc(($picker-list-height - $picker-item-height) / 2); overflow: hidden; diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index d6f1540de1..251a8c4d1d 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -78,7 +78,7 @@ const InternalPickerView: ForwardRefRenderFunction< const selectedOption = columnOptions.find( (item) => item.value === innerValue[index] ) - return selectedOption || columnOptions[0] // Fallback to the first option if not found + return selectedOption || columnOptions[0] }) }, [options, innerValue]) diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index b94b9db13f..e00e5f4d52 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -77,7 +77,7 @@ const InternalPickerView: ForwardRefRenderFunction< const selectedOption = columnOptions.find( (item) => item.value === innerValue[index] ) - return selectedOption || columnOptions[0] // Fallback to the first option if not found + return selectedOption || columnOptions[0] }) }, [options, innerValue]) From 7a279340c5187046714ed5a8182616329bef214b Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 11:16:23 +0800 Subject: [PATCH 04/24] =?UTF-8?q?fix:=20=E6=9B=B4=E6=96=B0pickerview?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/pickerview/demo.taro.tsx | 6 + src/packages/pickerview/demo.tsx | 6 + src/packages/pickerview/demos/h5/demo1.tsx | 4 +- src/packages/pickerview/demos/h5/demo2.tsx | 4 +- src/packages/pickerview/demos/h5/demo3.tsx | 4 +- src/packages/pickerview/demos/h5/demo4.tsx | 4 +- src/packages/pickerview/demos/h5/demo5.tsx | 4 +- src/packages/pickerview/demos/h5/demo6.tsx | 80 +++++++++ src/packages/pickerview/demos/taro/demo1.tsx | 4 +- src/packages/pickerview/demos/taro/demo2.tsx | 4 +- src/packages/pickerview/demos/taro/demo3.tsx | 4 +- src/packages/pickerview/demos/taro/demo4.tsx | 4 +- src/packages/pickerview/demos/taro/demo5.tsx | 4 +- src/packages/pickerview/demos/taro/demo6.tsx | 80 +++++++++ src/packages/pickerview/doc.en-US.md | 94 +++++++++++ src/packages/pickerview/doc.md | 10 +- src/packages/pickerview/doc.taro.md | 93 +++++++++++ src/packages/pickerview/doc.zh-TW.md | 93 +++++++++++ src/packages/pickerview/index.taro.ts | 9 +- src/packages/pickerview/index.ts | 9 +- src/packages/pickerview/pickerroller.taro.tsx | 10 +- src/packages/pickerview/pickerroller.tsx | 7 +- src/packages/pickerview/pickerview.taro.tsx | 157 +++++++++++++++--- src/packages/pickerview/pickerview.tsx | 157 +++++++++++++++--- src/packages/pickerview/types.ts | 11 +- 25 files changed, 771 insertions(+), 91 deletions(-) create mode 100644 src/packages/pickerview/demos/h5/demo6.tsx create mode 100644 src/packages/pickerview/demos/taro/demo6.tsx create mode 100644 src/packages/pickerview/doc.en-US.md create mode 100644 src/packages/pickerview/doc.taro.md create mode 100644 src/packages/pickerview/doc.zh-TW.md diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx index d05584575b..854ebb089d 100644 --- a/src/packages/pickerview/demo.taro.tsx +++ b/src/packages/pickerview/demo.taro.tsx @@ -8,6 +8,7 @@ import Demo2 from './demos/taro/demo2' import Demo3 from './demos/taro/demo3' import Demo4 from './demos/taro/demo4' import Demo5 from './demos/taro/demo5' +import Demo6 from './demos/taro/demo6' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -17,6 +18,7 @@ const PickerViewDemo = () => { multiColumn: '多列', controlled: '受控', tiled: '平铺', + cascade: '级联', }, 'en-US': { title: 'Basic Usage', @@ -24,6 +26,7 @@ const PickerViewDemo = () => { multiColumn: 'Multi Column', controlled: 'Controlled', tiled: 'Tiled', + cascade: 'Cascade', }, 'zh-TW': { title: '基礎用法', @@ -31,6 +34,7 @@ const PickerViewDemo = () => { multiColumn: '多列', controlled: '受控', tiled: '平鋪', + cascade: '級聯', }, }) return ( @@ -47,6 +51,8 @@ const PickerViewDemo = () => { {translated.tiled} +

{translated.cascade}

+ ) diff --git a/src/packages/pickerview/demo.tsx b/src/packages/pickerview/demo.tsx index 99de463ecd..b94f91de80 100644 --- a/src/packages/pickerview/demo.tsx +++ b/src/packages/pickerview/demo.tsx @@ -5,6 +5,7 @@ import Demo2 from './demos/h5/demo2' import Demo3 from './demos/h5/demo3' import Demo4 from './demos/h5/demo4' import Demo5 from './demos/h5/demo5' +import Demo6 from './demos/h5/demo6' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -14,6 +15,7 @@ const PickerViewDemo = () => { multiColumn: '多列', controlled: '受控', tiled: '平铺', + cascade: '级联', }, 'en-US': { title: 'Basic Usage', @@ -21,6 +23,7 @@ const PickerViewDemo = () => { multiColumn: 'Multi Column', controlled: 'Controlled', tiled: 'Tiled', + cascade: 'Cascade', }, 'zh-TW': { title: '基礎用法', @@ -28,6 +31,7 @@ const PickerViewDemo = () => { multiColumn: '多列', controlled: '受控', tiled: '平鋪', + cascade: '級聯', }, }) return ( @@ -42,6 +46,8 @@ const PickerViewDemo = () => {

{translated.tiled}

+

{translated.cascade}

+
) } diff --git a/src/packages/pickerview/demos/h5/demo1.tsx b/src/packages/pickerview/demos/h5/demo1.tsx index cacf341036..dbadb8c5a7 100644 --- a/src/packages/pickerview/demos/h5/demo1.tsx +++ b/src/packages/pickerview/demos/h5/demo1.tsx @@ -22,8 +22,8 @@ const Demo1 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/h5/demo2.tsx b/src/packages/pickerview/demos/h5/demo2.tsx index dd16c8a92e..dff4a404b0 100644 --- a/src/packages/pickerview/demos/h5/demo2.tsx +++ b/src/packages/pickerview/demos/h5/demo2.tsx @@ -23,8 +23,8 @@ const Demo1 = () => { style={{ '--nutui-picker-item-height': '28px' }} options={listData} defaultValue={[1]} - onChange={(value, selectOptions) => { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/h5/demo3.tsx b/src/packages/pickerview/demos/h5/demo3.tsx index 555c655e39..d23f719786 100644 --- a/src/packages/pickerview/demos/h5/demo3.tsx +++ b/src/packages/pickerview/demos/h5/demo3.tsx @@ -25,8 +25,8 @@ const Demo3 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) if (isEqual(value, ['Tuesday', 'Afternoon'])) { setValue(['Monday', 'Evening']) } diff --git a/src/packages/pickerview/demos/h5/demo4.tsx b/src/packages/pickerview/demos/h5/demo4.tsx index e205b5513a..320c7cc39e 100644 --- a/src/packages/pickerview/demos/h5/demo4.tsx +++ b/src/packages/pickerview/demos/h5/demo4.tsx @@ -24,8 +24,8 @@ const Demo1 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) if (value[0] === 3) { setValue([1]) } diff --git a/src/packages/pickerview/demos/h5/demo5.tsx b/src/packages/pickerview/demos/h5/demo5.tsx index 27741464db..4bf68a2261 100644 --- a/src/packages/pickerview/demos/h5/demo5.tsx +++ b/src/packages/pickerview/demos/h5/demo5.tsx @@ -24,8 +24,8 @@ const Demo1 = () => { options={listData} threeDimensional={false} duration={500} - onChange={(value, selectOptions) => { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/h5/demo6.tsx b/src/packages/pickerview/demos/h5/demo6.tsx new file mode 100644 index 0000000000..44aa46d8ae --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo6.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react' + +const Demo6 = () => { + const listData = [ + [ + { + value: 1, + label: '北京', + children: [ + { + value: 1, + label: '朝阳区', + }, + { + value: 2, + label: '海淀区', + }, + { + value: 3, + label: '大兴区', + }, + { + value: 4, + label: '东城区', + }, + { + value: 5, + label: '西城区', + }, + { + value: 6, + label: '丰台区', + }, + ], + }, + { + value: 2, + label: '上海', + children: [ + { + value: 1, + label: '黄埔区', + }, + { + value: 2, + label: '长宁区', + }, + { + value: 3, + label: '普陀区', + }, + { + value: 4, + label: '杨浦区', + }, + { + value: 5, + label: '浦东新区', + }, + ], + }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectedOptions) + }} + /> + + + ) +} +export default Demo6 diff --git a/src/packages/pickerview/demos/taro/demo1.tsx b/src/packages/pickerview/demos/taro/demo1.tsx index 4d3d363bc2..44c4bc6f3b 100644 --- a/src/packages/pickerview/demos/taro/demo1.tsx +++ b/src/packages/pickerview/demos/taro/demo1.tsx @@ -22,8 +22,8 @@ const Demo1 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/taro/demo2.tsx b/src/packages/pickerview/demos/taro/demo2.tsx index f717632ce1..72a79c0a84 100644 --- a/src/packages/pickerview/demos/taro/demo2.tsx +++ b/src/packages/pickerview/demos/taro/demo2.tsx @@ -23,8 +23,8 @@ const Demo1 = () => { style={{ '--nutui-picker-item-height': '28px' }} options={listData} defaultValue={[1]} - onChange={(value, selectOptions) => { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/taro/demo3.tsx b/src/packages/pickerview/demos/taro/demo3.tsx index ae6954258d..9c7b9cd1f9 100644 --- a/src/packages/pickerview/demos/taro/demo3.tsx +++ b/src/packages/pickerview/demos/taro/demo3.tsx @@ -25,8 +25,8 @@ const Demo3 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) if (isEqual(value, ['Tuesday', 'Afternoon'])) { setValue(['Monday', 'Evening']) } diff --git a/src/packages/pickerview/demos/taro/demo4.tsx b/src/packages/pickerview/demos/taro/demo4.tsx index c0234cdb03..2716cd35fd 100644 --- a/src/packages/pickerview/demos/taro/demo4.tsx +++ b/src/packages/pickerview/demos/taro/demo4.tsx @@ -24,8 +24,8 @@ const Demo1 = () => { { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) if (value[0] === 3) { setValue([1]) } diff --git a/src/packages/pickerview/demos/taro/demo5.tsx b/src/packages/pickerview/demos/taro/demo5.tsx index 98cfbdbeca..a8d43a86b0 100644 --- a/src/packages/pickerview/demos/taro/demo5.tsx +++ b/src/packages/pickerview/demos/taro/demo5.tsx @@ -24,8 +24,8 @@ const Demo1 = () => { options={listData} threeDimensional={false} duration={500} - onChange={(value, selectOptions) => { - console.log('onChange', value, selectOptions) + onChange={({ value, selectedOptions }) => { + console.log('onChange', value, selectedOptions) }} /> diff --git a/src/packages/pickerview/demos/taro/demo6.tsx b/src/packages/pickerview/demos/taro/demo6.tsx new file mode 100644 index 0000000000..9cf8ae0ac2 --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo6.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { PickerView, Cell } from '@nutui/nutui-react-taro' + +const Demo6 = () => { + const listData = [ + [ + { + value: 1, + label: '北京', + children: [ + { + value: 1, + label: '朝阳区', + }, + { + value: 2, + label: '海淀区', + }, + { + value: 3, + label: '大兴区', + }, + { + value: 4, + label: '东城区', + }, + { + value: 5, + label: '西城区', + }, + { + value: 6, + label: '丰台区', + }, + ], + }, + { + value: 2, + label: '上海', + children: [ + { + value: 1, + label: '黄埔区', + }, + { + value: 2, + label: '长宁区', + }, + { + value: 3, + label: '普陀区', + }, + { + value: 4, + label: '杨浦区', + }, + { + value: 5, + label: '浦东新区', + }, + ], + }, + ], + ] + + return ( + <> + + { + console.log('onChange', value, selectedOptions) + }} + /> + + + ) +} +export default Demo6 diff --git a/src/packages/pickerview/doc.en-US.md b/src/packages/pickerview/doc.en-US.md new file mode 100644 index 0000000000..6ec4aa0aa8 --- /dev/null +++ b/src/packages/pickerview/doc.en-US.md @@ -0,0 +1,94 @@ +# PickerView + +The PickerView is the content area of the Picker. + +## Import + +```tsx +import { Picker } from '@nutui/nutui-react' +``` + +## Demo + +### Basic Usage + +:::demo + + + +::: + +### Controlled + +:::demo + + + +::: + +### Adjust Height + +:::demo + + + +::: + +### Multi Column + +:::demo + + + +::: + +### Tiled + +:::demo + + + +::: + +### Cascade + +:::demo + + + +::: + +## PickerView + +### Props + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| options | Tabular data | `Array` | `[]` | +| value | Selected value, controlled | `Array` | `[]` | +| defaultValue | Default value | `Array` | `[]` | +| threeDimensional | Whether to enable 3D effect | `boolean` | `true` | +| duration | The duration of inertial rolling during rapid sliding, in ms | `string` \| `number` | `1000` | +| onChange | Called when the value of each column changes | `({value, index, selectedOptions}) => void` | `-` | + +### options + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| label | Text of column | `string` \| `number` | `-` | +| value | Value of column | `string` \| `number` | `-` | +| children | Cascader Option | `Array` | `-` | + +## Theming + +### CSS Variables + +The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](#/en-US/component/configprovider). + +| Name | Description | Default | +| --- | --- | --- | +| \--nutui-picker-item-height | Height of each data item on the panel | `36px` | +| \--nutui-picker-item-text-color | The color of each piece of data in the panel | `$color-title` | +| \--nutui-picker-item-text-font-size | The font size of each piece of data in the panel | `$font-size-base` | +| \--nutui-picker-item-active-line-border | The border value currently selected by the panel | `1px solid $color-border` | +| \--nut-picker-mask-background | Panel shade gradient value | `linear-gradient(180deg, var(--nutui-white-12), var(--nutui-white-7)),linear-gradient(0deg, var(--nutui-white-12), var(--nutui-white-7))` | diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index bb230e0346..ad1777daa9 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -50,6 +50,14 @@ import { name } from '@nutui/nutui-react' ::: +### 级联 + +:::demo + + + +::: + ## PickerView ### Props @@ -61,7 +69,7 @@ import { name } from '@nutui/nutui-react' | defaultValue | 默认选中 | `Array` | `[]` | | threeDimensional | 是否开启3D效果 | `boolean` | `true` | | duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | -| onChange | 每一列值变更时调用 | `(options, value) => void` | `-` | +| onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | ### options 数据结构 diff --git a/src/packages/pickerview/doc.taro.md b/src/packages/pickerview/doc.taro.md new file mode 100644 index 0000000000..f8e186b1be --- /dev/null +++ b/src/packages/pickerview/doc.taro.md @@ -0,0 +1,93 @@ +# PickerView 选择器视图 + +PickerView 是 Picker 的内容区域。 + +## 引入 + +```tsx +import { name } from '@nutui/nutui-react-taro' +``` + +## 示例代码 + +### 基础用法 + +:::demo + + + +::: + +### 受控 + +:::demo + + + +::: + +### 自定义高度 + +:::demo + + + +::: + +### 多列 + +:::demo + + + +::: + +### 平铺 + +:::demo + + + +::: + +### 级联 + +:::demo + + + +::: + +## PickerView + +### Props + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| options | 列表数据 | `Array` | `[]` | +| value | 选中值,受控 | `Array` | `[]` | +| defaultValue | 默认选中 | `Array` | `[]` | +| threeDimensional | 是否开启3D效果 | `boolean` | `true` | +| duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | +| onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | + +### options 数据结构 + +| 属性 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| label | 选项的文字内容 | `string` \| `number` | `-` | +| value | 选项对应的值,且唯一 | `string` \| `number` | `-` | + +## 主题定制 + +### 样式变量 + +组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/component/configprovider)。 + +| 名称 | 说明 | 默认值 | +| --- | --- | --- | +| \--nutui-picker-item-height | 面板每条数据高度 | `36px` | +| \--nutui-picker-item-text-color | 面板每条数据的字色 | `$color-title` | +| \--nutui-picker-item-text-font-size | 面板每条数据的字号 | `$font-size-base` | +| \--nutui-picker-item-active-line-border | 面板当前选中的border值 | `1px solid $color-border` | +| \--nut-picker-mask-background | 面板遮挡区渐变值 | `linear-gradient(180deg, var(--nutui-white-12), var(--nutui-white-7)),linear-gradient(0deg, var(--nutui-white-12), var(--nutui-white-7))` | diff --git a/src/packages/pickerview/doc.zh-TW.md b/src/packages/pickerview/doc.zh-TW.md new file mode 100644 index 0000000000..c944d61291 --- /dev/null +++ b/src/packages/pickerview/doc.zh-TW.md @@ -0,0 +1,93 @@ +# PickerView 選擇器視圖 + +PickerView 是 Picker 的內容區域。 + +## 引入 + +```tsx +import { name } from '@nutui/nutui-react-taro' +``` + +## 示例代碼 + +### 基礎用法 + +:::demo + + + +::: + +### 受控 + +:::demo + + + +::: + +### 自定義高度 + +:::demo + + + +::: + +### 多列 + +:::demo + + + +::: + +### 平鋪 + +:::demo + + + +::: + +### 級聯 + +:::demo + + + +::: + +## PickerView + +### Props + +| 屬性 | 說明 | 類型 | 默認值 | +| --- | --- | --- | --- | +| options | 列錶數據 | `Array` | `[]` | +| value | 選中值,受控 | `Array` | `[]` | +| defaultValue | 默認選中 | `Array` | `[]` | +| threeDimensional | 是否開啟3D效果 | `boolean` | `true` | +| duration | 快速滑動時慣性滾動的時長,單位 ms | `string` \| `number` | `1000` | +| onChange | 每一列值變更時調用 | `({value, index, selectedOptions}) => void` | `-` | + +### options 數據結構 + +| 屬性 | 說明 | 類型 | 默認值 | +| --- | --- | --- | --- | +| label | 選項的文字內容 | `string` \| `number` | `-` | +| value | 選項對應的值,且唯一 | `string` \| `number` | `-` | + +## 主題定制 + +### 樣式變量 + +組件提供了下列 CSS 變量,可用於自定義樣式,使用方法請參考 [ConfigProvider 組件](#/zh-CN/component/configprovider)。 + +| 名稱 | 說明 | 默認值 | +| --- | --- | --- | +| \--nutui-picker-item-height | 面闆每條數據高度 | `36px` | +| \--nutui-picker-item-text-color | 面闆每條數據的字色 | `$color-title` | +| \--nutui-picker-item-text-font-size | 面闆每條數據的字號 | `$font-size-base` | +| \--nutui-picker-item-active-line-border | 面闆當前選中的border值 | `1px solid $color-border` | +| \--nut-picker-mask-background | 面闆遮擋區漸變值 | `linear-gradient(180deg, var(--nutui-white-12), var(--nutui-white-7)),linear-gradient(0deg, var(--nutui-white-12), var(--nutui-white-7))` | diff --git a/src/packages/pickerview/index.taro.ts b/src/packages/pickerview/index.taro.ts index b42ce6bc9a..1bd5f2a397 100644 --- a/src/packages/pickerview/index.taro.ts +++ b/src/packages/pickerview/index.taro.ts @@ -1,4 +1,11 @@ import PickerView from './pickerview.taro' -export type { PickerViewProps } from './types' +export type { + PickerViewProps, + PickerOptionItem, + PickerRollerProps, + PickerValue, + PickerOptions, + PickerOnChangeCallbackParameter, +} from './types' export default PickerView diff --git a/src/packages/pickerview/index.ts b/src/packages/pickerview/index.ts index 47b8834afe..15c781a272 100644 --- a/src/packages/pickerview/index.ts +++ b/src/packages/pickerview/index.ts @@ -1,4 +1,11 @@ import PickerView from './pickerview' -export type { PickerViewProps } from './types' +export type { + PickerViewProps, + PickerOptionItem, + PickerRollerProps, + PickerValue, + PickerOptions, + PickerOnChangeCallbackParameter, +} from './types' export default PickerView diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index 4c5b1493bd..dc0b2b56a9 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -9,6 +9,7 @@ import { View } from '@tarojs/components' import { useTouch } from '@/utils/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' +import { web } from '@/utils/platform-taro' const InternalPickerRoller: ForwardRefRenderFunction< { stopMomentum: () => void; moving: boolean }, @@ -49,12 +50,11 @@ const InternalPickerRoller: ForwardRefRenderFunction< // 获取 lineSpacing.current CSS变量 useEffect(() => { const element = pickerRollerRef.current - if (element) { + if (element && web()) { const computedStyle = getComputedStyle(element) const currentLineSpacing = computedStyle.getPropertyValue( '--nutui-picker-item-height' ) - console.log('currentLineSpacing', currentLineSpacing) currentLineSpacing && (lineSpacing.current = parseFloat(currentLineSpacing)) } @@ -111,8 +111,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const minDeg = 0 deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) - - if (minDeg < deg && deg < maxDeg) { + if (minDeg <= deg && deg < maxDeg) { setTransform('', `${deg}deg`, undefined, updateMove) setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) } @@ -190,7 +189,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< setCurrIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current type && setChooseValue(-move) - console.log(index, move, 'props.value.index') setMove(-move) } @@ -216,7 +214,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< setScrollDistance(0) transformY.current = 0 modifyStatus(false) - }, [options]) + }, [options, props.value]) useImperativeHandle(ref, () => ({ stopMomentum, diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 604cf97476..47b66ef808 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -108,8 +108,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const minDeg = 0 deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) - - if (minDeg < deg && deg < maxDeg) { + if (minDeg <= deg && deg < maxDeg) { setTransform('', `${deg}deg`, undefined, updateMove) setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) } @@ -187,7 +186,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< setCurrIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current type && setChooseValue(-move) - console.log(index, move, 'props.value.index') setMove(-move) } @@ -215,8 +213,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< setScrollDistance(0) transformY.current = 0 modifyStatus(false) - console.log('modifyStatus', props.value) - }, [options]) + }, [options, props.value]) useImperativeHandle(ref, () => ({ stopMomentum, diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index 251a8c4d1d..06f20401f6 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -3,12 +3,19 @@ import React, { useCallback, useEffect, useMemo, + useRef, useState, } from 'react' import classNames from 'classnames' import { View } from '@tarojs/components' +import isEqual from 'react-fast-compare' import { ComponentDefaults } from '@/utils/typings' -import { PickerViewProps, PickerOptionItem, PickerValue } from './types' +import { + PickerViewProps, + PickerOptionItem, + PickerValue, + PickerOptions, +} from './types' import PickerRoller from './pickerroller.taro' import { usePropsValue } from '@/utils/use-props-value' @@ -26,7 +33,7 @@ const InternalPickerView: ForwardRefRenderFunction< > = (props, ref) => { const { options, - defaultValue, + defaultValue = [], value, duration, threeDimensional, @@ -40,56 +47,152 @@ const InternalPickerView: ForwardRefRenderFunction< const [selectedValue] = usePropsValue({ value, - defaultValue, - finalValue: defaultValue, + defaultValue: [...defaultValue], + finalValue: [...defaultValue], }) const [innerValue, setInnerValue] = useState(selectedValue) - const [innerOptions, setInnerOptions] = useState(options) + const [innerOptions, setInnerOptions] = useState([] as PickerOptions[]) + const changeIndex = useRef(0) + + /** + * 数据类型:级联、多列 + */ + const columnsType = useMemo(() => { + const [firstColumn] = props.options as PickerOptions[] + if (Array.isArray(firstColumn) && 'children' in firstColumn[0]) { + return 'cascade' + } + return 'multiple' + }, [props.options]) + + const formatCascadeOptions = ( + options: PickerOptions, + value: PickerValue[] + ) => { + if (!options.length) return [] // 如果 options 为空,直接返回空数组 + + const formatted: PickerOptions[] = [] + let columnOptions: PickerOptionItem = { + label: '', + value: '', + children: options, + } + + let columnIndex = 0 + while (columnOptions && columnOptions.children) { + const currentOptions: PickerOptions = columnOptions.children + formatted.push(currentOptions) + + const currentValue = value?.[columnIndex] + if (currentValue === 0) { + // 如果 currentValue 为 0,返回第一个 children + columnOptions = currentOptions[0] + } else if (currentValue) { + // 如果 currentValue 存在,查找匹配的项 + const index = currentOptions.findIndex( + (columnItem) => columnItem.value === currentValue + ) + columnOptions = currentOptions[index === -1 ? 0 : index] // 如果未找到,默认取第一个 + } else { + break // 如果 currentValue 不存在,终止循环 + } + + columnIndex++ + } + return formatted + } + + const formatOptions = useMemo(() => { + if (columnsType === 'cascade') { + return formatCascadeOptions( + props?.options?.[0] as PickerOptions, + innerValue + ) + } + return props.options + }, [innerValue, options, columnsType]) useEffect(() => { - if (selectedValue !== innerValue) { - setInnerValue(selectedValue) + if (props.options !== innerOptions) { + setInnerOptions(formatOptions as PickerOptions[]) } - }, [selectedValue]) + }, [props.options, innerValue]) useEffect(() => { - if (options !== innerOptions) { - setInnerOptions(options) + if (selectedValue !== innerValue) { + setInnerValue(selectedValue) } - }, [options]) + }, [selectedValue]) const handleSelect = useCallback( (option: PickerOptionItem, index: number) => { const newValue = option?.value - if (!newValue) return - setInnerValue((prev) => { - if (prev[index] === newValue) return prev - const next = [...prev] - next[index] = newValue - return next - }) + if (!newValue || innerValue[index] === newValue) return + changeIndex.current = index + if (columnsType === 'multiple') { + setInnerValue((prev) => { + const next = [...prev] + next[index] = newValue + return next + }) + } else { + const startIndex = index + const values: PickerValue[] = [] + values[index] = option.value + while (option?.children?.[0]) { + values[index + 1] = option.children[0].value + index++ + option = option.children[0] + } + // 当前改变列的下一列 children 值为空 + if (option?.children?.length) { + values[index + 1] = '' + } + const combineResult = [ + ...innerValue.slice(0, startIndex), + ...values.splice(startIndex), + ] + setInnerValue([...combineResult]) + const optionFirst = props?.options?.[0] as PickerOptionItem[] + if ( + !isEqual( + formatCascadeOptions(optionFirst, combineResult), + innerOptions + ) + ) { + setInnerOptions(formatCascadeOptions(optionFirst, combineResult)) + } + } }, - [] + [innerValue, props.options, columnsType, innerOptions] ) const selectedOptions = useMemo(() => { - return options.map((columnOptions, index) => { - const selectedOption = columnOptions.find( - (item) => item.value === innerValue[index] - ) - return selectedOption || columnOptions[0] - }) - }, [options, innerValue]) + return innerOptions + .map((columnOptions, index) => { + const selectedOption = columnOptions.find( + (item) => item.value === innerValue[index] + ) + return selectedOption + // return selectedOption || columnOptions[0] + }) + .filter(Boolean) as PickerOptionItem[] + }, [innerOptions, innerValue]) useEffect(() => { - onChange?.(innerValue, selectedOptions) + onChange?.({ + value: innerValue, + index: changeIndex.current, + selectedOptions, + }) }, [innerValue, selectedOptions, onChange]) return ( {innerOptions.map((item, index) => ( = (props, ref) => { const { options, - defaultValue, + defaultValue = [], value, duration, threeDimensional, @@ -39,56 +46,152 @@ const InternalPickerView: ForwardRefRenderFunction< const [selectedValue] = usePropsValue({ value, - defaultValue, - finalValue: defaultValue, + defaultValue: [...defaultValue], + finalValue: [...defaultValue], }) const [innerValue, setInnerValue] = useState(selectedValue) - const [innerOptions, setInnerOptions] = useState(options) + const [innerOptions, setInnerOptions] = useState([] as PickerOptions[]) + const changeIndex = useRef(0) + + /** + * 数据类型:级联、多列 + */ + const columnsType = useMemo(() => { + const [firstColumn] = props.options as PickerOptions[] + if (Array.isArray(firstColumn) && 'children' in firstColumn[0]) { + return 'cascade' + } + return 'multiple' + }, [props.options]) + + const formatCascadeOptions = ( + options: PickerOptions, + value: PickerValue[] + ) => { + if (!options.length) return [] // 如果 options 为空,直接返回空数组 + + const formatted: PickerOptions[] = [] + let columnOptions: PickerOptionItem = { + label: '', + value: '', + children: options, + } + + let columnIndex = 0 + while (columnOptions && columnOptions.children) { + const currentOptions: PickerOptions = columnOptions.children + formatted.push(currentOptions) + + const currentValue = value?.[columnIndex] + if (currentValue === 0) { + // 如果 currentValue 为 0,返回第一个 children + columnOptions = currentOptions[0] + } else if (currentValue) { + // 如果 currentValue 存在,查找匹配的项 + const index = currentOptions.findIndex( + (columnItem) => columnItem.value === currentValue + ) + columnOptions = currentOptions[index === -1 ? 0 : index] // 如果未找到,默认取第一个 + } else { + break // 如果 currentValue 不存在,终止循环 + } + + columnIndex++ + } + return formatted + } + + const formatOptions = useMemo(() => { + if (columnsType === 'cascade') { + return formatCascadeOptions( + props?.options?.[0] as PickerOptions, + innerValue + ) + } + return props.options + }, [innerValue, options, columnsType]) useEffect(() => { - if (selectedValue !== innerValue) { - setInnerValue(selectedValue) + if (props.options !== innerOptions) { + setInnerOptions(formatOptions as PickerOptions[]) } - }, [selectedValue]) + }, [props.options, innerValue]) useEffect(() => { - if (options !== innerOptions) { - setInnerOptions(options) + if (selectedValue !== innerValue) { + setInnerValue(selectedValue) } - }, [options]) + }, [selectedValue]) const handleSelect = useCallback( (option: PickerOptionItem, index: number) => { const newValue = option?.value - if (!newValue) return - setInnerValue((prev) => { - if (prev[index] === newValue) return prev - const next = [...prev] - next[index] = newValue - return next - }) + if (!newValue || innerValue[index] === newValue) return + changeIndex.current = index + if (columnsType === 'multiple') { + setInnerValue((prev) => { + const next = [...prev] + next[index] = newValue + return next + }) + } else { + const startIndex = index + const values: PickerValue[] = [] + values[index] = option.value + while (option?.children?.[0]) { + values[index + 1] = option.children[0].value + index++ + option = option.children[0] + } + // 当前改变列的下一列 children 值为空 + if (option?.children?.length) { + values[index + 1] = '' + } + const combineResult = [ + ...innerValue.slice(0, startIndex), + ...values.splice(startIndex), + ] + setInnerValue([...combineResult]) + const optionFirst = props?.options?.[0] as PickerOptionItem[] + if ( + !isEqual( + formatCascadeOptions(optionFirst, combineResult), + innerOptions + ) + ) { + setInnerOptions(formatCascadeOptions(optionFirst, combineResult)) + } + } }, - [] + [innerValue, props.options, columnsType, innerOptions] ) const selectedOptions = useMemo(() => { - return options.map((columnOptions, index) => { - const selectedOption = columnOptions.find( - (item) => item.value === innerValue[index] - ) - return selectedOption || columnOptions[0] - }) - }, [options, innerValue]) + return innerOptions + .map((columnOptions, index) => { + const selectedOption = columnOptions.find( + (item) => item.value === innerValue[index] + ) + return selectedOption + // return selectedOption || columnOptions[0] + }) + .filter(Boolean) as PickerOptionItem[] + }, [innerOptions, innerValue]) useEffect(() => { - onChange?.(innerValue, selectedOptions) + onChange?.({ + value: innerValue, + index: changeIndex.current, + selectedOptions, + }) }, [innerValue, selectedOptions, onChange]) return (
{innerOptions.map((item, index) => ( React.ReactNode } +export interface PickerOnChangeCallbackParameter { + value: PickerValue[] + index: number + selectedOptions: PickerOptionItem[] +} + export interface PickerViewProps extends BasicComponent { + setRefs?: (ref: any) => any options: PickerOptions[] value?: PickerValue[] defaultValue?: PickerValue[] threeDimensional?: boolean duration?: number | string renderLabel: (item: PickerOptionItem) => React.ReactNode - onChange?: (value: PickerValue[], selectOptions: PickerOptionItem[]) => void + onChange?: (arg0: PickerOnChangeCallbackParameter) => void } From a030ff49d096d7972990427565585e6319d53a0f Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 11:32:36 +0800 Subject: [PATCH 05/24] fix: update test --- .../__snapshots__/pickerview.spec.tsx.snap | 148 ++++++++++++++++++ .../pickerview/__test__/pickerview.spec.tsx | 29 +++- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap new file mode 100644 index 0000000000..b738806b33 --- /dev/null +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -0,0 +1,148 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`should match snapshot 1`] = ` +
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
+`; + +exports[`should render tiled 1`] = ` +
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
+`; diff --git a/src/packages/pickerview/__test__/pickerview.spec.tsx b/src/packages/pickerview/__test__/pickerview.spec.tsx index 089e995187..f4f9701505 100644 --- a/src/packages/pickerview/__test__/pickerview.spec.tsx +++ b/src/packages/pickerview/__test__/pickerview.spec.tsx @@ -3,7 +3,34 @@ import { render } from '@testing-library/react' import '@testing-library/jest-dom' import PickerView from '../pickerview' +const listData = [ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], +] + test('should match snapshot', () => { - const { container } = render() + const { container } = render( + + ) + expect(container).toMatchSnapshot() +}) + +test('should render tiled', () => { + const { container } = render( + + ) expect(container).toMatchSnapshot() }) From f436c51e119faefd2cd54a1e8a348c11aee2a845 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 11:39:30 +0800 Subject: [PATCH 06/24] fix: update error --- src/packages/pickerview/demo.taro.tsx | 2 +- src/packages/pickerview/doc.md | 2 +- src/packages/pickerview/doc.taro.md | 2 +- src/packages/pickerview/doc.zh-TW.md | 2 +- src/packages/pickerview/pickerroller.taro.tsx | 2 +- src/packages/pickerview/pickerroller.tsx | 2 +- src/packages/pickerview/pickerview.taro.tsx | 8 ++++++-- src/packages/pickerview/pickerview.tsx | 8 ++++++-- 8 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx index 854ebb089d..73487d67b0 100644 --- a/src/packages/pickerview/demo.taro.tsx +++ b/src/packages/pickerview/demo.taro.tsx @@ -51,7 +51,7 @@ const PickerViewDemo = () => { {translated.tiled} -

{translated.cascade}

+ {translated.cascade} diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index ad1777daa9..ba0979c327 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -5,7 +5,7 @@ PickerView 是 Picker 的内容区域。 ## 引入 ```tsx -import { name } from '@nutui/nutui-react' +import { PickerView } from '@nutui/nutui-react' ``` ## 示例代码 diff --git a/src/packages/pickerview/doc.taro.md b/src/packages/pickerview/doc.taro.md index f8e186b1be..03fdff98cc 100644 --- a/src/packages/pickerview/doc.taro.md +++ b/src/packages/pickerview/doc.taro.md @@ -5,7 +5,7 @@ PickerView 是 Picker 的内容区域。 ## 引入 ```tsx -import { name } from '@nutui/nutui-react-taro' +import { PickerView } from '@nutui/nutui-react-taro' ``` ## 示例代码 diff --git a/src/packages/pickerview/doc.zh-TW.md b/src/packages/pickerview/doc.zh-TW.md index c944d61291..155b4fc341 100644 --- a/src/packages/pickerview/doc.zh-TW.md +++ b/src/packages/pickerview/doc.zh-TW.md @@ -5,7 +5,7 @@ PickerView 是 Picker 的內容區域。 ## 引入 ```tsx -import { name } from '@nutui/nutui-react-taro' +import { PickerView } from '@nutui/nutui-react-taro' ``` ## 示例代碼 diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index dc0b2b56a9..476e5da967 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -257,7 +257,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< eventOptions ) } - }) + }, []) const touchRollerStyle = () => { return { diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 47b66ef808..e4a485b3ae 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -230,7 +230,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< pickerRollerRef.current?.removeEventListener('touchmove', touchMove) pickerRollerRef.current?.removeEventListener('touchend', touchEnd) } - }) + }, []) const touchRollerStyle = () => { return { diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index 06f20401f6..c13a3c052b 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -59,8 +59,12 @@ const InternalPickerView: ForwardRefRenderFunction< * 数据类型:级联、多列 */ const columnsType = useMemo(() => { - const [firstColumn] = props.options as PickerOptions[] - if (Array.isArray(firstColumn) && 'children' in firstColumn[0]) { + const firstColumn = (props.options as PickerOptions[])[0] || [] + if ( + Array.isArray(firstColumn) && + firstColumn.length > 0 && + 'children' in firstColumn[0] + ) { return 'cascade' } return 'multiple' diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index daeff9c23c..60b98efe85 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -58,8 +58,12 @@ const InternalPickerView: ForwardRefRenderFunction< * 数据类型:级联、多列 */ const columnsType = useMemo(() => { - const [firstColumn] = props.options as PickerOptions[] - if (Array.isArray(firstColumn) && 'children' in firstColumn[0]) { + const firstColumn = (props.options as PickerOptions[])[0] || [] + if ( + Array.isArray(firstColumn) && + firstColumn.length > 0 && + 'children' in firstColumn[0] + ) { return 'cascade' } return 'multiple' From 6a6bc39190df5de4a639f4ad56d70b137a093f73 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 11:42:13 +0800 Subject: [PATCH 07/24] fix: update error --- src/packages/pickerview/pickerroller.taro.tsx | 2 +- src/packages/pickerview/pickerroller.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index 476e5da967..9f7768459f 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -257,7 +257,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< eventOptions ) } - }, []) + }, [pickerRollerRef.current, touchStart, touchMove, touchEnd]) const touchRollerStyle = () => { return { diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index e4a485b3ae..02ff4f535b 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -230,7 +230,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< pickerRollerRef.current?.removeEventListener('touchmove', touchMove) pickerRollerRef.current?.removeEventListener('touchend', touchEnd) } - }, []) + }, [pickerRollerRef.current, touchStart, touchMove, touchEnd]) const touchRollerStyle = () => { return { From 10be5cd5bebd6d257f83bf7affbff2dfa5e79ecd Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 11:46:09 +0800 Subject: [PATCH 08/24] fix: update demo --- src/packages/pickerview/demos/h5/demo2.tsx | 4 ++-- src/packages/pickerview/demos/h5/demo4.tsx | 4 ++-- src/packages/pickerview/demos/h5/demo5.tsx | 4 ++-- src/packages/pickerview/demos/taro/demo2.tsx | 4 ++-- src/packages/pickerview/demos/taro/demo4.tsx | 4 ++-- src/packages/pickerview/demos/taro/demo5.tsx | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/packages/pickerview/demos/h5/demo2.tsx b/src/packages/pickerview/demos/h5/demo2.tsx index dff4a404b0..894b633b01 100644 --- a/src/packages/pickerview/demos/h5/demo2.tsx +++ b/src/packages/pickerview/demos/h5/demo2.tsx @@ -1,7 +1,7 @@ import React from 'react' import { PickerView, Cell } from '@nutui/nutui-react' -const Demo1 = () => { +const Demo2 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -31,4 +31,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo2 diff --git a/src/packages/pickerview/demos/h5/demo4.tsx b/src/packages/pickerview/demos/h5/demo4.tsx index 320c7cc39e..cfa2ab063e 100644 --- a/src/packages/pickerview/demos/h5/demo4.tsx +++ b/src/packages/pickerview/demos/h5/demo4.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { PickerView, Cell } from '@nutui/nutui-react' -const Demo1 = () => { +const Demo4 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -35,4 +35,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo4 diff --git a/src/packages/pickerview/demos/h5/demo5.tsx b/src/packages/pickerview/demos/h5/demo5.tsx index 4bf68a2261..e9795b79f4 100644 --- a/src/packages/pickerview/demos/h5/demo5.tsx +++ b/src/packages/pickerview/demos/h5/demo5.tsx @@ -1,7 +1,7 @@ import React from 'react' import { PickerView, Cell } from '@nutui/nutui-react' -const Demo1 = () => { +const Demo5 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -32,4 +32,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo5 diff --git a/src/packages/pickerview/demos/taro/demo2.tsx b/src/packages/pickerview/demos/taro/demo2.tsx index 72a79c0a84..838e0ea546 100644 --- a/src/packages/pickerview/demos/taro/demo2.tsx +++ b/src/packages/pickerview/demos/taro/demo2.tsx @@ -1,7 +1,7 @@ import React from 'react' import { PickerView, Cell } from '@nutui/nutui-react-taro' -const Demo1 = () => { +const Demo2 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -31,4 +31,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo2 diff --git a/src/packages/pickerview/demos/taro/demo4.tsx b/src/packages/pickerview/demos/taro/demo4.tsx index 2716cd35fd..e6c5666a3e 100644 --- a/src/packages/pickerview/demos/taro/demo4.tsx +++ b/src/packages/pickerview/demos/taro/demo4.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react' import { PickerView, Cell } from '@nutui/nutui-react-taro' -const Demo1 = () => { +const Demo4 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -35,4 +35,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo4 diff --git a/src/packages/pickerview/demos/taro/demo5.tsx b/src/packages/pickerview/demos/taro/demo5.tsx index a8d43a86b0..f313af7bb5 100644 --- a/src/packages/pickerview/demos/taro/demo5.tsx +++ b/src/packages/pickerview/demos/taro/demo5.tsx @@ -1,7 +1,7 @@ import React from 'react' import { PickerView, Cell } from '@nutui/nutui-react-taro' -const Demo1 = () => { +const Demo5 = () => { const listData = [ [ { value: 1, label: '南京市' }, @@ -32,4 +32,4 @@ const Demo1 = () => { ) } -export default Demo1 +export default Demo5 From 8ad73921297a66d681f966dd7917bf9bdede432c Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 12:32:23 +0800 Subject: [PATCH 09/24] fix: update test --- .../__snapshots__/pickerview.spec.tsx.snap | 371 ++++++++++++++++++ .../pickerview/__test__/pickerview.spec.tsx | 113 +++++- 2 files changed, 483 insertions(+), 1 deletion(-) diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap index b738806b33..58558b86b4 100644 --- a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -1,5 +1,295 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`should match cascade 1`] = ` +
+
+
+
+
+ 北京 | 测试 +
+
+ 上海 | 测试 +
+
+
+
+
+
+ 朝阳区 | 测试 +
+
+ 海淀区 | 测试 +
+
+ 大兴区 | 测试 +
+
+ 东城区 | 测试 +
+
+ 西城区 | 测试 +
+
+ 丰台区 | 测试 +
+
+
+
+
+
+
+`; + +exports[`should match onchange 1`] = ` +{ + "asFragment": [Function], + "baseElement": +
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
+ , + "container":
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; + exports[`should match snapshot 1`] = `
`; + +exports[`should render with Multi Column 1`] = ` +
+
+
+
+
+ 周一 +
+
+ 周二 +
+
+ 周三 +
+
+ 周四 +
+
+ 周五 +
+
+
+
+
+
+ 上午 +
+
+ 下午 +
+
+ 晚上 +
+
+
+
+
+
+
+`; diff --git a/src/packages/pickerview/__test__/pickerview.spec.tsx b/src/packages/pickerview/__test__/pickerview.spec.tsx index f4f9701505..dc758e7088 100644 --- a/src/packages/pickerview/__test__/pickerview.spec.tsx +++ b/src/packages/pickerview/__test__/pickerview.spec.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useState } from 'react' import { render } from '@testing-library/react' import '@testing-library/jest-dom' import PickerView from '../pickerview' @@ -17,6 +17,82 @@ const listData = [ ], ] +const MultiColumnData = [ + [ + { label: '周一', value: 'Monday' }, + { label: '周二', value: 'Tuesday' }, + { label: '周三', value: 'Wednesday' }, + { label: '周四', value: 'Thursday' }, + { label: '周五', value: 'Friday' }, + ], + [ + { label: '上午', value: 'Morning' }, + { label: '下午', value: 'Afternoon' }, + { label: '晚上', value: 'Evening' }, + ], +] + +const cascadeData = [ + [ + { + value: 1, + label: '北京', + children: [ + { + value: 1, + label: '朝阳区', + }, + { + value: 2, + label: '海淀区', + }, + { + value: 3, + label: '大兴区', + }, + { + value: 4, + label: '东城区', + }, + { + value: 5, + label: '西城区', + }, + { + value: 6, + label: '丰台区', + }, + ], + }, + { + value: 2, + label: '上海', + children: [ + { + value: 1, + label: '黄埔区', + }, + { + value: 2, + label: '长宁区', + }, + { + value: 3, + label: '普陀区', + }, + { + value: 4, + label: '杨浦区', + }, + { + value: 5, + label: '浦东新区', + }, + ], + }, + ], +] + test('should match snapshot', () => { const { container } = render( @@ -34,3 +110,38 @@ test('should render tiled', () => { ) expect(container).toMatchSnapshot() }) + +test('should render with Multi Column', () => { + const { container } = render( + + ) + expect(container.querySelectorAll('.nut-pickerview-list').length).toBe(2) + expect(container).toMatchSnapshot() +}) + +test('should match onchange', async () => { + const PenderContent = () => { + const [value, setValue] = useState([1]) + + setTimeout(() => { + setValue([2]) + }, 1000) + + return + } + const container = render() + + expect(container).toMatchSnapshot() +}) + +test('should match cascade', () => { + const { container } = render( + `${item.label} | 测试`} + options={cascadeData} + onChange={() => {}} + /> + ) + expect(container).toMatchSnapshot() +}) From 95b5423d24b23c84959cfbbb07dba81d0328b2bc Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 14:37:13 +0800 Subject: [PATCH 10/24] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=BC=82?= =?UTF-8?q?=E6=AD=A5demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__snapshots__/pickerview.spec.tsx.snap | 171 ++++++++++++++++++ .../pickerview/__test__/pickerview.spec.tsx | 41 ++++- src/packages/pickerview/demo.taro.tsx | 6 + src/packages/pickerview/demo.tsx | 6 + src/packages/pickerview/demos/h5/demo7.tsx | 38 ++++ src/packages/pickerview/demos/taro/demo7.tsx | 38 ++++ src/packages/pickerview/pickerview.taro.tsx | 11 +- src/packages/pickerview/pickerview.tsx | 11 +- 8 files changed, 308 insertions(+), 14 deletions(-) create mode 100644 src/packages/pickerview/demos/h5/demo7.tsx create mode 100644 src/packages/pickerview/demos/taro/demo7.tsx diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap index 58558b86b4..0ab994d585 100644 --- a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -1,5 +1,98 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html +exports[`should handle empty options 1`] = ` +
+
+
+
+
+
+`; + +exports[`should handle invalid defaultValue 1`] = ` +
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
+`; + exports[`should match cascade 1`] = `
+
+
+
+
+ 南京市 +
+
+ 无锡市 +
+
+ 海北藏族自治区 +
+
+ 北京市 +
+
+ 连云港市 +
+
+ 大庆市 +
+
+ 绥化市 +
+
+ 潍坊市 +
+
+ 乌鲁木齐市 +
+
+
+
+
+
+
+`; + exports[`should match snapshot 1`] = `
{ ) expect(container).toMatchSnapshot() + expect(screen.getByText('南京市')).toBeInTheDocument() }) test('should render tiled', () => { @@ -115,7 +116,17 @@ test('should render with Multi Column', () => { const { container } = render( ) - expect(container.querySelectorAll('.nut-pickerview-list').length).toBe(2) + const columns = container.querySelectorAll('.nut-pickerview-list') + expect(columns.length).toBe(2) + + // 检查列内容 + const firstColumn = columns[0] + expect(firstColumn.textContent).toContain('周一') + expect(firstColumn.textContent).toContain('周二') + + const secondColumn = columns[1] + expect(secondColumn.textContent).toContain('上午') + expect(secondColumn.textContent).toContain('下午') expect(container).toMatchSnapshot() }) @@ -123,15 +134,29 @@ test('should match onchange', async () => { const PenderContent = () => { const [value, setValue] = useState([1]) - setTimeout(() => { - setValue([2]) - }, 1000) + useEffect(() => { + setTimeout(() => { + setValue([3]) + }, 1000) + }, []) - return + return ( + { + if (value[0] === 3) { + setValue([1]) + } + }} + /> + ) } const container = render() - expect(container).toMatchSnapshot() + await waitFor(() => { + expect(container).toMatchSnapshot() + }) }) test('should match cascade', () => { diff --git a/src/packages/pickerview/demo.taro.tsx b/src/packages/pickerview/demo.taro.tsx index 73487d67b0..8f34000096 100644 --- a/src/packages/pickerview/demo.taro.tsx +++ b/src/packages/pickerview/demo.taro.tsx @@ -9,6 +9,7 @@ import Demo3 from './demos/taro/demo3' import Demo4 from './demos/taro/demo4' import Demo5 from './demos/taro/demo5' import Demo6 from './demos/taro/demo6' +import Demo7 from './demos/taro/demo7' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -19,6 +20,7 @@ const PickerViewDemo = () => { controlled: '受控', tiled: '平铺', cascade: '级联', + asynchronous: '异步数据', }, 'en-US': { title: 'Basic Usage', @@ -27,6 +29,7 @@ const PickerViewDemo = () => { controlled: 'Controlled', tiled: 'Tiled', cascade: 'Cascade', + asynchronous: 'Asynchronous', }, 'zh-TW': { title: '基礎用法', @@ -35,6 +38,7 @@ const PickerViewDemo = () => { controlled: '受控', tiled: '平鋪', cascade: '級聯', + asynchronous: '異步數據', }, }) return ( @@ -53,6 +57,8 @@ const PickerViewDemo = () => { {translated.cascade} + {translated.asynchronous} + ) diff --git a/src/packages/pickerview/demo.tsx b/src/packages/pickerview/demo.tsx index b94f91de80..8d95022679 100644 --- a/src/packages/pickerview/demo.tsx +++ b/src/packages/pickerview/demo.tsx @@ -6,6 +6,7 @@ import Demo3 from './demos/h5/demo3' import Demo4 from './demos/h5/demo4' import Demo5 from './demos/h5/demo5' import Demo6 from './demos/h5/demo6' +import Demo7 from './demos/h5/demo7' const PickerViewDemo = () => { const [translated] = useTranslate({ @@ -16,6 +17,7 @@ const PickerViewDemo = () => { controlled: '受控', tiled: '平铺', cascade: '级联', + asynchronous: '异步数据', }, 'en-US': { title: 'Basic Usage', @@ -24,6 +26,7 @@ const PickerViewDemo = () => { controlled: 'Controlled', tiled: 'Tiled', cascade: 'Cascade', + asynchronous: 'Asynchronous', }, 'zh-TW': { title: '基礎用法', @@ -32,6 +35,7 @@ const PickerViewDemo = () => { controlled: '受控', tiled: '平鋪', cascade: '級聯', + asynchronous: '異步數據', }, }) return ( @@ -48,6 +52,8 @@ const PickerViewDemo = () => {

{translated.cascade}

+

{translated.asynchronous}

+
) } diff --git a/src/packages/pickerview/demos/h5/demo7.tsx b/src/packages/pickerview/demos/h5/demo7.tsx new file mode 100644 index 0000000000..d245a189c5 --- /dev/null +++ b/src/packages/pickerview/demos/h5/demo7.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, useState } from 'react' +import { PickerView, Cell, PickerOptions } from '@nutui/nutui-react' + +const Demo7 = () => { + const [columnsList, setColumnsList] = useState([] as PickerOptions[]) + + useEffect(() => { + setTimeout(() => { + setColumnsList([ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ]) + }, 3000) + }, []) + return ( + <> + + { + console.log('onChange', value, selectedOptions) + }} + /> + + + ) +} +export default Demo7 diff --git a/src/packages/pickerview/demos/taro/demo7.tsx b/src/packages/pickerview/demos/taro/demo7.tsx new file mode 100644 index 0000000000..a32defecfc --- /dev/null +++ b/src/packages/pickerview/demos/taro/demo7.tsx @@ -0,0 +1,38 @@ +import React, { useEffect, useState } from 'react' +import { PickerView, Cell, PickerOptions } from '@nutui/nutui-react-taro' + +const Demo7 = () => { + const [columnsList, setColumnsList] = useState([] as PickerOptions[]) + + useEffect(() => { + setTimeout(() => { + setColumnsList([ + [ + { value: 1, label: '南京市' }, + { value: 2, label: '无锡市' }, + { value: 3, label: '海北藏族自治区' }, + { value: 4, label: '北京市' }, + { value: 5, label: '连云港市' }, + { value: 8, label: '大庆市' }, + { value: 9, label: '绥化市' }, + { value: 10, label: '潍坊市' }, + { value: 12, label: '乌鲁木齐市' }, + ], + ]) + }, 3000) + }, []) + return ( + <> + + { + console.log('onChange', value, selectedOptions) + }} + /> + + + ) +} +export default Demo7 diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index c13a3c052b..a1ba018910 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -118,7 +118,8 @@ const InternalPickerView: ForwardRefRenderFunction< }, [innerValue, options, columnsType]) useEffect(() => { - if (props.options !== innerOptions) { + const options = props.options + if (Array.isArray(options) && options.length && options !== innerOptions) { setInnerOptions(formatOptions as PickerOptions[]) } }, [props.options, innerValue]) @@ -207,8 +208,12 @@ const InternalPickerView: ForwardRefRenderFunction< threeDimensional={threeDimensional} /> ))} - - + {innerOptions?.length ? ( + <> +
+
+ + ) : null} ) } diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index 60b98efe85..66770248c0 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -117,7 +117,8 @@ const InternalPickerView: ForwardRefRenderFunction< }, [innerValue, options, columnsType]) useEffect(() => { - if (props.options !== innerOptions) { + const options = props.options + if (Array.isArray(options) && options.length && options !== innerOptions) { setInnerOptions(formatOptions as PickerOptions[]) } }, [props.options, innerValue]) @@ -206,8 +207,12 @@ const InternalPickerView: ForwardRefRenderFunction< threeDimensional={threeDimensional} /> ))} -
-
+ {innerOptions?.length ? ( + <> +
+
+ + ) : null}
) } From fe66362d4d9bd81e29569e9591a9e48d385aafaf Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Wed, 12 Feb 2025 14:51:56 +0800 Subject: [PATCH 11/24] fix: update doc --- src/packages/pickerview/doc.en-US.md | 6 +++--- src/packages/pickerview/doc.md | 5 +++-- src/packages/pickerview/doc.taro.md | 5 +++-- src/packages/pickerview/doc.zh-TW.md | 5 +++-- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/packages/pickerview/doc.en-US.md b/src/packages/pickerview/doc.en-US.md index 6ec4aa0aa8..a66464c311 100644 --- a/src/packages/pickerview/doc.en-US.md +++ b/src/packages/pickerview/doc.en-US.md @@ -64,20 +64,20 @@ import { Picker } from '@nutui/nutui-react' | Property | Description | Type | Default | | --- | --- | --- | --- | -| options | Tabular data | `Array` | `[]` | +| options | Tabular data | `PickerOptionItem[][]` | `[]` | | value | Selected value, controlled | `Array` | `[]` | | defaultValue | Default value | `Array` | `[]` | | threeDimensional | Whether to enable 3D effect | `boolean` | `true` | | duration | The duration of inertial rolling during rapid sliding, in ms | `string` \| `number` | `1000` | | onChange | Called when the value of each column changes | `({value, index, selectedOptions}) => void` | `-` | -### options +### PickerOptionItem | Property | Description | Type | Default | | --- | --- | --- | --- | | label | Text of column | `string` \| `number` | `-` | | value | Value of column | `string` \| `number` | `-` | -| children | Cascader Option | `Array` | `-` | +| children | Cascader Option | `PickerOptionItem[]` | `-` | ## Theming diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index ba0979c327..0ba9af80dc 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -64,19 +64,20 @@ import { PickerView } from '@nutui/nutui-react' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| options | 列表数据 | `Array` | `[]` | +| options | 列表数据 | `PickerOptionItem[][]` | `[]` | | value | 选中值,受控 | `Array` | `[]` | | defaultValue | 默认选中 | `Array` | `[]` | | threeDimensional | 是否开启3D效果 | `boolean` | `true` | | duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | | onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | -### options 数据结构 +### PickerOptionItem | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | label | 选项的文字内容 | `string` \| `number` | `-` | | value | 选项对应的值,且唯一 | `string` \| `number` | `-` | +| children | 用于级联选项 | `PickerOptionItem[]` | `-` | ## 主题定制 diff --git a/src/packages/pickerview/doc.taro.md b/src/packages/pickerview/doc.taro.md index 03fdff98cc..fe1a838aca 100644 --- a/src/packages/pickerview/doc.taro.md +++ b/src/packages/pickerview/doc.taro.md @@ -64,19 +64,20 @@ import { PickerView } from '@nutui/nutui-react-taro' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| options | 列表数据 | `Array` | `[]` | +| options | 列表数据 | `PickerOptionItem[][]` | `[]` | | value | 选中值,受控 | `Array` | `[]` | | defaultValue | 默认选中 | `Array` | `[]` | | threeDimensional | 是否开启3D效果 | `boolean` | `true` | | duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | | onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | -### options 数据结构 +### PickerOptionItem | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | label | 选项的文字内容 | `string` \| `number` | `-` | | value | 选项对应的值,且唯一 | `string` \| `number` | `-` | +| children | 用于级联选项 | `PickerOptionItem[]` | `-` | ## 主题定制 diff --git a/src/packages/pickerview/doc.zh-TW.md b/src/packages/pickerview/doc.zh-TW.md index 155b4fc341..aecd67eaff 100644 --- a/src/packages/pickerview/doc.zh-TW.md +++ b/src/packages/pickerview/doc.zh-TW.md @@ -64,19 +64,20 @@ import { PickerView } from '@nutui/nutui-react-taro' | 屬性 | 說明 | 類型 | 默認值 | | --- | --- | --- | --- | -| options | 列錶數據 | `Array` | `[]` | +| options | 列錶數據 | `PickerOptionItem[][]` | `[]` | | value | 選中值,受控 | `Array` | `[]` | | defaultValue | 默認選中 | `Array` | `[]` | | threeDimensional | 是否開啟3D效果 | `boolean` | `true` | | duration | 快速滑動時慣性滾動的時長,單位 ms | `string` \| `number` | `1000` | | onChange | 每一列值變更時調用 | `({value, index, selectedOptions}) => void` | `-` | -### options 數據結構 +### PickerOptionItem | 屬性 | 說明 | 類型 | 默認值 | | --- | --- | --- | --- | | label | 選項的文字內容 | `string` \| `number` | `-` | | value | 選項對應的值,且唯一 | `string` \| `number` | `-` | +| children | 用於級聯選項 | `PickerOptionItem[]` | `-` | ## 主題定制 From d5118f6f22e75f56dede15257aec3dcd01b08a62 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 09:39:17 +0800 Subject: [PATCH 12/24] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=A0=87?= =?UTF-8?q?=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/packages/pickerview/pickerview.taro.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index a1ba018910..b6675f4dd1 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -210,8 +210,8 @@ const InternalPickerView: ForwardRefRenderFunction< ))} {innerOptions?.length ? ( <> -
-
+ + ) : null} From a787f7da9bbb2e4de3c59875a5fe029e38e8889b Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 11:58:58 +0800 Subject: [PATCH 13/24] feat: add test --- .../__snapshots__/pickerview.spec.tsx.snap | 298 +----------------- .../pickerview/__test__/pickerview.spec.tsx | 50 ++- 2 files changed, 42 insertions(+), 306 deletions(-) diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap index 0ab994d585..2374bb7a94 100644 --- a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -1,21 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`should handle empty options 1`] = ` -
-
-
-
-
-
-`; - -exports[`should handle invalid defaultValue 1`] = ` +exports[`should match base 1`] = `
-
-
-
-
-
- 南京市 -
-
- 无锡市 -
-
- 海北藏族自治区 -
-
- 北京市 -
-
- 连云港市 -
-
- 大庆市 -
-
- 绥化市 -
-
- 潍坊市 -
-
- 乌鲁木齐市 -
-
-
-
-
-
-
- , - "container":
-
-
-
-
- 南京市 -
-
- 无锡市 -
-
- 海北藏族自治区 -
-
- 北京市 -
-
- 连云港市 -
-
- 大庆市 -
-
- 绥化市 -
-
- 潍坊市 -
-
- 乌鲁木齐市 -
-
-
-
-
-
-
, - "debug": [Function], - "findAllByAltText": [Function], - "findAllByDisplayValue": [Function], - "findAllByLabelText": [Function], - "findAllByPlaceholderText": [Function], - "findAllByRole": [Function], - "findAllByTestId": [Function], - "findAllByText": [Function], - "findAllByTitle": [Function], - "findByAltText": [Function], - "findByDisplayValue": [Function], - "findByLabelText": [Function], - "findByPlaceholderText": [Function], - "findByRole": [Function], - "findByTestId": [Function], - "findByText": [Function], - "findByTitle": [Function], - "getAllByAltText": [Function], - "getAllByDisplayValue": [Function], - "getAllByLabelText": [Function], - "getAllByPlaceholderText": [Function], - "getAllByRole": [Function], - "getAllByTestId": [Function], - "getAllByText": [Function], - "getAllByTitle": [Function], - "getByAltText": [Function], - "getByDisplayValue": [Function], - "getByLabelText": [Function], - "getByPlaceholderText": [Function], - "getByRole": [Function], - "getByTestId": [Function], - "getByText": [Function], - "getByTitle": [Function], - "queryAllByAltText": [Function], - "queryAllByDisplayValue": [Function], - "queryAllByLabelText": [Function], - "queryAllByPlaceholderText": [Function], - "queryAllByRole": [Function], - "queryAllByTestId": [Function], - "queryAllByText": [Function], - "queryAllByTitle": [Function], - "queryByAltText": [Function], - "queryByDisplayValue": [Function], - "queryByLabelText": [Function], - "queryByPlaceholderText": [Function], - "queryByRole": [Function], - "queryByTestId": [Function], - "queryByText": [Function], - "queryByTitle": [Function], - "rerender": [Function], - "unmount": [Function], -} -`; - -exports[`should match onchange 2`] = `
-
-
-
- 南京市 -
-
- 无锡市 -
-
- 海北藏族自治区 -
-
- 北京市 -
-
- 连云港市 -
-
- 大庆市 -
-
- 绥化市 -
-
- 潍坊市 -
-
- 乌鲁木齐市 -
-
-
-
-
-
+ />
`; diff --git a/src/packages/pickerview/__test__/pickerview.spec.tsx b/src/packages/pickerview/__test__/pickerview.spec.tsx index 85c48a0cfd..4079631473 100644 --- a/src/packages/pickerview/__test__/pickerview.spec.tsx +++ b/src/packages/pickerview/__test__/pickerview.spec.tsx @@ -2,6 +2,8 @@ import React, { useEffect, useState } from 'react' import { render, waitFor, screen } from '@testing-library/react' import '@testing-library/jest-dom' import PickerView from '../pickerview' +import { PickerOptions } from '../types' +import useRefs from '@/utils/use-refs' const listData = [ [ @@ -93,7 +95,7 @@ const cascadeData = [ ], ] -test('should match snapshot', () => { +test('should match base', () => { const { container } = render( ) @@ -132,27 +134,32 @@ test('should render with Multi Column', () => { test('should match onchange', async () => { const PenderContent = () => { - const [value, setValue] = useState([1]) + const [value, setValue] = useState([]) + const [options, setInnerOptions] = useState([]) useEffect(() => { - setTimeout(() => { - setValue([3]) + const timer = setTimeout(() => { + setInnerOptions(listData) + setValue([1]) }, 1000) + + return () => clearTimeout(timer) // 清理定时器 }, []) return ( { - if (value[0] === 3) { - setValue([1]) + options={options} + onChange={({ value }) => { + if (value[0] === 1) { + setValue([3]) } }} /> ) } - const container = render() + + const { container } = render() await waitFor(() => { expect(container).toMatchSnapshot() @@ -162,7 +169,7 @@ test('should match onchange', async () => { test('should match cascade', () => { const { container } = render( `${item.label} | 测试`} options={cascadeData} onChange={() => {}} @@ -170,3 +177,26 @@ test('should match cascade', () => { ) expect(container).toMatchSnapshot() }) + +test('should match stopMomentum', async () => { + const PenderContent = () => { + const [refs, setRefs] = useRefs() + + return ( + { + refs.map((ref: any) => { + ref.stopMomentum() + return ref + }) + }} + /> + ) + } + const { container } = render() + const columns = container.querySelectorAll('.nut-pickerview-list') + expect(columns.length).toBe(2) +}) From c10fcc7922aa031db40c3e476ca0e1daca6b9c07 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 15:25:38 +0800 Subject: [PATCH 14/24] fix: update doc and test --- .../__snapshots__/pickerview.spec.tsx.snap | 82 +------------------ .../pickerview/__test__/pickerview.spec.tsx | 35 +++++--- src/packages/pickerview/doc.en-US.md | 2 +- src/packages/pickerview/pickerroller.taro.tsx | 2 +- src/packages/pickerview/pickerroller.tsx | 4 +- 5 files changed, 32 insertions(+), 93 deletions(-) diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap index 2374bb7a94..10f61a642c 100644 --- a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`should match base 1`] = ` >
`; -exports[`should match snapshot 1`] = ` -
-
-
-
-
- 南京市 -
-
- 无锡市 -
-
- 海北藏族自治区 -
-
- 北京市 -
-
- 连云港市 -
-
- 大庆市 -
-
- 绥化市 -
-
- 潍坊市 -
-
- 乌鲁木齐市 -
-
-
-
-
-
-
-`; - exports[`should render tiled 1`] = `
{ ) expect(container).toMatchSnapshot() }) - test('should match stopMomentum', async () => { const PenderContent = () => { + function useRefs() { + const refs = React.useRef([]) + + const setRefs = React.useCallback( + (index: number) => (el: HTMLDivElement) => { + if (el) refs.current[index] = el + }, + [] + ) + + const reset = React.useCallback(() => { + refs.current = [] + }, []) + + return [refs.current, setRefs as any, reset] + } + const [refs, setRefs] = useRefs() + const first = useRef(true) return ( { options={cascadeData} setRefs={setRefs} onChange={() => { - refs.map((ref: any) => { - ref.stopMomentum() - return ref - }) + if (!first.current) { + refs[0].stopMomentum() + } else { + first.current = false + } }} /> ) } - const { container } = render() - const columns = container.querySelectorAll('.nut-pickerview-list') - expect(columns.length).toBe(2) + render() }) diff --git a/src/packages/pickerview/doc.en-US.md b/src/packages/pickerview/doc.en-US.md index a66464c311..fb09497e26 100644 --- a/src/packages/pickerview/doc.en-US.md +++ b/src/packages/pickerview/doc.en-US.md @@ -5,7 +5,7 @@ The PickerView is the content area of the Picker. ## Import ```tsx -import { Picker } from '@nutui/nutui-react' +import { PickerView } from '@nutui/nutui-react' ``` ## Demo diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index 9f7768459f..9a543abd91 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -55,7 +55,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const currentLineSpacing = computedStyle.getPropertyValue( '--nutui-picker-item-height' ) - currentLineSpacing && + !!currentLineSpacing && (lineSpacing.current = parseFloat(currentLineSpacing)) } }, [pickerRollerRef.current]) diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 02ff4f535b..1cfd1f4b6a 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -53,7 +53,9 @@ const InternalPickerRoller: ForwardRefRenderFunction< const currentLineSpacing = computedStyle.getPropertyValue( '--nutui-picker-item-height' ) - lineSpacing.current = parseFloat(currentLineSpacing) + + !!currentLineSpacing && + (lineSpacing.current = parseFloat(currentLineSpacing)) } }, []) From 9e32bcdc7caba956d726f288706ccf87545ef0c7 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 15:31:32 +0800 Subject: [PATCH 15/24] fix: adjust the dependency package location --- src/packages/pickerview/pickerview.taro.tsx | 2 +- src/packages/pickerview/pickerview.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index b6675f4dd1..ffcf57760b 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -10,6 +10,7 @@ import classNames from 'classnames' import { View } from '@tarojs/components' import isEqual from 'react-fast-compare' import { ComponentDefaults } from '@/utils/typings' +import { usePropsValue } from '@/utils/use-props-value' import { PickerViewProps, PickerOptionItem, @@ -17,7 +18,6 @@ import { PickerOptions, } from './types' import PickerRoller from './pickerroller.taro' -import { usePropsValue } from '@/utils/use-props-value' const defaultProps = { ...ComponentDefaults, diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index 66770248c0..75d3bb3c14 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -9,6 +9,7 @@ import React, { import classNames from 'classnames' import isEqual from 'react-fast-compare' import { ComponentDefaults } from '@/utils/typings' +import { usePropsValue } from '@/utils/use-props-value' import { PickerViewProps, PickerOptionItem, @@ -16,7 +17,6 @@ import { PickerOptions, } from './types' import PickerRoller from './pickerroller' -import { usePropsValue } from '@/utils/use-props-value' const defaultProps = { ...ComponentDefaults, From f539b833f0427e639a22606b3c1ec0a7728fb7e7 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 15:33:25 +0800 Subject: [PATCH 16/24] fix: adjust the dependency package path --- src/packages/pickerview/pickerroller.taro.tsx | 2 +- src/packages/pickerview/pickerroller.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index 9a543abd91..ab97d306ec 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -6,7 +6,7 @@ import React, { useImperativeHandle, } from 'react' import { View } from '@tarojs/components' -import { useTouch } from '@/utils/use-touch' +import { useTouch } from '@/hooks/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' import { web } from '@/utils/platform-taro' diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 1cfd1f4b6a..75d443f5b4 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -5,7 +5,7 @@ import React, { ForwardRefRenderFunction, useImperativeHandle, } from 'react' -import { useTouch } from '@/utils/use-touch' +import { useTouch } from '@/hooks/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' From f06ec00809d2f84fedf5903982e2629525641214 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 16:05:30 +0800 Subject: [PATCH 17/24] fix: adjust the dependency package path --- src/packages/pickerview/pickerview.taro.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/pickerview/pickerview.taro.tsx b/src/packages/pickerview/pickerview.taro.tsx index ffcf57760b..8a52e52e97 100644 --- a/src/packages/pickerview/pickerview.taro.tsx +++ b/src/packages/pickerview/pickerview.taro.tsx @@ -10,7 +10,7 @@ import classNames from 'classnames' import { View } from '@tarojs/components' import isEqual from 'react-fast-compare' import { ComponentDefaults } from '@/utils/typings' -import { usePropsValue } from '@/utils/use-props-value' +import { usePropsValue } from '@/hooks/use-props-value' import { PickerViewProps, PickerOptionItem, From b67dfcaf8df17d49095b64e1684435730940495d Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 16:09:03 +0800 Subject: [PATCH 18/24] fix: adjust the dependency package path --- src/packages/pickerview/pickerview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/pickerview/pickerview.tsx b/src/packages/pickerview/pickerview.tsx index 75d3bb3c14..0fef411bea 100644 --- a/src/packages/pickerview/pickerview.tsx +++ b/src/packages/pickerview/pickerview.tsx @@ -9,7 +9,7 @@ import React, { import classNames from 'classnames' import isEqual from 'react-fast-compare' import { ComponentDefaults } from '@/utils/typings' -import { usePropsValue } from '@/utils/use-props-value' +import { usePropsValue } from '@/hooks/use-props-value' import { PickerViewProps, PickerOptionItem, From 99055646d4468db60b5c82976c6f2cf16482eafd Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 16:18:02 +0800 Subject: [PATCH 19/24] fix: update doc props type --- src/packages/pickerview/doc.en-US.md | 6 +++--- src/packages/pickerview/doc.md | 6 +++--- src/packages/pickerview/doc.taro.md | 6 +++--- src/packages/pickerview/doc.zh-TW.md | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/packages/pickerview/doc.en-US.md b/src/packages/pickerview/doc.en-US.md index fb09497e26..42006732bd 100644 --- a/src/packages/pickerview/doc.en-US.md +++ b/src/packages/pickerview/doc.en-US.md @@ -64,9 +64,9 @@ import { PickerView } from '@nutui/nutui-react' | Property | Description | Type | Default | | --- | --- | --- | --- | -| options | Tabular data | `PickerOptionItem[][]` | `[]` | -| value | Selected value, controlled | `Array` | `[]` | -| defaultValue | Default value | `Array` | `[]` | +| options | Tabular data | `PickerOptions[]` | `[]` | +| value | Selected value, controlled | `PickerValue[]` | `[]` | +| defaultValue | Default value | `PickerValue[]` | `[]` | | threeDimensional | Whether to enable 3D effect | `boolean` | `true` | | duration | The duration of inertial rolling during rapid sliding, in ms | `string` \| `number` | `1000` | | onChange | Called when the value of each column changes | `({value, index, selectedOptions}) => void` | `-` | diff --git a/src/packages/pickerview/doc.md b/src/packages/pickerview/doc.md index 0ba9af80dc..ef3cb7dc18 100644 --- a/src/packages/pickerview/doc.md +++ b/src/packages/pickerview/doc.md @@ -64,9 +64,9 @@ import { PickerView } from '@nutui/nutui-react' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| options | 列表数据 | `PickerOptionItem[][]` | `[]` | -| value | 选中值,受控 | `Array` | `[]` | -| defaultValue | 默认选中 | `Array` | `[]` | +| options | 列表数据 | `PickerOptions[]` | `[]` | +| value | 选中值,受控 | `PickerValue[]` | `[]` | +| defaultValue | 默认选中 | `PickerValue[]` | `[]` | | threeDimensional | 是否开启3D效果 | `boolean` | `true` | | duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | | onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | diff --git a/src/packages/pickerview/doc.taro.md b/src/packages/pickerview/doc.taro.md index fe1a838aca..344fc9dee3 100644 --- a/src/packages/pickerview/doc.taro.md +++ b/src/packages/pickerview/doc.taro.md @@ -64,9 +64,9 @@ import { PickerView } from '@nutui/nutui-react-taro' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | -| options | 列表数据 | `PickerOptionItem[][]` | `[]` | -| value | 选中值,受控 | `Array` | `[]` | -| defaultValue | 默认选中 | `Array` | `[]` | +| options | 列表数据 | `PickerOptions[]` | `[]` | +| value | 选中值,受控 | `PickerValue[]` | `[]` | +| defaultValue | 默认选中 | `PickerValue[]` | `[]` | | threeDimensional | 是否开启3D效果 | `boolean` | `true` | | duration | 快速滑动时惯性滚动的时长,单位 ms | `string` \| `number` | `1000` | | onChange | 每一列值变更时调用 | `({value, index, selectedOptions}) => void` | `-` | diff --git a/src/packages/pickerview/doc.zh-TW.md b/src/packages/pickerview/doc.zh-TW.md index aecd67eaff..073f5b8e4f 100644 --- a/src/packages/pickerview/doc.zh-TW.md +++ b/src/packages/pickerview/doc.zh-TW.md @@ -64,9 +64,9 @@ import { PickerView } from '@nutui/nutui-react-taro' | 屬性 | 說明 | 類型 | 默認值 | | --- | --- | --- | --- | -| options | 列錶數據 | `PickerOptionItem[][]` | `[]` | -| value | 選中值,受控 | `Array` | `[]` | -| defaultValue | 默認選中 | `Array` | `[]` | +| options | 列錶數據 | `PickerOptions[]` | `[]` | +| value | 選中值,受控 | `PickerValue[]` | `[]` | +| defaultValue | 默認選中 | `PickerValue[]` | `[]` | | threeDimensional | 是否開啟3D效果 | `boolean` | `true` | | duration | 快速滑動時慣性滾動的時長,單位 ms | `string` \| `number` | `1000` | | onChange | 每一列值變更時調用 | `({value, index, selectedOptions}) => void` | `-` | From 0c448c8dc8b0bbee4bac7e01e63a3154aaf6ac6b Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 16:48:48 +0800 Subject: [PATCH 20/24] fix: adjust note --- src/packages/pickerview/pickerroller.taro.tsx | 38 ++++++------------ src/packages/pickerview/pickerroller.tsx | 39 ++++++------------- 2 files changed, 24 insertions(+), 53 deletions(-) diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index ab97d306ec..eb310acdb1 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -47,7 +47,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const transformY = useRef(0) const [scrollDistance, setScrollDistance] = useState(0) - // 获取 lineSpacing.current CSS变量 + // lineSpacing.current CSS variable useEffect(() => { const element = pickerRollerRef.current if (element && web()) { @@ -55,6 +55,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const currentLineSpacing = computedStyle.getPropertyValue( '--nutui-picker-item-height' ) + !!currentLineSpacing && (lineSpacing.current = parseFloat(currentLineSpacing)) } @@ -85,7 +86,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< const setMove = (move: number, type?: string, time?: number) => { let updateMove = move + transformY.current if (type === 'end') { - // 限定滚动距离 if (updateMove > 0) { updateMove = 0 } @@ -106,7 +106,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< let deg = 0 const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation - // picker 滚动的最大角度 const maxDeg = (options.length + 1) * rotation const minDeg = 0 @@ -122,7 +121,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) } - // 开始滚动 const touchStart = (event: React.TouchEvent) => { touch.start(event) setStartY(touch.deltaY.current) @@ -144,9 +142,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< if (!moving.current) return const move = touch.deltaY.current - startY const moveTime = Date.now() - startTime - // 区分是否为惯性滚动 if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { - // 惯性滚动 const distance = momentum(move, moveTime) setMove(distance, 'end', +duration) } else { @@ -157,12 +153,10 @@ const InternalPickerRoller: ForwardRefRenderFunction< }, 0) } - // 惯性滚动 距离 + // inertial rolling distance const momentum = (distance: number, duration: number) => { let nDistance = distance - // 惯性滚动的速度 const speed = Math.abs(nDistance / duration) - // 惯性滚动的距离 nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) return nDistance } @@ -170,21 +164,13 @@ const InternalPickerRoller: ForwardRefRenderFunction< const modifyStatus = (type?: boolean, val?: string | number) => { const value = val || props.value let index = -1 - if (value) { - options.some((item, idx) => { - if (item.value === value) { - index = idx - return true - } - return false - }) - } else { - options.forEach((item, i) => { - if (item.value === props.value) { - index = i - } - }) - } + options.some((item, idx) => { + if (item.value === value) { + index = idx + return true // Stop iterating once the match is found + } + return false + }) setCurrIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current @@ -192,13 +178,13 @@ const InternalPickerRoller: ForwardRefRenderFunction< setMove(-move) } - // 惯性滚动结束 + // stop inertial rolling const stopMomentum = () => { moving.current = false setTouchTime(0) setChooseValue(scrollDistance) } - // 阻止默认事件 + const preventDefault = ( event: React.TouchEvent, isStopPropagation?: boolean diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 75d443f5b4..0a2dbae31f 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -45,7 +45,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< const transformY = useRef(0) const [scrollDistance, setScrollDistance] = useState(0) - // 获取 lineSpacing.current CSS变量 + // lineSpacing.current CSS variable useEffect(() => { const element = pickerRollerRef.current if (element) { @@ -57,7 +57,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< !!currentLineSpacing && (lineSpacing.current = parseFloat(currentLineSpacing)) } - }, []) + }, [pickerRollerRef.current]) const isHidden = (index: number) => { if (index >= currIndex + 8 || index <= currIndex - 8) { @@ -84,7 +84,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< const setMove = (move: number, type?: string, time?: number) => { let updateMove = move + transformY.current if (type === 'end') { - // 限定滚动距离 if (updateMove > 0) { updateMove = 0 } @@ -105,7 +104,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< let deg = 0 const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation - // picker 滚动的最大角度 const maxDeg = (options.length + 1) * rotation const minDeg = 0 @@ -121,7 +119,6 @@ const InternalPickerRoller: ForwardRefRenderFunction< onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) } - // 开始滚动 const touchStart = (event: React.TouchEvent) => { touch.start(event) setStartY(touch.deltaY.current) @@ -143,9 +140,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< if (!moving.current) return const move = touch.deltaY.current - startY const moveTime = Date.now() - startTime - // 区分是否为惯性滚动 if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { - // 惯性滚动 const distance = momentum(move, moveTime) setMove(distance, 'end', +duration) } else { @@ -156,12 +151,10 @@ const InternalPickerRoller: ForwardRefRenderFunction< }, 0) } - // 惯性滚动 距离 + // inertial rolling distance const momentum = (distance: number, duration: number) => { let nDistance = distance - // 惯性滚动的速度 const speed = Math.abs(nDistance / duration) - // 惯性滚动的距离 nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) return nDistance } @@ -169,21 +162,13 @@ const InternalPickerRoller: ForwardRefRenderFunction< const modifyStatus = (type?: boolean, val?: string | number) => { const value = val || props.value let index = -1 - if (value) { - options.some((item, idx) => { - if (item.value === value) { - index = idx - return true - } - return false - }) - } else { - options.forEach((item, i) => { - if (item.value === props.value) { - index = i - } - }) - } + options.some((item, idx) => { + if (item.value === value) { + index = idx + return true // Stop iterating once the match is found + } + return false + }) setCurrIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current @@ -191,13 +176,13 @@ const InternalPickerRoller: ForwardRefRenderFunction< setMove(-move) } - // 惯性滚动结束 + // stop inertial rolling const stopMomentum = () => { moving.current = false setTouchTime(0) setChooseValue(scrollDistance) } - // 阻止默认事件 + const preventDefault = ( event: React.TouchEvent, isStopPropagation?: boolean From 88d4f804bb6493cd39f106d1533cadc1084be807 Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 18:49:30 +0800 Subject: [PATCH 21/24] fix: optimize details --- src/packages/pickerview/hooks/useStyles.ts | 26 ++ src/packages/pickerview/pickerroller.taro.tsx | 234 ++++++--------- src/packages/pickerview/pickerroller.tsx | 267 ++++++++---------- src/packages/pickerview/pickerview.scss | 2 +- src/packages/pickerview/utils.ts | 4 + 5 files changed, 235 insertions(+), 298 deletions(-) create mode 100644 src/packages/pickerview/hooks/useStyles.ts create mode 100644 src/packages/pickerview/utils.ts diff --git a/src/packages/pickerview/hooks/useStyles.ts b/src/packages/pickerview/hooks/useStyles.ts new file mode 100644 index 0000000000..ecdb3e178e --- /dev/null +++ b/src/packages/pickerview/hooks/useStyles.ts @@ -0,0 +1,26 @@ +export const useStyles = ( + touchTime: number, + touchDeg: string, + scrollDistance: number, + lineSpacing: React.MutableRefObject, + rotation: number +) => { + const getTransitionStyle = (transformValue: string) => ({ + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: transformValue, + }) + + const touchRollerStyle = () => + getTransitionStyle(`rotate3d(1, 0, 0, ${touchDeg})`) + + const touchTiledStyle = () => + getTransitionStyle(`translate3d(0, ${scrollDistance}px, 0)`) + + const rollerStyle = (index: number) => ({ + transform: `rotate3d(1, 0, 0, ${-rotation * (index + 1)}deg) translate3d(0px, 0px, ${Math.round( + lineSpacing.current * 3.2 + )}px)`, + }) + + return { touchRollerStyle, touchTiledStyle, rollerStyle } +} diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index eb310acdb1..c8dfe41a1a 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -10,6 +10,9 @@ import { useTouch } from '@/hooks/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' import { web } from '@/utils/platform-taro' +import { preventDefault } from '@/utils' +import { momentum } from './utils' +import { useStyles } from './hooks/useStyles' const InternalPickerRoller: ForwardRefRenderFunction< { stopMomentum: () => void; moving: boolean }, @@ -21,190 +24,151 @@ const InternalPickerRoller: ForwardRefRenderFunction< threeDimensional = true, duration = 1000, onSelect, - renderLabel = (item: PickerOptionItem) => { - return item.label - }, + renderLabel = (item: PickerOptionItem) => item.label, } = props - const touch = useTouch() const DEFAULT_DURATION = 200 - // 触发惯性滑动条件: 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move, 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动 const INERTIA_TIME = 300 const INERTIA_DISTANCE = 15 - const [currIndex, setCurrIndex] = useState(1) + const ROTATION = 20 + const touch = useTouch() + const [currentIndex, setCurrentIndex] = useState(1) const lineSpacing = useRef(36) const [touchTime, setTouchTime] = useState(0) const [touchDeg, setTouchDeg] = useState('0deg') - const rotation = 20 - const moving = useRef(false) - + const isMoving = useRef(false) const rollerRef = useRef(null) const pickerRollerRef = useRef(null) - const [startTime, setStartTime] = useState(0) const [startY, setStartY] = useState(0) - const transformY = useRef(0) const [scrollDistance, setScrollDistance] = useState(0) - // lineSpacing.current CSS variable - useEffect(() => { - const element = pickerRollerRef.current - if (element && web()) { - const computedStyle = getComputedStyle(element) - const currentLineSpacing = computedStyle.getPropertyValue( - '--nutui-picker-item-height' - ) - - !!currentLineSpacing && - (lineSpacing.current = parseFloat(currentLineSpacing)) - } - }, [pickerRollerRef.current]) + const { touchRollerStyle, touchTiledStyle, rollerStyle } = useStyles( + touchTime, + touchDeg, + scrollDistance, + lineSpacing, + ROTATION + ) - const isHidden = (index: number) => { - if (index >= currIndex + 8 || index <= currIndex - 8) { - return true - } - return false - } + const isItemHidden = (index: number) => + index >= currentIndex + 8 || index <= currentIndex - 8 - const setTransform = ( + const applyTransform = ( type: string, deg: string, time = DEFAULT_DURATION, translateY = 0 ) => { - let nTime = time - if (type !== 'end') { - nTime = 0 - } - setTouchTime(nTime) + setTouchTime(type !== 'end' ? 0 : time) setTouchDeg(deg) setScrollDistance(translateY) } - const setMove = (move: number, type?: string, time?: number) => { - let updateMove = move + transformY.current + const handleMove = (move: number, type?: string, time?: number) => { + let updatedMove = move + transformY.current if (type === 'end') { - if (updateMove > 0) { - updateMove = 0 - } - if (updateMove < -(options.length - 1) * lineSpacing.current) { - updateMove = -(options.length - 1) * lineSpacing.current - } + updatedMove = Math.max( + Math.min(updatedMove, 0), + -(options.length - 1) * lineSpacing.current + ) - // 设置滚动距离为lineSpacing.current的倍数值 + // 滚动距离为lineSpacing.current的倍数值 const endMove = - Math.round(updateMove / lineSpacing.current) * lineSpacing.current - const deg = `${ - (Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * rotation - }deg` - - setTransform(type, deg, time, endMove) - setCurrIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) + Math.round(updatedMove / lineSpacing.current) * lineSpacing.current + const deg = `${(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * ROTATION}deg` + applyTransform(type, deg, time, endMove) + setCurrentIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) } else { - let deg = 0 - const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation - - const maxDeg = (options.length + 1) * rotation - const minDeg = 0 - - deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) - if (minDeg <= deg && deg < maxDeg) { - setTransform('', `${deg}deg`, undefined, updateMove) - setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) + const currentDeg = (-updatedMove / lineSpacing.current + 1) * ROTATION + const deg = Math.min( + Math.max(currentDeg, 0), + (options.length + 1) * ROTATION + ) + if (deg >= 0 && deg < (options.length + 1) * ROTATION) { + applyTransform('', `${deg}deg`, undefined, updatedMove) + setCurrentIndex( + Math.abs(Math.round(updatedMove / lineSpacing.current)) + 1 + ) } } } - const setChooseValue = (move: number) => { + const selectValue = (move: number) => { onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) } - const touchStart = (event: React.TouchEvent) => { + const handleTouchStart = (event: React.TouchEvent) => { touch.start(event) setStartY(touch.deltaY.current) setStartTime(Date.now()) transformY.current = scrollDistance } - const touchMove = (event: React.TouchEvent) => { + const handleTouchMove = (event: React.TouchEvent) => { touch.move(event) if ((touch as any).isVertical) { - moving.current = true + isMoving.current = true preventDefault(event, true) } const move = touch.deltaY.current - startY - setMove(move) + handleMove(move) } - const touchEnd = () => { - if (!moving.current) return + const handleTouchEnd = () => { + if (!isMoving.current) return const move = touch.deltaY.current - startY const moveTime = Date.now() - startTime if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { const distance = momentum(move, moveTime) - setMove(distance, 'end', +duration) + handleMove(distance, 'end', +duration) } else { - setMove(move, 'end') + handleMove(move, 'end') } setTimeout(() => { touch.reset() }, 0) } - // inertial rolling distance - const momentum = (distance: number, duration: number) => { - let nDistance = distance - const speed = Math.abs(nDistance / duration) - nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) - return nDistance - } - - const modifyStatus = (type?: boolean, val?: string | number) => { - const value = val || props.value - let index = -1 - options.some((item, idx) => { - if (item.value === value) { - index = idx - return true // Stop iterating once the match is found - } - return false - }) - - setCurrIndex(index === -1 ? 1 : index + 1) + const updateStatus = (shouldSelect?: boolean, value?: string | number) => { + const selectedValue = value || props.value + const index = options.findIndex((item) => item.value === selectedValue) + setCurrentIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current - type && setChooseValue(-move) - setMove(-move) + shouldSelect && selectValue(-move) + handleMove(-move) } - // stop inertial rolling - const stopMomentum = () => { - moving.current = false + const stopMomentumScroll = () => { + isMoving.current = false setTouchTime(0) - setChooseValue(scrollDistance) + selectValue(scrollDistance) } - const preventDefault = ( - event: React.TouchEvent, - isStopPropagation?: boolean - ) => { - event.preventDefault() - - if (isStopPropagation) { - event.stopPropagation() + // lineSpacing.current CSS variable + useEffect(() => { + const element = pickerRollerRef.current + if (element && web()) { + const computedStyle = getComputedStyle(element) + const currentLineSpacing = computedStyle.getPropertyValue( + '--nutui-picker-item-height' + ) + !!currentLineSpacing && + (lineSpacing.current = parseFloat(currentLineSpacing)) } - } + }, [pickerRollerRef.current]) useEffect(() => { + isMoving.current = false setScrollDistance(0) transformY.current = 0 - modifyStatus(false) + updateStatus(false) }, [options, props.value]) useImperativeHandle(ref, () => ({ - stopMomentum, - moving: moving.current, + stopMomentum: stopMomentumScroll, + moving: isMoving.current, })) useEffect(() => { @@ -213,73 +177,57 @@ const InternalPickerRoller: ForwardRefRenderFunction< : false pickerRollerRef.current?.addEventListener( 'touchstart', - touchStart, + handleTouchStart, eventOptions ) pickerRollerRef.current?.addEventListener( 'touchmove', - touchMove, + handleTouchMove, eventOptions ) pickerRollerRef.current?.addEventListener( 'touchend', - touchEnd, + handleTouchEnd, eventOptions ) return () => { pickerRollerRef.current?.removeEventListener( 'touchstart', - touchStart, + handleTouchStart, eventOptions ) pickerRollerRef.current?.removeEventListener( 'touchmove', - touchMove, + handleTouchMove, eventOptions ) pickerRollerRef.current?.removeEventListener( 'touchend', - touchEnd, + handleTouchEnd, eventOptions ) } - }, [pickerRollerRef.current, touchStart, touchMove, touchEnd]) - - const touchRollerStyle = () => { - return { - transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, - transform: `rotate3d(1, 0, 0, ${touchDeg})`, - } - } - const touchTileStyle = () => { - return { - transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, - transform: `translate3d(0, ${scrollDistance}px, 0)`, - } - } - - const rollerStyle = (index: number) => { - return { - transform: `rotate3d(1, 0, 0, ${ - -rotation * (index + 1) - }deg) translate3d(0px, 0px, ${Math.round(lineSpacing.current * 3.2)}px)`, - } - } + }, [ + pickerRollerRef.current, + handleTouchStart, + handleTouchMove, + handleTouchEnd, + ]) return ( {/* 3D 效果 */} {threeDimensional && options.map((item, index) => ( ))} - {/* 平铺 */} + {/* Tiled */} {!threeDimensional && options.map((item, index) => { return ( {renderLabel(item)} diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 0a2dbae31f..60ff26ada5 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -8,6 +8,9 @@ import React, { import { useTouch } from '@/hooks/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' +import { preventDefault } from '@/utils' +import { momentum } from './utils' +import { useStyles } from './hooks/useStyles' const InternalPickerRoller: ForwardRefRenderFunction< { stopMomentum: () => void; moving: boolean }, @@ -19,241 +22,199 @@ const InternalPickerRoller: ForwardRefRenderFunction< threeDimensional = true, duration = 1000, onSelect, - renderLabel = (item: PickerOptionItem) => { - return item.label - }, + renderLabel = (item: PickerOptionItem) => item.label, } = props - const touch = useTouch() const DEFAULT_DURATION = 200 - // 触发惯性滑动条件: 在手指离开屏幕时,如果和上一次 move 时的间隔小于 `MOMENTUM_TIME` 且 move, 距离大于 `MOMENTUM_DISTANCE` 时,执行惯性滑动 const INERTIA_TIME = 300 const INERTIA_DISTANCE = 15 - const [currIndex, setCurrIndex] = useState(1) + const ROTATION = 20 + const touch = useTouch() + const [currentIndex, setCurrentIndex] = useState(1) const lineSpacing = useRef(36) const [touchTime, setTouchTime] = useState(0) const [touchDeg, setTouchDeg] = useState('0deg') - const rotation = 20 - const moving = useRef(false) - + const isMoving = useRef(false) const rollerRef = useRef(null) const pickerRollerRef = useRef(null) - const [startTime, setStartTime] = useState(0) const [startY, setStartY] = useState(0) - const transformY = useRef(0) const [scrollDistance, setScrollDistance] = useState(0) - // lineSpacing.current CSS variable - useEffect(() => { - const element = pickerRollerRef.current - if (element) { - const computedStyle = getComputedStyle(element) - const currentLineSpacing = computedStyle.getPropertyValue( - '--nutui-picker-item-height' - ) - - !!currentLineSpacing && - (lineSpacing.current = parseFloat(currentLineSpacing)) - } - }, [pickerRollerRef.current]) + const { touchRollerStyle, touchTiledStyle, rollerStyle } = useStyles( + touchTime, + touchDeg, + scrollDistance, + lineSpacing, + ROTATION + ) - const isHidden = (index: number) => { - if (index >= currIndex + 8 || index <= currIndex - 8) { - return true - } - return false - } + const isItemHidden = (index: number) => + index >= currentIndex + 8 || index <= currentIndex - 8 - const setTransform = ( + const applyTransform = ( type: string, deg: string, time = DEFAULT_DURATION, translateY = 0 ) => { - let nTime = time - if (type !== 'end') { - nTime = 0 - } - setTouchTime(nTime) + setTouchTime(type !== 'end' ? 0 : time) setTouchDeg(deg) setScrollDistance(translateY) } - const setMove = (move: number, type?: string, time?: number) => { - let updateMove = move + transformY.current + const handleMove = (move: number, type?: string, time?: number) => { + let updatedMove = move + transformY.current if (type === 'end') { - if (updateMove > 0) { - updateMove = 0 - } - if (updateMove < -(options.length - 1) * lineSpacing.current) { - updateMove = -(options.length - 1) * lineSpacing.current - } + updatedMove = Math.max( + Math.min(updatedMove, 0), + -(options.length - 1) * lineSpacing.current + ) - // 设置滚动距离为lineSpacing.current的倍数值 + // 滚动距离为lineSpacing.current的倍数值 const endMove = - Math.round(updateMove / lineSpacing.current) * lineSpacing.current - const deg = `${ - (Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * rotation - }deg` - - setTransform(type, deg, time, endMove) - setCurrIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) + Math.round(updatedMove / lineSpacing.current) * lineSpacing.current + const deg = `${(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) * ROTATION}deg` + applyTransform(type, deg, time, endMove) + setCurrentIndex(Math.abs(Math.round(endMove / lineSpacing.current)) + 1) } else { - let deg = 0 - const currentDeg = (-updateMove / lineSpacing.current + 1) * rotation - - const maxDeg = (options.length + 1) * rotation - const minDeg = 0 - - deg = Math.min(Math.max(currentDeg, minDeg), maxDeg) - if (minDeg <= deg && deg < maxDeg) { - setTransform('', `${deg}deg`, undefined, updateMove) - setCurrIndex(Math.abs(Math.round(updateMove / lineSpacing.current)) + 1) + const currentDeg = (-updatedMove / lineSpacing.current + 1) * ROTATION + const deg = Math.min( + Math.max(currentDeg, 0), + (options.length + 1) * ROTATION + ) + if (deg >= 0 && deg < (options.length + 1) * ROTATION) { + applyTransform('', `${deg}deg`, undefined, updatedMove) + setCurrentIndex( + Math.abs(Math.round(updatedMove / lineSpacing.current)) + 1 + ) } } } - const setChooseValue = (move: number) => { + const selectValue = (move: number) => { onSelect?.(options?.[Math.round(-move / lineSpacing.current)], keyIndex) } - const touchStart = (event: React.TouchEvent) => { + const handleTouchStart = (event: React.TouchEvent) => { touch.start(event) setStartY(touch.deltaY.current) setStartTime(Date.now()) transformY.current = scrollDistance } - const touchMove = (event: React.TouchEvent) => { + const handleTouchMove = (event: React.TouchEvent) => { touch.move(event) if ((touch as any).isVertical) { - moving.current = true + isMoving.current = true preventDefault(event, true) } const move = touch.deltaY.current - startY - setMove(move) + handleMove(move) } - const touchEnd = () => { - if (!moving.current) return + const handleTouchEnd = () => { + if (!isMoving.current) return const move = touch.deltaY.current - startY const moveTime = Date.now() - startTime if (moveTime <= INERTIA_TIME && Math.abs(move) > INERTIA_DISTANCE) { const distance = momentum(move, moveTime) - setMove(distance, 'end', +duration) + handleMove(distance, 'end', +duration) } else { - setMove(move, 'end') + handleMove(move, 'end') } setTimeout(() => { touch.reset() }, 0) } - // inertial rolling distance - const momentum = (distance: number, duration: number) => { - let nDistance = distance - const speed = Math.abs(nDistance / duration) - nDistance = (speed / 0.003) * (nDistance < 0 ? -1 : 1) - return nDistance - } - - const modifyStatus = (type?: boolean, val?: string | number) => { - const value = val || props.value - let index = -1 - options.some((item, idx) => { - if (item.value === value) { - index = idx - return true // Stop iterating once the match is found - } - return false - }) - - setCurrIndex(index === -1 ? 1 : index + 1) + const updateStatus = (shouldSelect?: boolean, value?: string | number) => { + const selectedValue = value || props.value + const index = options.findIndex((item) => item.value === selectedValue) + setCurrentIndex(index === -1 ? 1 : index + 1) const move = index * lineSpacing.current - type && setChooseValue(-move) - setMove(-move) + shouldSelect && selectValue(-move) + handleMove(-move) } - // stop inertial rolling - const stopMomentum = () => { - moving.current = false + const stopMomentumScroll = () => { + isMoving.current = false setTouchTime(0) - setChooseValue(scrollDistance) + selectValue(scrollDistance) } - const preventDefault = ( - event: React.TouchEvent, - isStopPropagation?: boolean - ) => { - if (typeof event.cancelable !== 'boolean' || event.cancelable) { - event.preventDefault() - } - - if (isStopPropagation) { - event.stopPropagation() + // lineSpacing.current CSS variable + useEffect(() => { + const element = pickerRollerRef.current + if (element) { + const computedStyle = getComputedStyle(element) + const currentLineSpacing = computedStyle.getPropertyValue( + '--nutui-picker-item-height' + ) + !!currentLineSpacing && + (lineSpacing.current = parseFloat(currentLineSpacing)) } - } + }, [pickerRollerRef.current]) useEffect(() => { + isMoving.current = false setScrollDistance(0) transformY.current = 0 - modifyStatus(false) + updateStatus(false) }, [options, props.value]) useImperativeHandle(ref, () => ({ - stopMomentum, - moving: moving.current, + stopMomentum: stopMomentumScroll, + moving: isMoving.current, })) useEffect(() => { const options = passiveSupported ? { passive: false } : false - pickerRollerRef.current?.addEventListener('touchstart', touchStart, options) - pickerRollerRef.current?.addEventListener('touchmove', touchMove, options) - pickerRollerRef.current?.addEventListener('touchend', touchEnd, options) + pickerRollerRef.current?.addEventListener( + 'touchstart', + handleTouchStart, + options + ) + pickerRollerRef.current?.addEventListener( + 'touchmove', + handleTouchMove, + options + ) + pickerRollerRef.current?.addEventListener( + 'touchend', + handleTouchEnd, + options + ) return () => { - pickerRollerRef.current?.removeEventListener('touchstart', touchStart) - pickerRollerRef.current?.removeEventListener('touchmove', touchMove) - pickerRollerRef.current?.removeEventListener('touchend', touchEnd) - } - }, [pickerRollerRef.current, touchStart, touchMove, touchEnd]) - - const touchRollerStyle = () => { - return { - transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, - transform: `rotate3d(1, 0, 0, ${touchDeg})`, - } - } - const touchTileStyle = () => { - return { - transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, - transform: `translate3d(0, ${scrollDistance}px, 0)`, - } - } - - const rollerStyle = (index: number) => { - return { - transform: `rotate3d(1, 0, 0, ${ - -rotation * (index + 1) - }deg) translate3d(0px, 0px, ${Math.round(lineSpacing.current * 3.2)}px)`, + pickerRollerRef.current?.removeEventListener( + 'touchstart', + handleTouchStart + ) + pickerRollerRef.current?.removeEventListener('touchmove', handleTouchMove) + pickerRollerRef.current?.removeEventListener('touchend', handleTouchEnd) } - } + }, [ + pickerRollerRef.current, + handleTouchStart, + handleTouchMove, + handleTouchEnd, + ]) return (
- {/* 3D 效果 */} + {/* 3D */} {threeDimensional && options.map((item, index) => (
))} - {/* 平铺 */} + {/* Tiled */} {!threeDimensional && - options.map((item, index) => { - return ( -
- {renderLabel(item)} -
- ) - })} + options.map((item, index) => ( +
+ {renderLabel(item)} +
+ ))}
) diff --git a/src/packages/pickerview/pickerview.scss b/src/packages/pickerview/pickerview.scss index 595e61d224..1face81104 100644 --- a/src/packages/pickerview/pickerview.scss +++ b/src/packages/pickerview/pickerview.scss @@ -65,7 +65,7 @@ } &-roller-item, - &-roller-item-title { + &-roller-item-tiled { width: 100%; height: $picker-item-height; line-height: $picker-item-height; diff --git a/src/packages/pickerview/utils.ts b/src/packages/pickerview/utils.ts new file mode 100644 index 0000000000..cc022656da --- /dev/null +++ b/src/packages/pickerview/utils.ts @@ -0,0 +1,4 @@ +export const momentum = (distance: number, duration: number) => { + const speed = Math.abs(distance / duration) + return (speed / 0.003) * (distance < 0 ? -1 : 1) +} From 654d1e023198fa09428b3a402adbdebfec69071b Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 18:52:52 +0800 Subject: [PATCH 22/24] fix: update test --- .../__snapshots__/pickerview.spec.tsx.snap | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap index 10f61a642c..723cd2df1b 100644 --- a/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap +++ b/src/packages/pickerview/__test__/__snapshots__/pickerview.spec.tsx.snap @@ -180,47 +180,47 @@ exports[`should render tiled 1`] = ` style="transition: transform 0ms cubic-bezier(0.17, 0.89, 0.45, 1); transform: translate3d(0, 0px, 0);" >
南京市
无锡市
海北藏族自治区
北京市
连云港市
大庆市
绥化市
潍坊市
乌鲁木齐市
From d9628e7c560e29de33ff5bf859d8eda6a0cc00df Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 20:13:02 +0800 Subject: [PATCH 23/24] fix: path not compilable --- src/packages/pickerview/hooks/useStyles.ts | 26 ------------------ src/packages/pickerview/pickerroller.taro.tsx | 3 +-- src/packages/pickerview/pickerroller.tsx | 3 +-- src/packages/pickerview/utils.ts | 27 +++++++++++++++++++ 4 files changed, 29 insertions(+), 30 deletions(-) delete mode 100644 src/packages/pickerview/hooks/useStyles.ts diff --git a/src/packages/pickerview/hooks/useStyles.ts b/src/packages/pickerview/hooks/useStyles.ts deleted file mode 100644 index ecdb3e178e..0000000000 --- a/src/packages/pickerview/hooks/useStyles.ts +++ /dev/null @@ -1,26 +0,0 @@ -export const useStyles = ( - touchTime: number, - touchDeg: string, - scrollDistance: number, - lineSpacing: React.MutableRefObject, - rotation: number -) => { - const getTransitionStyle = (transformValue: string) => ({ - transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, - transform: transformValue, - }) - - const touchRollerStyle = () => - getTransitionStyle(`rotate3d(1, 0, 0, ${touchDeg})`) - - const touchTiledStyle = () => - getTransitionStyle(`translate3d(0, ${scrollDistance}px, 0)`) - - const rollerStyle = (index: number) => ({ - transform: `rotate3d(1, 0, 0, ${-rotation * (index + 1)}deg) translate3d(0px, 0px, ${Math.round( - lineSpacing.current * 3.2 - )}px)`, - }) - - return { touchRollerStyle, touchTiledStyle, rollerStyle } -} diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index c8dfe41a1a..d553df569f 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -11,8 +11,7 @@ import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' import { web } from '@/utils/platform-taro' import { preventDefault } from '@/utils' -import { momentum } from './utils' -import { useStyles } from './hooks/useStyles' +import { momentum, useStyles } from './utils' const InternalPickerRoller: ForwardRefRenderFunction< { stopMomentum: () => void; moving: boolean }, diff --git a/src/packages/pickerview/pickerroller.tsx b/src/packages/pickerview/pickerroller.tsx index 60ff26ada5..deb1512a76 100644 --- a/src/packages/pickerview/pickerroller.tsx +++ b/src/packages/pickerview/pickerroller.tsx @@ -9,8 +9,7 @@ import { useTouch } from '@/hooks/use-touch' import { passiveSupported } from '@/utils/supports-passive' import { PickerRollerProps, PickerOptionItem } from './types' import { preventDefault } from '@/utils' -import { momentum } from './utils' -import { useStyles } from './hooks/useStyles' +import { momentum, useStyles } from './utils' const InternalPickerRoller: ForwardRefRenderFunction< { stopMomentum: () => void; moving: boolean }, diff --git a/src/packages/pickerview/utils.ts b/src/packages/pickerview/utils.ts index cc022656da..2d948b7361 100644 --- a/src/packages/pickerview/utils.ts +++ b/src/packages/pickerview/utils.ts @@ -2,3 +2,30 @@ export const momentum = (distance: number, duration: number) => { const speed = Math.abs(distance / duration) return (speed / 0.003) * (distance < 0 ? -1 : 1) } + +export const useStyles = ( + touchTime: number, + touchDeg: string, + scrollDistance: number, + lineSpacing: React.MutableRefObject, + rotation: number +) => { + const getTransitionStyle = (transformValue: string) => ({ + transition: `transform ${touchTime}ms cubic-bezier(0.17, 0.89, 0.45, 1)`, + transform: transformValue, + }) + + const touchRollerStyle = () => + getTransitionStyle(`rotate3d(1, 0, 0, ${touchDeg})`) + + const touchTiledStyle = () => + getTransitionStyle(`translate3d(0, ${scrollDistance}px, 0)`) + + const rollerStyle = (index: number) => ({ + transform: `rotate3d(1, 0, 0, ${-rotation * (index + 1)}deg) translate3d(0px, 0px, ${Math.round( + lineSpacing.current * 3.2 + )}px)`, + }) + + return { touchRollerStyle, touchTiledStyle, rollerStyle } +} From 38296d379b36162ebec0a07afc36cc1f1b1f5f9d Mon Sep 17 00:00:00 2001 From: songchenglin3 <353833373@qq.com> Date: Thu, 13 Feb 2025 20:21:40 +0800 Subject: [PATCH 24/24] fix: key error --- src/packages/pickerview/pickerroller.taro.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packages/pickerview/pickerroller.taro.tsx b/src/packages/pickerview/pickerroller.taro.tsx index d553df569f..10c38d145f 100644 --- a/src/packages/pickerview/pickerroller.taro.tsx +++ b/src/packages/pickerview/pickerroller.taro.tsx @@ -240,7 +240,7 @@ const InternalPickerRoller: ForwardRefRenderFunction< return ( {renderLabel(item)}