Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(segmented): v14 #2762

Merged
merged 4 commits into from
Dec 5, 2024
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
12 changes: 12 additions & 0 deletions src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1053,6 +1053,18 @@
"v14": true,
"author": "lkjh3214"
},
{
"version": "3.0.0",
"name": "Segmented",
"type": "component",
"cName": "分段选择器",
"desc": "用于带页面类型区分的内容型卡片切换,如主图视频、图文切换。",
"sort": 6,
"show": true,
"taro": true,
"v14": true,
"author": "lkjh3214"
},
oasis-cloud marked this conversation as resolved.
Show resolved Hide resolved
{
"version": "3.0.0",
"name": "Price",
Expand Down
47 changes: 47 additions & 0 deletions src/packages/segmented/__tests__/segmented.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react'
import { fireEvent, render } from '@testing-library/react'
import '@testing-library/jest-dom'
import { SegmentedItem, Segmented } from '@nutui/nutui-react'

const defaultOptions = ['Daily', 'Weekly', 'Monthly']
test('Simple Value Testing', () => {
const handleChange = vi.fn(() => {})
const { queryByText } = render(
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={handleChange}
/>
)

expect(queryByText('Weekly')).toBeInTheDocument()

const secondItem = queryByText('Weekly')
if (secondItem) {
fireEvent.click(secondItem)
}
expect(handleChange).toBeCalled()
})

test('Complex Value Testing', () => {
const defaultOptions: SegmentedItem[] = [
{
label: 'Apps',
value: 'Apps',
},
{
label: 'AfterSaleService',
value: 'AfterSaleService',
},
]
const handleChange = vi.fn(() => {})
const { container, queryByText } = render(
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={handleChange}
/>
)

expect(queryByText('AfterSaleService')).toBeInTheDocument()
})
Comment on lines +26 to +47
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

复杂值测试用例需要补充

当前的复杂值测试过于简单,建议增加以下测试场景:

  1. 交互测试
  2. 值变更验证
  3. 类型检查

建议按照以下方式重构测试:

 test('Complex Value Testing', () => {
   const defaultOptions: SegmentedItem[] = [
     {
       label: 'Apps',
       value: 'Apps',
     },
     {
       label: 'AfterSaleService',
       value: 'AfterSaleService',
     },
   ]
   const handleChange = vi.fn(() => {})
   const { container, queryByText } = render(
     <Segmented
       defaultValue={0}
       options={defaultOptions}
       onChange={handleChange}
     />
   )
 
   expect(queryByText('AfterSaleService')).toBeInTheDocument()
+  
+  // 验证初始选中状态
+  const firstItem = queryByText('Apps')
+  expect(firstItem?.parentElement).toHaveClass('nut-segmented-item-active')
+
+  // 测试交互
+  const secondItem = queryByText('AfterSaleService')
+  if (secondItem) {
+    fireEvent.click(secondItem)
+  }
+
+  // 验证回调和状态
+  expect(handleChange).toHaveBeenCalledWith(1, 'AfterSaleService')
+  expect(secondItem?.parentElement).toHaveClass('nut-segmented-item-active')
 })
+
+// 添加错误情况测试
+test('Invalid Props Testing', () => {
+  const invalidOptions = [{ invalid: 'structure' }]
+  expect(() => {
+    render(<Segmented options={invalidOptions} />)
+  }).toThrow()
+})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('Complex Value Testing', () => {
const defaultOptions: SegmentedItem[] = [
{
label: 'Apps',
value: 'Apps',
},
{
label: 'AfterSaleService',
value: 'AfterSaleService',
},
]
const handleChange = vi.fn(() => {})
const { container, queryByText } = render(
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={handleChange}
/>
)
expect(queryByText('AfterSaleService')).toBeInTheDocument()
})
test('Complex Value Testing', () => {
const defaultOptions: SegmentedItem[] = [
{
label: 'Apps',
value: 'Apps',
},
{
label: 'AfterSaleService',
value: 'AfterSaleService',
},
]
const handleChange = vi.fn(() => {})
const { container, queryByText } = render(
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={handleChange}
/>
)
expect(queryByText('AfterSaleService')).toBeInTheDocument()
// 验证初始选中状态
const firstItem = queryByText('Apps')
expect(firstItem?.parentElement).toHaveClass('nut-segmented-item-active')
// 测试交互
const secondItem = queryByText('AfterSaleService')
if (secondItem) {
fireEvent.click(secondItem)
}
// 验证回调和状态
expect(handleChange).toHaveBeenCalledWith(1, 'AfterSaleService')
expect(secondItem?.parentElement).toHaveClass('nut-segmented-item-active')
})
// 添加错误情况测试
test('Invalid Props Testing', () => {
const invalidOptions = [{ invalid: 'structure' }]
expect(() => {
render(<Segmented options={invalidOptions} />)
}).toThrow()
})

33 changes: 33 additions & 0 deletions src/packages/segmented/demo.taro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react'
import { ScrollView, View } from '@tarojs/components'
import Taro from '@tarojs/taro'
import Demo1 from './demos/taro/demo1'
import Demo2 from './demos/taro/demo2'
import Demo3 from './demos/taro/demo3'
import { useTranslate } from '@/sites/assets/locale/taro'
import Header from '@/sites/components/header'

const SegmentedDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
uncontrolled: '非受控',
controlled: '受控',
optionItems: '设置图标',
},
})
return (
<>
<Header />
<ScrollView className={`demo ${Taro.getEnv() === 'WEB' ? 'web' : ''}`}>
<View className="h2">{translated.uncontrolled}</View>
<Demo1 />
<View className="h2">{translated.controlled}</View>
<Demo2 />
<View className="h2">{translated.optionItems}</View>
<Demo3 />
</ScrollView>
</>
)
}

export default SegmentedDemo
27 changes: 27 additions & 0 deletions src/packages/segmented/demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import Demo1 from './demos/h5/demo1'
import Demo2 from './demos/h5/demo2'
import Demo3 from './demos/h5/demo3'
import { useTranslate } from '@/sites/assets/locale'

const SegmentedDemo = () => {
const [translated] = useTranslate({
'zh-CN': {
uncontrolled: '非受控',
controlled: '受控',
optionItems: '设置图标',
},
})
return (
<div className="demo">
<h2>{translated.uncontrolled}</h2>
<Demo1 />
<h2>{translated.controlled}</h2>
<Demo2 />
<h2>{translated.optionItems}</h2>
<Demo3 />
</div>
)
}

export default SegmentedDemo
19 changes: 19 additions & 0 deletions src/packages/segmented/demos/h5/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { Segmented, Cell } from '@nutui/nutui-react'

const defaultOptions = ['Daily', 'Weekly', 'Monthly']

const Demo1 = () => {
return (
<Cell>
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={(e) => {
console.log('onChange', e)
}}
/>
</Cell>
)
}
export default Demo1
20 changes: 20 additions & 0 deletions src/packages/segmented/demos/h5/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from 'react'
import { Segmented, Cell } from '@nutui/nutui-react'

const defaultOptions = ['Daily', 'Weekly', 'Monthly']

const Demo2 = () => {
const [value, setValue] = useState<string | number>(1)
oasis-cloud marked this conversation as resolved.
Show resolved Hide resolved
return (
<Cell>
<Segmented
value={value}
options={defaultOptions}
onChange={(val) => {
setValue(val)
}}
/>
</Cell>
)
}
export default Demo2
32 changes: 32 additions & 0 deletions src/packages/segmented/demos/h5/demo3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState } from 'react'
import { Segmented, SegmentedItem, Cell } from '@nutui/nutui-react'
import { AfterSaleService, Apps } from '@nutui/icons-react'

const defaultOptions: SegmentedItem[] = [
{
label: 'Apps',
value: 'Apps',
icon: <Apps />,
},
{
label: 'AfterSaleService',
value: 'AfterSaleService',
icon: <AfterSaleService />,
},
]

const Demo3 = () => {
const [value, setValue] = useState<string | number>('Apps')
return (
<Cell>
<Segmented
value={value}
options={defaultOptions}
onChange={(val) => {
setValue(val)
}}
/>
</Cell>
)
}
export default Demo3
19 changes: 19 additions & 0 deletions src/packages/segmented/demos/taro/demo1.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import { Segmented, Cell } from '@nutui/nutui-react-taro'

const defaultOptions = ['Daily', 'Weekly', 'Monthly']

const Demo1 = () => {
return (
<Cell>
<Segmented
defaultValue={0}
options={defaultOptions}
onChange={(e) => {
console.log('onChange', e)
}}
/>
</Cell>
)
}
export default Demo1
20 changes: 20 additions & 0 deletions src/packages/segmented/demos/taro/demo2.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from 'react'
import { Segmented, Cell } from '@nutui/nutui-react-taro'

const defaultOptions = ['Daily', 'Weekly', 'Monthly']

const Demo2 = () => {
const [value, setValue] = useState<string | number>(1)
oasis-cloud marked this conversation as resolved.
Show resolved Hide resolved
return (
<Cell>
<Segmented
value={value}
options={defaultOptions}
onChange={(val) => {
setValue(val)
}}
/>
</Cell>
)
}
export default Demo2
32 changes: 32 additions & 0 deletions src/packages/segmented/demos/taro/demo3.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState } from 'react'
import { Segmented, SegmentedItem, Cell } from '@nutui/nutui-react-taro'
import { AfterSaleService, Apps } from '@nutui/icons-react-taro'

const defaultOptions: SegmentedItem[] = [
{
label: 'Apps',
value: 'Apps',
icon: <Apps color="#ffffff" />,
},
{
label: 'AfterSaleService',
value: 'AfterSaleService',
icon: <AfterSaleService color="#ffffff" />,
},
]

const Demo3 = () => {
const [value, setValue] = useState<string | number>('Apps')
return (
<Cell>
<Segmented
value={value}
options={defaultOptions}
onChange={(val) => {
setValue(val)
}}
/>
</Cell>
)
}
export default Demo3
74 changes: 74 additions & 0 deletions src/packages/segmented/doc.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Segmented

Used for switching content cards with page type distinctions, such as switching between main image videos and text with images.

## Import

```tsx
import { Segmented } from '@nutui/nutui-react'
```

## Demo

### Uncontrolled

:::demo

<CodeBlock src='h5/demo1.tsx'></CodeBlock>

:::

### Controlled

:::demo

<CodeBlock src='h5/demo2.tsx'></CodeBlock>

:::

### Icon

:::demo

<CodeBlock src='h5/demo3.tsx'></CodeBlock>

:::

## Segmented

### Props

| Property | Description | Type | Default |
| --- | --- | --- | --- |
| value | Currently selected value | `string \| number` | `-` |
| defaultValue | Default selected value | `string \| number` | `-` |
| options | Set children optional | `string[] \| number[]\| SegmentedItem[]` | `-` |
| onChange | The callback function that is triggered when the state changes | `(value: string \| number) => void` | `-` |

### SegmentedItem

| Property | Description | Type | Default |
| --- | --- | --- | --- |
| label | Display text for Segmented item | `ReactNode` | `-` |
| value | Value for Segmented item | `string \| number` | `-` |
| disabled | Disabled state of segmented item | `boolean` | `false` |
| icon | Display icon for Segmented item | `ReactNode` | `-` |
| className | The additional css class | `string` | `-` |

## 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).
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

修复 ConfigProvider 组件的链接

链接 [ConfigProvider component](#/en-US/component/configprovider) 格式不正确。建议修改为正确的相对路径。

-Please refer to [ConfigProvider component](#/en-US/component/configprovider).
+Please refer to [ConfigProvider component](../configprovider/doc.en-US.md).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](#/en-US/component/configprovider).
The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](../configprovider/doc.en-US.md).
🧰 Tools
🪛 Markdownlint

62-62: null
Link fragments should be valid

(MD051, link-fragments)


| Name | Description | Default Value |
| --- | --- | --- |
| \--nutui-segmented-padding | Padding inside the segmented selector | `$spacing-xxxs` |
| \--nutui-segmented-radius | Border radius of the segmented selector | `$radius-xs` |
| \--nutui-segmented-background | Background color of the segmented selector | `$color-mask-part` |
| \--nutui-segmented-item-padding | Padding for options in the segmented selector | `$spacing-xxs $spacing-xs` |
| \--nutui-segmented-item-radius | Border radius for options in the segmented selector | `$spacing-xxs $spacing-xs` |
| \--nutui-segmented-item-fontsize | Font size for options in the segmented selector | `$font-size-s` |
| \--nutui-segmented-item-color | Text color for options in the segmented selector | `$color-primary-text` |
| \--nutui-segmented-active-background | Background color for selected options in the segmented selector | `$color-mask-part` |
| \--nutui-segmented-icon-margin-right | Spacing between options in the segmented selector | `$color-mask-part` |
Loading
Loading