mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-05-20 21:39:15 +08:00
feat(PickerGroup): the tab of PickerGroup supports controlled mode (#11771)
* feat(PickerGroup): add method to set the active of tab * chore: update * chore: format code * chore: update * chore: update * docs: update docs
This commit is contained in:
parent
e0ae206c89
commit
3dabce8e98
@ -1,15 +1,17 @@
|
||||
import {
|
||||
ref,
|
||||
defineComponent,
|
||||
type InjectionKey,
|
||||
type ExtractPropTypes,
|
||||
} from 'vue';
|
||||
import { defineComponent, type InjectionKey, type ExtractPropTypes } from 'vue';
|
||||
|
||||
// Utils
|
||||
import { extend, pick, makeArrayProp, createNamespace } from '../utils';
|
||||
import {
|
||||
pick,
|
||||
extend,
|
||||
makeArrayProp,
|
||||
makeNumericProp,
|
||||
createNamespace,
|
||||
} from '../utils';
|
||||
|
||||
// Composables
|
||||
import { useChildren } from '@vant/use';
|
||||
import { useSyncPropRef } from '../composables/use-sync-prop-ref';
|
||||
|
||||
// Components
|
||||
import { Tab } from '../tab';
|
||||
@ -28,6 +30,7 @@ export const PICKER_GROUP_KEY: InjectionKey<PickerGroupProvide> = Symbol(name);
|
||||
export const pickerGroupProps = extend(
|
||||
{
|
||||
tabs: makeArrayProp<string>(),
|
||||
activeTab: makeNumericProp(0),
|
||||
nextStepText: String,
|
||||
},
|
||||
pickerToolbarProps
|
||||
@ -40,20 +43,23 @@ export default defineComponent({
|
||||
|
||||
props: pickerGroupProps,
|
||||
|
||||
emits: ['confirm', 'cancel'],
|
||||
emits: ['confirm', 'cancel', 'update:activeTab'],
|
||||
|
||||
setup(props, { emit, slots }) {
|
||||
const activeTab = ref(0);
|
||||
const activeTab = useSyncPropRef(
|
||||
() => props.activeTab,
|
||||
(value) => emit('update:activeTab', value)
|
||||
);
|
||||
const { children, linkChildren } = useChildren(PICKER_GROUP_KEY);
|
||||
|
||||
linkChildren();
|
||||
|
||||
const showNextButton = () =>
|
||||
activeTab.value < props.tabs.length - 1 && props.nextStepText;
|
||||
+activeTab.value < props.tabs.length - 1 && props.nextStepText;
|
||||
|
||||
const onConfirm = () => {
|
||||
if (showNextButton()) {
|
||||
activeTab.value++;
|
||||
activeTab.value = +activeTab.value + 1;
|
||||
} else {
|
||||
emit(
|
||||
'confirm',
|
||||
|
@ -56,11 +56,13 @@ export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -70,6 +72,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
currentTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -104,11 +108,13 @@ export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -118,6 +124,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
currentTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -155,6 +163,7 @@ export default {
|
||||
const onConfirm = () => {
|
||||
showToast(`${startDate.value.join('/')} ${endDate.value.join('/')}`);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -164,6 +173,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
endDate,
|
||||
startDate,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -197,6 +208,7 @@ export default {
|
||||
const onConfirm = () => {
|
||||
showToast(`${startTime.value.join(':')} ${endTime.value.join(':')}`);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -204,6 +216,72 @@ export default {
|
||||
return {
|
||||
endTime,
|
||||
startTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Controlled Mode
|
||||
|
||||
Supports both uncontrolled and controlled modes:
|
||||
|
||||
- When `v-model:active-tab` is not bound, the PickerGroup component completely controls the `tab` switching.
|
||||
- When `v-model:active-tab` is bound, PickerGroup supports controlled mode, and the `tab` switching is controlled by both the `v-model:active-tab` value and the component itself.
|
||||
|
||||
```html
|
||||
<van-button type="primary" @click="setActiveTab">
|
||||
toggle tab, current {{ activeTab }}
|
||||
</van-button>
|
||||
<van-picker-group
|
||||
v-model:active-tab="activeTab"
|
||||
title="Title"
|
||||
:tabs="['Date', 'Time']"
|
||||
@confirm="onConfirm"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
<van-time-picker v-model="currentTime" />
|
||||
</van-picker-group>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const activeTab = ref(0);
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const setActiveTab = () => {
|
||||
activeTab.value = activeTab.value ? 0 : 1;
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
activeTab,
|
||||
currentDate,
|
||||
currentTime,
|
||||
setActiveTab,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -213,13 +291,14 @@ export default {
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| ----------------------- | ------------------------ | ---------- | --------- |
|
||||
| tabs | Titles of tabs | _string[]_ | `[]` |
|
||||
| title | Toolbar title | _string_ | `''` |
|
||||
| next-step-text `v4.0.8` | Text of next step button | _string_ | `''` |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model:active-tab | Set index of active tab | _number \| string_ | `0` |
|
||||
| tabs | Titles of tabs | _string[]_ | `[]` |
|
||||
| title | Toolbar title | _string_ | `''` |
|
||||
| next-step-text `v4.0.8` | Text of next step button | _string_ | `''` |
|
||||
| confirm-button-text | Text of confirm button | _string_ | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | _string_ | `Cancel` |
|
||||
|
||||
### Slots
|
||||
|
||||
@ -235,5 +314,15 @@ export default {
|
||||
The component exports the following type definitions:
|
||||
|
||||
```ts
|
||||
import type { DatePickerProps, DatePickerColumnType } from 'vant';
|
||||
import type { PickerGroupProps, PickerGroupThemeVars } from 'vant';
|
||||
```
|
||||
|
||||
## Theming
|
||||
|
||||
### CSS Variables
|
||||
|
||||
The component provides the following CSS variables, which can be used to customize styles. Please refer to [ConfigProvider component](#/en-US/config-provider).
|
||||
|
||||
| Name | Default Value | Description |
|
||||
| ----------------------------- | -------------------- | ----------- |
|
||||
| --van-picker-group-background | _--van-background-2_ | - |
|
||||
|
@ -56,11 +56,13 @@ export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -70,6 +72,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
currentTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -104,11 +108,13 @@ export default {
|
||||
setup() {
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -118,6 +124,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
currentDate,
|
||||
currentTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -155,6 +163,7 @@ export default {
|
||||
const onConfirm = () => {
|
||||
showToast(`${startDate.value.join('/')} ${endDate.value.join('/')}`);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -164,6 +173,8 @@ export default {
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
endDate,
|
||||
startDate,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -197,6 +208,7 @@ export default {
|
||||
const onConfirm = () => {
|
||||
showToast(`${startTime.value.join(':')} ${endTime.value.join(':')}`);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
@ -204,6 +216,73 @@ export default {
|
||||
return {
|
||||
endTime,
|
||||
startTime,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 受控模式
|
||||
|
||||
`PickerGroup` 中 `tab` 的切换支持非受控模式和受控模式:
|
||||
|
||||
- 当未绑定 `v-model:active-tab` 时,PickerGroup 组件 `tab` 的切换完全由组件自身控制。
|
||||
- 当绑定 `v-model:active-tab` 时,PickerGroup 支持受控模式,此时组件 `tab` 的切换同时支持 `v-model:active-tab` 的值和组件本身控制。
|
||||
|
||||
```html
|
||||
<van-button type="primary" @click="setActiveTab">
|
||||
点击切换 tab,当前为 {{ activeTab }}
|
||||
</van-button>
|
||||
<van-picker-group
|
||||
v-model:active-tab="activeTab"
|
||||
title="预约日期"
|
||||
:tabs="['选择日期', '选择时间']"
|
||||
@confirm="onConfirm"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
<van-time-picker v-model="currentTime" />
|
||||
</van-picker-group>
|
||||
```
|
||||
|
||||
```js
|
||||
import { ref } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
export default {
|
||||
setup() {
|
||||
const activeTab = ref(0);
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const currentTime = ref(['12', '00']);
|
||||
|
||||
const setActiveTab = () => {
|
||||
activeTab.value = activeTab.value ? 0 : 1;
|
||||
};
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(
|
||||
`${currentDate.value.join('/')} ${currentTime.value.join(':')}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
|
||||
return {
|
||||
minDate: new Date(2020, 0, 1),
|
||||
maxDate: new Date(2025, 5, 1),
|
||||
activeTab,
|
||||
currentDate,
|
||||
currentTime,
|
||||
setActiveTab,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -213,13 +292,14 @@ export default {
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ----------------------- | ---------------- | ---------- | ------ |
|
||||
| tabs | 设置标签页的标题 | _string[]_ | `[]` |
|
||||
| title | 顶部栏标题 | _string_ | `''` |
|
||||
| next-step-text `v4.0.8` | 下一步按钮的文字 | _string_ | `''` |
|
||||
| confirm-button-text | 确认按钮的文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮的文字 | _string_ | `取消` |
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| ----------------------- | ------------------ | ------------------ | ------ |
|
||||
| v-model:active-tab | 设置当前选中的标签 | _number \| string_ | `0` |
|
||||
| tabs | 设置标签页的标题 | _string[]_ | `[]` |
|
||||
| title | 顶部栏标题 | _string_ | `''` |
|
||||
| next-step-text `v4.0.8` | 下一步按钮的文字 | _string_ | `''` |
|
||||
| confirm-button-text | 确认按钮的文字 | _string_ | `确认` |
|
||||
| cancel-button-text | 取消按钮的文字 | _string_ | `取消` |
|
||||
|
||||
### Slots
|
||||
|
||||
@ -235,5 +315,15 @@ export default {
|
||||
组件导出以下类型定义:
|
||||
|
||||
```ts
|
||||
import type { PickerGroupProps } from 'vant';
|
||||
import type { PickerGroupProps, PickerGroupThemeVars } from 'vant';
|
||||
```
|
||||
|
||||
## 主题定制
|
||||
|
||||
### 样式变量
|
||||
|
||||
组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](#/zh-CN/config-provider)。
|
||||
|
||||
| 名称 | 默认值 | 描述 |
|
||||
| ----------------------------- | -------------------- | ---- |
|
||||
| --van-picker-group-background | _--van-background-2_ | - |
|
||||
|
60
packages/vant/src/picker-group/demo/ControlTab.vue
Normal file
60
packages/vant/src/picker-group/demo/ControlTab.vue
Normal file
@ -0,0 +1,60 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useTranslate } from '../../../docs/site';
|
||||
import VanPickerGroup from '..';
|
||||
import VanButton from '../../button';
|
||||
import VanTimePicker from '../../time-picker';
|
||||
import VanDatePicker from '../../date-picker';
|
||||
import { showToast } from '../../toast';
|
||||
|
||||
const t = useTranslate({
|
||||
'zh-CN': {
|
||||
date: '选择日期',
|
||||
time: '选择时间',
|
||||
title: '预约日期',
|
||||
btnText: '点击切换 tab,当前为 ',
|
||||
},
|
||||
'en-US': {
|
||||
date: 'Date',
|
||||
time: 'Time',
|
||||
title: 'Title',
|
||||
btnText: 'toggle tab, current ',
|
||||
},
|
||||
});
|
||||
const activeTab = ref(0);
|
||||
const currentTime = ref(['12', '00']);
|
||||
const currentDate = ref(['2022', '06', '01']);
|
||||
const minDate = new Date(2020, 0, 1);
|
||||
const maxDate = new Date(2025, 5, 1);
|
||||
|
||||
const onConfirm = () => {
|
||||
showToast(`${currentDate.value.join('/')} ${currentTime.value.join(':')}`);
|
||||
};
|
||||
const onCancel = () => {
|
||||
showToast('cancel');
|
||||
};
|
||||
|
||||
const setActiveTab = () => {
|
||||
activeTab.value = activeTab.value ? 0 : 1;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<van-button style="margin: 10px 0" type="primary" @click="setActiveTab">
|
||||
{{ t('btnText') + activeTab }}
|
||||
</van-button>
|
||||
<van-picker-group
|
||||
v-model:active-tab="activeTab"
|
||||
:title="t('title')"
|
||||
:tabs="[t('date'), t('time')]"
|
||||
@confirm="onConfirm"
|
||||
@cancel="onCancel"
|
||||
>
|
||||
<van-date-picker
|
||||
v-model="currentDate"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
/>
|
||||
<van-time-picker v-model="currentTime" />
|
||||
</van-picker-group>
|
||||
</template>
|
@ -3,6 +3,7 @@ import SelectDateTime from './SelectDateTime.vue';
|
||||
import SelectTimeRange from './SelectTimeRange.vue';
|
||||
import SelectDateRange from './SelectDateRange.vue';
|
||||
import NextStepButton from './NextStepButton.vue';
|
||||
import ControlTab from './ControlTab.vue';
|
||||
import { useTranslate } from '../../../docs/site';
|
||||
|
||||
const t = useTranslate({
|
||||
@ -11,12 +12,14 @@ const t = useTranslate({
|
||||
selectDateRange: '选择日期范围',
|
||||
selectTimeRange: '选择时间范围',
|
||||
nextStepButton: '下一步按钮',
|
||||
controlled: '受控模式',
|
||||
},
|
||||
'en-US': {
|
||||
selectDateTime: 'Select Date Time',
|
||||
selectDateRange: 'Select Date Range',
|
||||
selectTimeRange: 'Select Time Range',
|
||||
nextStepButton: 'Next Step Button',
|
||||
controlled: 'Controlled Mode',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@ -37,4 +40,8 @@ const t = useTranslate({
|
||||
<demo-block card :title="t('selectTimeRange')">
|
||||
<select-time-range />
|
||||
</demo-block>
|
||||
|
||||
<demo-block card :title="t('controlled')">
|
||||
<control-tab />
|
||||
</demo-block>
|
||||
</template>
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,5 @@
|
||||
import { ref } from 'vue';
|
||||
import { mount } from '../../../test';
|
||||
import { later, mount } from '../../../test';
|
||||
import { Picker, PickerConfirmEventParams } from '../../picker';
|
||||
import { PickerGroup } from '..';
|
||||
|
||||
@ -108,3 +108,38 @@ test('should switch to next step when click confirm button', async () => {
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('support controlled mode to set active-tab', async () => {
|
||||
const value1 = ref(['1']);
|
||||
const value2 = ref(['2']);
|
||||
const activeTab = ref(0);
|
||||
|
||||
const wrapper = mount({
|
||||
render() {
|
||||
return (
|
||||
<PickerGroup
|
||||
activeTab={activeTab.value}
|
||||
title="Title"
|
||||
tabs={['Tab1', 'Tab2']}
|
||||
>
|
||||
<Picker
|
||||
v-model={value1.value}
|
||||
columns={[{ text: '1', value: '1' }]}
|
||||
/>
|
||||
<Picker
|
||||
v-model={value2.value}
|
||||
columns={[{ text: '2', value: '2' }]}
|
||||
/>
|
||||
</PickerGroup>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
await later();
|
||||
const tabs = wrapper.findAll('.van-tab');
|
||||
expect(tabs[0]?.classes()).toContain('van-tab--active');
|
||||
|
||||
activeTab.value = 1;
|
||||
await later();
|
||||
expect(tabs[1]?.classes()).toContain('van-tab--active');
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user