Skip to content

Commit 3dbc7bc

Browse files
authored
feat(Popup): 新增弹层可上下滑动 (#3340)
* feat: 支持popup 高度可以伸缩 * feat: 适配小程序 * feat: 修改文档 * fix: 默认值不需要,走样式 * feat: 增加使用的限制条件 * docs: 增加文档 * fix: 适配鸿蒙,初始值重置修改 * test: 添加单测 * fix: 增加h5 的初始值 * fix: 增加高度下限约束 * test: fix 单测
1 parent b8c3fd2 commit 3dbc7bc

File tree

16 files changed

+423
-72
lines changed

16 files changed

+423
-72
lines changed

src/packages/popup/__tests__/popup.spec.tsx

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,22 @@
11
import * as React from 'react'
2-
import { render, fireEvent } from '@testing-library/react'
2+
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
33
import '@testing-library/jest-dom'
44
import { Popup } from '../popup'
55

6-
test('should change z-index when using z-index prop', () => {
7-
const { container } = render(<Popup visible zIndex={99} />)
8-
const element = container.querySelector('.nut-popup') as HTMLElement
9-
expect(element.style.zIndex).toEqual('99')
6+
test('renders without crashing', () => {
7+
render(<Popup visible>Test Content</Popup>)
8+
expect(screen.getByText('Test Content')).toBeInTheDocument()
109
})
1110

12-
test('prop overlay-class test', async () => {
13-
const { container } = render(<Popup visible overlayClassName="testclas" />)
14-
const overlay = container.querySelector('.nut-overlay') as HTMLElement
15-
expect(overlay).toHaveClass('testclas')
16-
})
11+
test('opens and closes correctly', () => {
12+
const { rerender } = render(<Popup visible={false}>Test Content</Popup>)
1713

18-
test('prop overlay-style test', async () => {
19-
const { container } = render(
20-
<Popup visible overlayStyle={{ color: 'red' }} />
21-
)
22-
const overlay = container.querySelector('.nut-overlay') as HTMLElement
23-
expect(overlay).toHaveStyle({
24-
color: 'red',
25-
})
26-
})
14+
// Initially, it should not be visible
15+
expect(screen.queryByText('Test Content')).not.toBeInTheDocument()
2716

28-
test('should lock scroll when showed', async () => {
29-
const { rerender } = render(<Popup visible={false} />)
30-
rerender(<Popup visible />)
31-
expect(document.body.classList.contains('nut-overflow-hidden')).toBe(true)
17+
// Rerender with visible true
18+
rerender(<Popup visible>Test Content</Popup>)
19+
expect(screen.getByText('Test Content')).toBeInTheDocument()
3220
})
3321

3422
test('should not render overlay when overlay prop is false', () => {
@@ -91,6 +79,14 @@ test('pop description', () => {
9179
expect(title).toHaveTextContent('副标题')
9280
})
9381

82+
test('pop minHeight', () => {
83+
const { container } = render(
84+
<Popup minHeight="30%" visible position="bottom" />
85+
)
86+
const node = container.querySelector('.nut-popup') as HTMLElement
87+
expect(node).toHaveStyle({ minHeight: '30%' })
88+
})
89+
9490
test('should render close icon when using closeable prop', () => {
9591
const { container } = render(<Popup visible closeable />)
9692
const closeIcon = container.querySelector(
@@ -145,11 +141,15 @@ test('event click-title-right icon and keep overlay test ', () => {
145141
expect(overlay2).toBeNull()
146142
})
147143

148-
test('should emit open event when prop visible is set to true', () => {
144+
test('should emit open event when prop visible is set to true', async () => {
149145
const onOpen = vi.fn()
150146
const { rerender } = render(<Popup visible={false} onOpen={onOpen} />)
151-
rerender(<Popup visible onOpen={onOpen} />)
152-
expect(onOpen).toBeCalled()
147+
rerender(
148+
<Popup visible onOpen={onOpen} closeOnOverlayClick>
149+
test
150+
</Popup>
151+
)
152+
await waitFor(() => expect(onOpen).toBeCalled())
153153
})
154154

155155
test('event click-overlay test', async () => {
@@ -171,3 +171,38 @@ test('pop destroyOnClose', () => {
171171
fireEvent.click(overlay)
172172
expect(onClose).toBeCalled()
173173
})
174+
175+
test('handles touch events correctly', () => {
176+
const handleTouchStart = vi.fn()
177+
const handleTouchMove = vi.fn()
178+
const handleTouchEnd = vi.fn()
179+
180+
render(
181+
<Popup
182+
visible
183+
resizable
184+
position="bottom"
185+
// minHeight="400px"
186+
onTouchStart={handleTouchStart}
187+
onTouchMove={handleTouchMove}
188+
onTouchEnd={handleTouchEnd}
189+
>
190+
Test Content
191+
</Popup>
192+
)
193+
194+
const popup = document.body.querySelector('.nut-popup') as HTMLElement
195+
196+
// Simulate touch events
197+
fireEvent.touchStart(popup, { touches: [{ pageY: 400 }] })
198+
expect(handleTouchStart).toHaveBeenCalled()
199+
200+
fireEvent.touchMove(popup, { touches: [{ pageY: 50 }] })
201+
expect(handleTouchMove).toHaveBeenCalled()
202+
203+
fireEvent.touchMove(popup, { touches: [{ pageY: 450 }] })
204+
expect(handleTouchMove).toHaveBeenCalled()
205+
206+
fireEvent.touchEnd(popup)
207+
expect(handleTouchEnd).toHaveBeenCalled()
208+
})

src/packages/popup/demos/h5/demo1.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,53 @@ import React, { useState } from 'react'
22
import { Popup, Cell } from '@nutui/nutui-react'
33

44
const Demo = () => {
5-
const [showIcon, setShowIcon] = useState(false)
5+
const [showPopup, setShowPopup] = useState(false)
6+
const [showPopupResiable, setShowPopupResiable] = useState(false)
67

78
return (
89
<>
910
<Cell
1011
title="基础弹框"
1112
onClick={() => {
12-
setShowIcon(true)
13+
setShowPopup(true)
14+
}}
15+
/>
16+
<Cell
17+
title="基础弹框:可上下滑动"
18+
onClick={() => {
19+
setShowPopupResiable(true)
1320
}}
1421
/>
1522
<Popup
1623
closeable
17-
visible={showIcon}
24+
visible={showPopup}
1825
title="标题"
1926
description="这里是副标题这是副标题"
2027
position="bottom"
2128
onClose={() => {
22-
setShowIcon(false)
29+
setShowPopup(false)
30+
}}
31+
/>
32+
<Popup
33+
closeable
34+
resizable
35+
minHeight="10%"
36+
style={{ height: '60%' }}
37+
visible={showPopupResiable}
38+
title="上下滑动"
39+
description="弹层区域滑动起来"
40+
position="bottom"
41+
onClose={() => {
42+
setShowPopupResiable(false)
43+
}}
44+
onTouchMove={(height, e, direction) => {
45+
console.log('onTouchMove', height, e, direction)
46+
}}
47+
onTouchStart={(height, e) => {
48+
console.log('onTouchStart', height, e)
49+
}}
50+
onTouchEnd={(height, e) => {
51+
console.log('onTouchEnd', height, e)
2352
}}
2453
/>
2554
</>

src/packages/popup/demos/h5/demo2.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const Demo2 = () => {
4444
visible={showTop}
4545
destroyOnClose
4646
position="top"
47+
resizable
4748
onClose={() => {
4849
setShowTop(false)
4950
}}

src/packages/popup/demos/taro/demo1.tsx

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,53 @@ import React, { useState } from 'react'
22
import { Popup, Cell } from '@nutui/nutui-react-taro'
33

44
const Demo = () => {
5-
const [showIcon, setShowIcon] = useState(false)
5+
const [showPopup, setShowPopup] = useState(false)
6+
const [showPopupResiable, setShowPopupResiable] = useState(false)
67

78
return (
89
<>
910
<Cell
1011
title="基础弹框"
1112
onClick={() => {
12-
setShowIcon(true)
13+
setShowPopup(true)
14+
}}
15+
/>
16+
<Cell
17+
title="基础弹框:可上下滑动"
18+
onClick={() => {
19+
setShowPopupResiable(true)
1320
}}
1421
/>
1522
<Popup
1623
closeable
17-
visible={showIcon}
24+
visible={showPopup}
1825
title="标题"
1926
description="这里是副标题这是副标题"
2027
position="bottom"
2128
onClose={() => {
22-
setShowIcon(false)
29+
setShowPopup(false)
30+
}}
31+
/>
32+
<Popup
33+
closeable
34+
resizable
35+
minHeight="10%"
36+
style={{ height: '60%' }}
37+
visible={showPopupResiable}
38+
title="上下滑动"
39+
description="弹层区域滑动起来"
40+
position="bottom"
41+
onClose={() => {
42+
setShowPopupResiable(false)
43+
}}
44+
onTouchMove={(height, e, direction) => {
45+
console.log('onTouchMove', height, e, direction)
46+
}}
47+
onTouchStart={(height, e) => {
48+
console.log('onTouchStart', height, e)
49+
}}
50+
onTouchEnd={(height, e) => {
51+
console.log('onTouchEnd', height, e)
2352
}}
2453
/>
2554
</>

src/packages/popup/doc.en-US.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,24 @@ import { Popup } from '@nutui/nutui-react'
8787
| closeable | whether to show the close button | `boolean` | `false` |
8888
| closeIconPosition | close button position | `top-left` \| `top-right` \| `bottom-left` \| `bottom-right` | `top-right` |
8989
| closeIcon | Custom Icon | `ReactNode` | `close` |
90+
| resizable | Enable vertical resizing of the popup | `boolean` | `false` |
91+
| minHeight | Minimum height of the popup | `string` | `26%` |
9092
| left | The left of title | `ReactNode` | `-` |
9193
| title | The center of title | `ReactNode` | `-` |
9294
| description | The subtitle/description | `ReactNode` | `-` |
9395
| destroyOnClose | Whether to close after the component is destroyed | `boolean` | `false` |
9496
| round | Whether to show rounded corners | `boolean` | `false` |
9597
| portal | Mount the specified node | `HTMLElement` \| `(() => HTMLElement)` \| null` | `null` |
98+
| afterShow | afterShow from `Overlay`, Fired when the mask opening animation ends | `event: HTMLElement` | `-` |
99+
| afterClose | afterClose from `Overlay`, Fired when the mask closing animation ends | `event: HTMLElement` | `-` |
96100
| onClick | Triggered when the popup is clicked | `event: MouseEvent` | `-` |
97101
| onCloseIconClick | Fired when the close icon is clicked | `event: MouseEvent` | `-` |
98102
| onOpen | Triggered when the popup is opened | `-` | `-` |
99103
| onClose | Fired when the popup is closed | `-` | `-` |
100-
| afterShow | afterShow from `Overlay`, Fired when the mask opening animation ends | `event: HTMLElement` | `-` |
101-
| afterClose | afterClose from `Overlay`, Fired when the mask closing animation ends | `event: HTMLElement` | `-` |
102104
| onOverlayClick | Click on the mask to trigger | `event: MouseEvent` | `-` |
105+
| onTouchStart | triggered when starting to touch | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
106+
| onTouchMove | triggered while moving | `(height: number, event: TouchEvent<HTMLDivElement>, direction: 'up' \| 'down') => void` | `-` |
107+
| onTouchEnd | triggered when finishing to touch | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
103108

104109
## Theming
105110

src/packages/popup/doc.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,24 @@ import { Popup } from '@nutui/nutui-react'
8787
| closeable | 是否显示关闭按钮 | `boolean` | `false` |
8888
| closeIconPosition | 关闭按钮位置 | `top-left` \| `top-right` \| `bottom-left` \| `bottom-right` | `top-right` |
8989
| closeIcon | 自定义 Icon | `ReactNode` | `close` |
90+
| resizable | 上下滑动调整高度,当前只支持从底部弹出 | `boolean` | `false` |
91+
| minHeight | 设置最小高度 | `string` | `26%` |
9092
| left | 标题左侧部分 | `ReactNode` | `-` |
9193
| title | 标题中间部分 | `ReactNode` | `-` |
9294
| description | 子标题/描述部分 | `ReactNode` | `-` |
9395
| destroyOnClose | 组件不可见时,卸载内容 | `boolean` | `false` |
9496
| round | 是否显示圆角 | `boolean` | `false` |
9597
| portal | 指定节点挂载 | `HTMLElement` \| `(() => HTMLElement)` \| null` | `null` |
98+
| afterShow | 继承于`Overlay`, 遮罩打开动画结束时触发 | `event: HTMLElement` | `-` |
99+
| afterClose | 继承于`Overlay`, 遮罩关闭动画结束时触发 | `event: HTMLElement` | `-` |
96100
| onClick | 点击弹框时触发 | `event: MouseEvent` | `-` |
97101
| onCloseIconClick | 点击关闭图标时触发 | `event: MouseEvent` | `-` |
98102
| onOpen | 打开弹框时触发 | `-` | `-` |
99103
| onClose | 关闭弹框时触发 | `-` | `-` |
100-
| afterShow | 继承于`Overlay`, 遮罩打开动画结束时触发 | `event: HTMLElement` | `-` |
101-
| afterClose | 继承于`Overlay`, 遮罩关闭动画结束时触发 | `event: HTMLElement` | `-` |
102104
| onOverlayClick | 点击遮罩触发 | `event: MouseEvent` | `-` |
105+
| onTouchStart | 开始触碰时触发 | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
106+
| onTouchMove | 滑动时触发 | `(height: number, event: TouchEvent<HTMLDivElement>, direction: 'up' \| 'down') => void` | `-` |
107+
| onTouchEnd | 结束触碰时触发 | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
103108

104109
## 主题定制
105110

src/packages/popup/doc.taro.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,24 @@ import { Popup } from '@nutui/nutui-react-taro'
9797
| closeable | 是否显示关闭按钮 | `boolean` | `false` |
9898
| closeIconPosition | 关闭按钮位置 | `top-left` \| `top-right` \| `bottom-left` \| `bottom-right` | `top-right` |
9999
| closeIcon | 自定义 Icon | `ReactNode` | `close` |
100+
| resizable | 上下滑动调整高度,当前只支持从底部弹出 | `boolean` | `false` |
101+
| minHeight | 设置最小高度 | `string` | `26%` |
100102
| left | 标题左侧部分 | `ReactNode` | `-` |
101103
| title | 标题中间部分 | `ReactNode` | `-` |
102104
| description | 子标题/描述部分 | `ReactNode` | `-` |
103105
| destroyOnClose | 组件不可见时,卸载内容 | `boolean` | `false` |
104106
| round | 是否显示圆角 | `boolean` | `false` |
105107
| portal | 指定节点挂载 | ``HTMLElement` \| `(() => HTMLElement)` \| null`` | `null` |
108+
| afterShow | 继承于`Overlay`, 遮罩打开动画结束时触发 | `event: HTMLElement` | `-` |
109+
| afterClose | 继承于`Overlay`, 遮罩关闭动画结束时触发 | `event: HTMLElement` | `-` |
106110
| onClick | 点击弹框时触发 | `event: MouseEvent` | `-` |
107111
| onCloseIconClick | 点击关闭图标时触发 | `event: MouseEvent` | `-` |
108112
| onOpen | 打开弹框时触发 | `-` | `-` |
109113
| onClose | 关闭弹框时触发 | `-` | `-` |
110-
| afterShow | 继承于`Overlay`, 遮罩打开动画结束时触发 | `event: HTMLElement` | `-` |
111-
| afterClose | 继承于`Overlay`, 遮罩关闭动画结束时触发 | `event: HTMLElement` | `-` |
112114
| onOverlayClick | 点击遮罩触发 | `event: MouseEvent` | `-` |
115+
| onTouchStart | 开始触碰时触发 | `(height: number, event: ITouchEvent) => void` | `-` |
116+
| onTouchMove | 滑动时触发 | `(height: number, event: ITouchEvent, direction: 'up' \| 'down') => void` | `-` |
117+
| onTouchEnd | 结束触碰时触发 | `(height: number, event: ITouchEvent) => void` | `-` |
113118

114119
## 主题定制
115120

src/packages/popup/doc.zh-TW.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,24 @@ import { Popup } from '@nutui/nutui-react'
8787
| closeable | 是否顯示關閉按鈕 | `boolean` | `false` |
8888
| closeIconPosition | 關閉按鈕位置(top-left,top-right,bottom-left,bottom-right) | `string` | `top-right` |
8989
| closeIcon | 自定義 Icon | `ReactNode` | `close` |
90+
| resizable | 上下滑動調整高度,目前只支援從底部彈出 | `boolean` | `false` |
91+
| minHeight | 設定最小高度 | `string` | `26%` |
9092
| left | 标题左侧部分 | `ReactNode` | `-` |
9193
| title | 标题中间部分 | `ReactNode` | `-` |
9294
| description | 子標題/描述部分 | `ReactNode` | `-` |
9395
| destroyOnClose | 组件不可见时,卸载内容 | `boolean` | `false` |
9496
| round | 是否顯示圓角 | `boolean` | `false` |
9597
| portal | 指定節點掛載 | `HTMLElement` \| `(() => HTMLElement)` \| null` | `null` |
98+
| afterShow | 继承于`Overlay`, 遮罩打開動畫結束時觸發 | `event: HTMLElement` | `-` |
99+
| afterClose | 继承于`Overlay`, 遮罩關閉動畫結束時觸發 | `event: HTMLElement` | `-` |
96100
| onClick | 點擊彈框時觸發 | `event: MouseEvent` | `-` |
97101
| onCloseIconClick | 點擊關閉圖標時觸發 | `event: MouseEvent` | `-` |
98102
| onOpen | 打開彈框時觸發 | `-` | `-` |
99103
| onClose | 關閉彈框時觸發 | `-` | `-` |
100-
| afterShow | 继承于`Overlay`, 遮罩打開動畫結束時觸發 | `event: HTMLElement` | `-` |
101-
| afterClose | 继承于`Overlay`, 遮罩關閉動畫結束時觸發 | `event: HTMLElement` | `-` |
102104
| onOverlayClick | 點擊遮罩觸發 | `event: MouseEvent` | `-` |
105+
| onTouchStart | 開始觸碰時觸發 | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
106+
| onTouchMove | 滑動時觸發 | `(height: number, event: TouchEvent<HTMLDivElement>, 'up' \| 'down') => void` | `-` |
107+
| onTouchEnd | 結束觸碰時觸發 | `(height: number, event: TouchEvent<HTMLDivElement>) => void` | `-` |
103108

104109
## 主題定制
105110

src/packages/popup/popup.scss

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,6 @@
9090
}
9191
}
9292

93-
&-bottom,
94-
&-top {
95-
max-height: 87%;
96-
}
97-
9893
&-bottom {
9994
bottom: 0;
10095
left: 0;

0 commit comments

Comments
 (0)