Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions migrate-from-v2.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,16 @@ plugins: [

- `offset` 重命名为 `indent`

#### SideBar

- 新增SideBar组件
- 支持属性value,用于当前激活的`item`的key
- 支持属性defaultValue, 表示未设置value时,`item`的key的默认值
- 支持属性contentDuration, 用于内容滚动动画时长
- 支持属性sidebarDuration, 用于侧栏滚动动画时长
- 支持属性onClick, 点击标签时触发
- 支持属性onChange, 当前激活的标签改变时触发

#### Tabbar

- `unactiveColor` 重命名为 `inactiveColor`
Expand Down Expand Up @@ -597,7 +607,7 @@ plugins: [
- 移除 `isAsync`,通过 `checked`实现
- 移除 `activeColor` ,通过css变量`--nutui-switch-open-background-color`实现
- 移除 `inactiveColor`,通过css变量`--nutui-switch-close-background-color`实现
- `activeText 属性类型更改为 `ReactNode`
- `activeText` 属性类型更改为`ReactNode`
- `inactiveText` 属性类型更改为 `ReactNode`

#### Toast
Expand Down Expand Up @@ -780,7 +790,7 @@ plugins: [
- 移除 `pageContent`,通过 indicator 实现
- `autoplay` 重命名为 `autoplay`
- `initPage` 重命名为 `defaultValue`
- `paginationVisible` 重命名为 `indicator`,类型改为` ReactNode`
- `paginationVisible` 重命名为 `indicator`,类型改为`ReactNode`
- `isPreventDefault` 重命名为 `preventDefault`
- `isStopPropagation` 重命名为 `stopPropagation`
- `isCenter` 重命名为 `center`
Expand Down
24 changes: 24 additions & 0 deletions src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,30 @@
"taro": true,
"author": "hx"
},
{
"version": "3.0.0",
"name": "SideBar",
"type": "component",
"cName": "侧边栏导航",
"desc": "用于侧边内容选择和切换",
"sort": 10,
"show": true,
"taro": true,
"author": "Alex.hxy",
"v15": true
},
{
"version": "3.0.0",
"name": "SideBarItem",
"type": "component",
"cName": "侧边栏导航子组件",
"desc": "用于侧边内容选择和切换",
"sort": 10,
"show": false,
"taro": true,
"author": "Alex.hxy",
"v15": true
},
{
"version": "2.0.0",
"name": "SideNavBarItem",
Expand Down
70 changes: 70 additions & 0 deletions src/packages/sidebar/_test_/sidebar.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import * as React from 'react'
import { fireEvent, render } from '@testing-library/react'
import '@testing-library/jest-dom'
import { SideBar } from '../sidebar'

const list = Array.from(new Array(3).keys())

test('should render defaultValue correctly', async () => {
const { container } = render(
<SideBar style={{ height: 300 }} value="0">
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`}>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
)
const item = container.querySelectorAll('.nut-sidebar-titles-item')[0]
expect(item).toHaveClass('nut-sidebar-titles-item-active')
})

test('should choose and scroll to the right option', async () => {
const onChange = vi.fn()
const { container } = render(
<SideBar style={{ height: 300 }} value="0" onChange={onChange}>
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`}>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
)
const items = container.querySelectorAll('.nut-sidebar-titles-item')
fireEvent.click(items[1])
expect(onChange).toHaveBeenCalledWith(1)
})
test('disabled option', async () => {
const onChange = vi.fn()
const { container } = render(
<SideBar style={{ height: 300 }} value="0" onChange={onChange}>
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`} disabled>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
)
const items = container.querySelectorAll('.nut-sidebar-titles-item')
fireEvent.click(items[1])
expect(onChange).not.toHaveBeenCalled()
})
test('matchByValue', async () => {
const list1 = [
{ value: 'a', title: 'Opt a Opt a Opt a Opt a' },
{ value: 'b', title: 'Opt b' },
{ value: 'c', title: 'Opt c' },
]
const onChange = vi.fn()
const { container } = render(
<SideBar style={{ height: 300 }} value="b" onChange={onChange}>
{list1.map((item) => (
<SideBar.Item key={item.value} title={item.title} value={item.value}>
Content {item.value}
</SideBar.Item>
))}
</SideBar>
)
const items = container.querySelectorAll('.nut-sidebar-titles-item')
expect(items[1]).toHaveClass('nut-sidebar-titles-item-active')
})
56 changes: 56 additions & 0 deletions src/packages/sidebar/demo.taro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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'
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 TabsDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
basic: '基础用法',
disabled: '禁用选项',
matchByValue: '根据value匹配',
multiTitle: '多个标题',
setDuration: '设置滚动动画时长',
padding: '内容区域留白边距',
},
'en-US': {
basic: 'Basic Usage',
disabled: 'Disabled',
matchByValue: 'Match By Value',
multiTitle: 'Multiple Titles',
setDuration: 'Set Scroll Animation Duration',
padding: 'Set Content Padding',
},
})

return (
<>
<Header />
<ScrollView
className={`demo ${Taro.getEnv() === 'WEB' ? 'web full' : ''}`}
>
<View className="h2">{translated.basic}</View>
<Demo1 />
<View className="h2">{translated.disabled}</View>
<Demo2 />
<View className="h2">{translated.matchByValue}</View>
<Demo3 />
<View className="h2">{translated.multiTitle}</View>
<Demo4 />
<View className="h2">{translated.setDuration}</View>
<Demo5 />
<View className="h2">{translated.padding}</View>
<Demo6 />
</ScrollView>
</>
)
}

export default TabsDemo
50 changes: 50 additions & 0 deletions src/packages/sidebar/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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'
import Demo5 from './demos/h5/demo5'
import Demo6 from './demos/h5/demo6'

const SideNavBarDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
basic: '基础用法',
disabled: '禁用选项',
matchByValue: '根据value匹配',
multiTitle: '多个标题',
setDuration: '设置滚动动画时长',
padding: '内容区域留白边距',
},
'en-US': {
basic: 'Basic Usage',
disabled: 'Disabled',
matchByValue: 'Match By Value',
multiTitle: 'Multiple Titles',
setDuration: 'Set Scroll Animation Duration',
padding: 'Set Content Padding',
},
})

return (
<>
<div className="demo">
<h2>{translated.basic}</h2>
<Demo1 />
<h2>{translated.disabled}</h2>
<Demo2 />
<h2>{translated.matchByValue}</h2>
<Demo3 />
<h2>{translated.multiTitle}</h2>
<Demo4 />
<h2>{translated.setDuration}</h2>
<Demo5 />
<h2>{translated.padding}</h2>
<Demo6 />
</div>
</>
)
}

export default SideNavBarDemo
25 changes: 25 additions & 0 deletions src/packages/sidebar/demos/h5/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useState } from 'react'
import { SideBar } from '@nutui/nutui-react'

const Demo1 = () => {
const [value, setValue] = useState<number | string>('0')
const list = Array.from(new Array(3).keys())
return (
<>
<SideBar
style={{ height: 300 }}
value={value}
onChange={(value) => {
setValue(value)
}}
>
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`}>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
</>
)
}
export default Demo1
22 changes: 22 additions & 0 deletions src/packages/sidebar/demos/h5/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { useState } from 'react'
import { SideBar } from '@nutui/nutui-react'

const Demo2 = () => {
const [value, setValue] = useState<number | string>('0')
return (
<>
<SideBar
style={{ height: 300 }}
value={value}
onChange={(value) => {
setValue(value)
}}
>
<SideBar.Item title="Opt 1">Content 1</SideBar.Item>
<SideBar.Item title="Opt 2">Content 2</SideBar.Item>
<SideBar.Item title="Opt 3" disabled />
</SideBar>
</>
)
}
export default Demo2
28 changes: 28 additions & 0 deletions src/packages/sidebar/demos/h5/demo3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from 'react'
import { SideBar } from '@nutui/nutui-react'

const Demo3 = () => {
const [value, setValue] = useState<number | string>('b')
return (
<>
<SideBar
style={{ height: 300 }}
value={value}
onChange={(value) => {
setValue(value)
}}
>
<SideBar.Item title="Opt 1" value="a">
Content 1
</SideBar.Item>
<SideBar.Item title="Opt 2" value="b">
Content 2
</SideBar.Item>
<SideBar.Item title="Opt 3" value="c">
Content 3
</SideBar.Item>
</SideBar>
</>
)
}
export default Demo3
25 changes: 25 additions & 0 deletions src/packages/sidebar/demos/h5/demo4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React, { useState } from 'react'
import { SideBar } from '@nutui/nutui-react'

const Demo4 = () => {
const [value, setValue] = useState<number | string>('0')
const list = Array.from(new Array(20).keys())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议优化大列表渲染性能

当列表项较多时,建议使用虚拟列表来优化性能。可以考虑使用 react-windowreact-virtualized 库。

另外,建议将列表数据抽离到组件外部:

+const SIDEBAR_ITEMS = Array.from(new Array(20).keys()).map((item) => ({
+  key: item,
+  title: `Opt ${item + 1}`,
+  content: `Content ${item + 1}`,
+}))

const Demo4 = () => {
  const [value, setValue] = useState<string>('0')
-  const list = Array.from(new Array(20).keys())

Also applies to: 16-20

return (
<>
<SideBar
style={{ height: 300 }}
value={value}
onChange={(value) => {
setValue(value)
}}
>
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`}>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
</>
)
}
export default Demo4
27 changes: 27 additions & 0 deletions src/packages/sidebar/demos/h5/demo5.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { useState } from 'react'
import { SideBar } from '@nutui/nutui-react'

const Demo5 = () => {
const [value, setValue] = useState<number | string>('0')
const list = Array.from(new Array(20).keys())
return (
<>
<SideBar
style={{ height: 300 }}
value={value}
contentDuration={500}
sidebarDuration={300}
onChange={(value) => {
setValue(value)
}}
>
{list.map((item) => (
<SideBar.Item key={item} title={`Opt ${item + 1}`}>
Content {item + 1}
</SideBar.Item>
))}
</SideBar>
</>
)
}
export default Demo5
Loading
Loading