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: selector's options support fieldNames #6303

Merged
merged 2 commits into from
Aug 15, 2023
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
5 changes: 1 addition & 4 deletions src/components/cascader-view/cascader-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,12 @@ import { optionSkeleton } from './option-skeleton'
import Skeleton from '../skeleton'
import { useUpdateEffect } from 'ahooks'
import { useFieldNames } from '../../hooks'
import type { FieldNamesType } from '../../hooks'
import type { FieldNamesType, BaseOptionType } from '../../hooks'

const classPrefix = `adm-cascader-view`

export type CascaderValue = string

type BaseOptionType = {
[key: string]: any
}
export type CascaderOption = {
label?: string
value?: string
Expand Down
23 changes: 18 additions & 5 deletions src/components/selector/demos/demo1.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import { Selector, Space } from 'antd-mobile'
import { DemoBlock } from 'demos'
import { options } from './options'
import { options, fieldNamesOptions } from './options'

export default () => {
return (
Expand All @@ -18,7 +18,7 @@ export default () => {
<Selector
options={options}
defaultValue={['2', '3']}
multiple={true}
multiple
onChange={(arr, extend) => console.log(arr, extend.items)}
/>
</DemoBlock>
Expand All @@ -28,7 +28,7 @@ export default () => {
columns={2}
options={options}
defaultValue={['2', '3']}
multiple={true}
multiple
/>
</DemoBlock>

Expand All @@ -37,13 +37,13 @@ export default () => {
columns={3}
options={options}
defaultValue={['2', '3']}
multiple={true}
multiple
/>
</DemoBlock>

<DemoBlock title='禁用状态'>
<Space block direction='vertical'>
<Selector options={options} defaultValue={['1']} disabled={true} />
<Selector options={options} defaultValue={['1']} disabled />
<Selector
options={[
{
Expand All @@ -64,6 +64,19 @@ export default () => {
/>
</Space>
</DemoBlock>
<DemoBlock title='自定义FieldName'>
<Selector
options={fieldNamesOptions}
fieldNames={{
label: 'labelT',
value: 'valueT',
disabled: 'disabledT',
}}
defaultValue={['1']}
multiple
onChange={(arr, extend) => console.log(arr, extend.items)}
/>
</DemoBlock>
</>
)
}
16 changes: 16 additions & 0 deletions src/components/selector/demos/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,19 @@ export const options = [
value: '3',
},
]

export const fieldNamesOptions = [
{
labelT: '选项一',
valueT: '1',
},
{
labelT: '选项二',
valueT: '2',
},
{
labelT: '选项三',
valueT: '3',
disabledT: true,
},
]
1 change: 1 addition & 0 deletions src/components/selector/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Provides multiple options for the user to choose from, typically used in filters
| columns | Number of the displayed columns (Note that `grid` layout not support in IOS 9) | `number` | - |
| defaultValue | Selected value by default | `SelectorValue[]` | `[]` |
| disabled | Whether to disable selecting | `boolean` | `false` |
| fieldNames | Custom field name for label and value and disabled | `{ label: string, value: string, disabled: string }` | `{ label: 'label', value: 'value',disabled:'disabled' }` |
| multiple | Whether to allow multiple selections | `boolean` | `false` |
| onChange | Triggered when the value is changed | `(value: SelectorValue[], extend: { items: SelectorOption[] }) => void` | - |
| options | Optional selector | `SelectorOption[]` | - |
Expand Down
1 change: 1 addition & 0 deletions src/components/selector/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
| columns | 列数(注意 `grid` 布局在 IOS 9 下不支持) | `number` | - |
| defaultValue | 默认项 | `SelectorValue[]` | `[]` |
| disabled | 是否全局禁止选中 | `boolean` | `false` |
| fieldNames | 自定义 options 中 label value disabled 的字段 | `{ label: string, value: string, disabled: string }` | `{ label: 'label', value: 'value',disabled:'disabled' }` |
| multiple | 是否允许多选 | `boolean` | `false` |
| onChange | 选项改变时触发 | `(value: SelectorValue[], extend: { items: SelectorOption[] }) => void` | - |
| options | 可选项 | `SelectorOption[]` | - |
Expand Down
36 changes: 22 additions & 14 deletions src/components/selector/selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ import Grid, { GridProps } from '../grid'
import { usePropsValue } from '../../utils/use-props-value'
import { CheckMark } from './check-mark'
import { useConfig } from '../config-provider'
import { useFieldNames } from '../../hooks'
import type { FieldNamesType, BaseOptionType } from '../../hooks'

const classPrefix = `adm-selector`

type SelectorValue = string | number

export interface SelectorOption<V> {
label: ReactNode
export type SelectorOption<V> = {
label?: ReactNode
description?: ReactNode
value: V
value?: V
disabled?: boolean
}
} & BaseOptionType

export type SelectorProps<V> = {
options: SelectorOption<V>[]
Expand All @@ -29,6 +31,7 @@ export type SelectorProps<V> = {
value?: V[]
onChange?: (v: V[], extend: { items: SelectorOption<V>[] }) => void
showCheckMark?: boolean
fieldNames?: FieldNamesType
} & NativeProps<
| '--color'
| '--checked-color'
Expand All @@ -51,23 +54,25 @@ const defaultProps = {

export const Selector = <V extends SelectorValue>(p: SelectorProps<V>) => {
const props = mergeProps(defaultProps, p)
const [labelName, valueName, , disabledName] = useFieldNames(props.fieldNames)
const [value, setValue] = usePropsValue({
value: props.value,
defaultValue: props.defaultValue,
onChange: val => {
const extend = {
get items() {
return props.options.filter(option => val.includes(option.value))
return props.options.filter(option => val.includes(option[valueName]))
},
}
props.onChange?.(val, extend)
},
})

const { locale } = useConfig()

const items = props.options.map(option => {
const active = (value || []).includes(option.value)
const disabled = option.disabled || props.disabled
const active = (value || []).includes(option[valueName])
const disabled = option[disabledName] || props.disabled
const itemCls = classNames(`${classPrefix}-item`, {
[`${classPrefix}-item-active`]: active && !props.multiple,
[`${classPrefix}-item-multiple-active`]: active && props.multiple,
Expand All @@ -76,19 +81,19 @@ export const Selector = <V extends SelectorValue>(p: SelectorProps<V>) => {

return (
<div
key={option.value}
key={option[valueName]}
className={itemCls}
onClick={() => {
if (disabled) {
return
}
if (props.multiple) {
const val = active
? value.filter(v => v !== option.value)
: [...value, option.value]
? value.filter(v => v !== option[valueName])
: [...value, option[valueName]]
setValue(val)
} else {
const val = active ? [] : [option.value]
const val = active ? [] : [option[valueName]]
setValue(val)
}
}}
Expand All @@ -97,7 +102,7 @@ export const Selector = <V extends SelectorValue>(p: SelectorProps<V>) => {
(active && !props.multiple) || (active && props.multiple)
}
>
{option.label}
{option[labelName]}
{option.description && (
<div className={`${classPrefix}-item-description`}>
{option.description}
Expand All @@ -119,8 +124,11 @@ export const Selector = <V extends SelectorValue>(p: SelectorProps<V>) => {
role='listbox'
aria-label={locale.Selector.name}
>
{!props.columns && <Space wrap>{items}</Space>}
{props.columns && <Grid columns={props.columns}>{items}</Grid>}
{props.columns ? (
<Grid columns={props.columns}>{items}</Grid>
) : (
<Space wrap>{items}</Space>
)}
</div>
)
}
39 changes: 37 additions & 2 deletions src/components/selector/tests/selector.test.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import React from 'react'
import { render, testA11y, fireEvent, screen } from 'testing'
import Selector from '..'
import { options } from '../demos/options'
import { options, fieldNamesOptions } from '../demos/options'

const classPrefix = `adm-selector`

describe('Selector', () => {
test('a11y', async () => {
await testA11y(<Selector options={options} />)
})

test('a11y fieldNames', async () => {
await testA11y(
<Selector
options={fieldNamesOptions}
fieldNames={{
label: 'labelT',
value: 'valueT',
disabled: 'disabledT',
}}
/>
)
})
test('onChange should be work', () => {
const onChange = jest.fn()
render(<Selector options={options} onChange={onChange} />)
Expand All @@ -20,7 +31,31 @@ describe('Selector', () => {
expect(onChange.mock.calls[0][0]).toMatchObject([options[1].value])
expect(onChange.mock.calls[0][1].items).toMatchObject([options[1]])
})
test('fieldNames onChange should be work', () => {
const onChange = jest.fn()
render(
<Selector
options={fieldNamesOptions}
fieldNames={{
label: 'labelT',
value: 'valueT',
disabled: 'disabledT',
}}
onChange={onChange}
/>
)

const label = screen.getByText(fieldNamesOptions[1].labelT)
fireEvent.click(label)

expect(label).toHaveClass(`${classPrefix}-item-active`)
expect(onChange.mock.calls[0][0]).toMatchObject([
fieldNamesOptions[1].valueT,
])
expect(onChange.mock.calls[0][1].items).toMatchObject([
fieldNamesOptions[1],
])
})
test('disabled should be work', () => {
const onChange = jest.fn()
render(
Expand Down
4 changes: 4 additions & 0 deletions src/hooks/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
export { useFieldNames } from './useFieldNames'
export type { FieldNamesType } from './useFieldNames'

export type BaseOptionType = {
[key: string]: any
}
Loading