feat(Radio): support dot shape (#12057)

* feat(Radio): support dot shape

* chore: update test

* chore: format code
This commit is contained in:
Gavin 2023-07-08 09:56:29 +08:00 committed by GitHub
parent 3319d1e5cd
commit 39a54a8d9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 169 additions and 16 deletions

View File

@ -1,7 +1,13 @@
import { watch, computed, defineComponent, type ExtractPropTypes } from 'vue'; import { watch, computed, defineComponent, type ExtractPropTypes } from 'vue';
// Utils // Utils
import { createNamespace, extend, pick, truthProp } from '../utils'; import {
pick,
extend,
truthProp,
makeStringProp,
createNamespace,
} from '../utils';
import { CHECKBOX_GROUP_KEY } from '../checkbox-group/CheckboxGroup'; import { CHECKBOX_GROUP_KEY } from '../checkbox-group/CheckboxGroup';
// Composables // Composables
@ -9,7 +15,7 @@ import { useParent, useCustomFieldValue } from '@vant/use';
import { useExpose } from '../composables/use-expose'; import { useExpose } from '../composables/use-expose';
// Components // Components
import Checker, { checkerProps } from './Checker'; import Checker, { checkerProps, type CheckerShape } from './Checker';
// Types // Types
import type { CheckboxExpose } from './types'; import type { CheckboxExpose } from './types';
@ -17,6 +23,7 @@ import type { CheckboxExpose } from './types';
const [name, bem] = createNamespace('checkbox'); const [name, bem] = createNamespace('checkbox');
export const checkboxProps = extend({}, checkerProps, { export const checkboxProps = extend({}, checkerProps, {
shape: makeStringProp<CheckerShape>('round'),
bindGroup: truthProp, bindGroup: truthProp,
}); });

View File

@ -11,6 +11,8 @@ import {
} from '../utils'; } from '../utils';
import { Icon } from '../icon'; import { Icon } from '../icon';
import type { RadioShape } from '../radio';
export type CheckerShape = 'square' | 'round'; export type CheckerShape = 'square' | 'round';
export type CheckerDirection = 'horizontal' | 'vertical'; export type CheckerDirection = 'horizontal' | 'vertical';
export type CheckerLabelPosition = 'left' | 'right'; export type CheckerLabelPosition = 'left' | 'right';
@ -27,7 +29,6 @@ export type CheckerParent = {
export const checkerProps = { export const checkerProps = {
name: unknownProp, name: unknownProp,
shape: makeStringProp<CheckerShape>('round'),
disabled: Boolean, disabled: Boolean,
iconSize: numericProp, iconSize: numericProp,
modelValue: unknownProp, modelValue: unknownProp,
@ -40,6 +41,7 @@ export default defineComponent({
props: extend({}, checkerProps, { props: extend({}, checkerProps, {
bem: makeRequiredProp(Function), bem: makeRequiredProp(Function),
role: String, role: String,
shape: makeStringProp<CheckerShape | RadioShape>('round'),
parent: Object as PropType<CheckerParent | null>, parent: Object as PropType<CheckerParent | null>,
checked: Boolean, checked: Boolean,
bindGroup: truthProp, bindGroup: truthProp,
@ -107,12 +109,25 @@ export default defineComponent({
<div <div
ref={iconRef} ref={iconRef}
class={bem('icon', [shape, { disabled: disabled.value, checked }])} class={bem('icon', [shape, { disabled: disabled.value, checked }])}
style={{ fontSize: addUnit(iconSize) }} style={
shape !== 'dot'
? { fontSize: addUnit(iconSize) }
: {
width: addUnit(iconSize),
height: addUnit(iconSize),
borderColor: iconStyle.value?.borderColor,
}
}
> >
{slots.icon ? ( {slots.icon ? (
slots.icon({ checked, disabled: disabled.value }) slots.icon({ checked, disabled: disabled.value })
) : ( ) : shape !== 'dot' ? (
<Icon name="success" style={iconStyle.value} /> <Icon name="success" style={iconStyle.value} />
) : (
<div
class={bem('icon--dot__icon')}
style={{ backgroundColor: iconStyle.value?.backgroundColor }}
></div>
)} )}
</div> </div>
); );

View File

@ -66,6 +66,11 @@ export default {
<van-radio name="1" shape="square">Radio 1</van-radio> <van-radio name="1" shape="square">Radio 1</van-radio>
<van-radio name="2" shape="square">Radio 2</van-radio> <van-radio name="2" shape="square">Radio 2</van-radio>
</van-radio-group> </van-radio-group>
<van-radio-group v-model="checked">
<van-radio name="1" shape="dot">Radio 1</van-radio>
<van-radio name="2" shape="dot">Radio 2</van-radio>
</van-radio-group>
``` ```
### Custom Color ### Custom Color
@ -190,7 +195,7 @@ import type {
| Attribute | Description | Type | Default | | Attribute | Description | Type | Default |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| name | Radio name, usually a unique string or number | _any_ | - | | name | Radio name, usually a unique string or number | _any_ | - |
| shape | Can be set to `square` | _string_ | `round` | | shape | Can be set to `square` `dot` | _string_ | `round` |
| disabled | Whether to disable radio | _boolean_ | `false` | | disabled | Whether to disable radio | _boolean_ | `false` |
| label-disabled | Whether to disable label click | _boolean_ | `false` | | label-disabled | Whether to disable label click | _boolean_ | `false` |
| label-position | Can be set to `left` | _string_ | `right` | | label-position | Can be set to `left` | _string_ | `right` |
@ -235,6 +240,7 @@ The component provides the following CSS variables, which can be used to customi
| Name | Default Value | Description | | Name | Default Value | Description |
| -------------------------------- | -------------------------- | ----------- | | -------------------------------- | -------------------------- | ----------- |
| --van-radio-size | _20px_ | - | | --van-radio-size | _20px_ | - |
| --van-radio-dot-size | _8px_ | - |
| --van-radio-border-color | _var(--van-gray-5)_ | - | | --van-radio-border-color | _var(--van-gray-5)_ | - |
| --van-radio-duration | _var(--van-duration-fast)_ | - | | --van-radio-duration | _var(--van-duration-fast)_ | - |
| --van-radio-label-margin | _var(--van-padding-xs)_ | - | | --van-radio-label-margin | _var(--van-padding-xs)_ | - |

View File

@ -65,13 +65,18 @@ export default {
### 自定义形状 ### 自定义形状
`shape` 属性设置为 `square`,单选框的形状会变成方形。 `shape` 属性可选值为 `square``dot`,单选框形状分别对应方形和圆形。
```html ```html
<van-radio-group v-model="checked"> <van-radio-group v-model="checked">
<van-radio name="1" shape="square">单选框 1</van-radio> <van-radio name="1" shape="square">单选框 1</van-radio>
<van-radio name="2" shape="square">单选框 2</van-radio> <van-radio name="2" shape="square">单选框 2</van-radio>
</van-radio-group> </van-radio-group>
<van-radio-group v-model="checked">
<van-radio name="1" shape="dot">Radio 1</van-radio>
<van-radio name="2" shape="dot">Radio 2</van-radio>
</van-radio-group>
``` ```
### 自定义颜色 ### 自定义颜色
@ -190,7 +195,7 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| name | 标识符,通常为一个唯一的字符串或数字 | _any_ | - | | name | 标识符,通常为一个唯一的字符串或数字 | _any_ | - |
| shape | 形状,可选值为 `square` | _string_ | `round` | | shape | 形状,可选值为 `square` `dot` | _string_ | `round` |
| disabled | 是否为禁用状态 | _boolean_ | `false` | | disabled | 是否为禁用状态 | _boolean_ | `false` |
| label-disabled | 是否禁用文本内容点击 | _boolean_ | `false` | | label-disabled | 是否禁用文本内容点击 | _boolean_ | `false` |
| label-position | 文本位置,可选值为 `left` | _string_ | `right` | | label-position | 文本位置,可选值为 `left` | _string_ | `right` |
@ -249,6 +254,7 @@ import type {
| 名称 | 默认值 | 描述 | | 名称 | 默认值 | 描述 |
| -------------------------------- | -------------------------- | ---- | | -------------------------------- | -------------------------- | ---- |
| --van-radio-size | _20px_ | - | | --van-radio-size | _20px_ | - |
| --van-radio-dot-size | _8px_ | - |
| --van-radio-border-color | _var(--van-gray-5)_ | - | | --van-radio-border-color | _var(--van-gray-5)_ | - |
| --van-radio-duration | _var(--van-duration-fast)_ | - | | --van-radio-duration | _var(--van-duration-fast)_ | - |
| --van-radio-label-margin | _var(--van-padding-xs)_ | - | | --van-radio-label-margin | _var(--van-padding-xs)_ | - |

View File

@ -1,7 +1,7 @@
import { defineComponent, type ExtractPropTypes } from 'vue'; import { defineComponent, type ExtractPropTypes } from 'vue';
// Utils // Utils
import { pick, createNamespace } from '../utils'; import { pick, extend, createNamespace, makeStringProp } from '../utils';
import { RADIO_KEY } from '../radio-group/RadioGroup'; import { RADIO_KEY } from '../radio-group/RadioGroup';
// Composables // Composables
@ -10,13 +10,16 @@ import { useParent } from '@vant/use';
// Components // Components
import Checker, { import Checker, {
checkerProps, checkerProps,
CheckerShape, type CheckerShape,
CheckerLabelPosition, type CheckerLabelPosition,
} from '../checkbox/Checker'; } from '../checkbox/Checker';
export const radioProps = checkerProps; export type RadioShape = CheckerShape | 'dot';
export const radioProps = extend({}, checkerProps, {
shape: makeStringProp<RadioShape>('round'),
});
export type RadioShape = CheckerShape;
export type RadioLabelPosition = CheckerLabelPosition; export type RadioLabelPosition = CheckerLabelPosition;
export type RadioProps = ExtractPropTypes<typeof radioProps>; export type RadioProps = ExtractPropTypes<typeof radioProps>;
@ -25,7 +28,7 @@ const [name, bem] = createNamespace('radio');
export default defineComponent({ export default defineComponent({
name, name,
props: checkerProps, props: radioProps,
emits: ['update:modelValue'], emits: ['update:modelValue'],

View File

@ -41,7 +41,8 @@ const radio3 = ref('1');
const radio4 = ref('1'); const radio4 = ref('1');
const radio5 = ref('1'); const radio5 = ref('1');
const radioLabel = ref('1'); const radioLabel = ref('1');
const radioShape = ref('1'); const radioSquare = ref('1');
const radioDot = ref('1');
const radioIconSize = ref('1'); const radioIconSize = ref('1');
const radioHorizontal = ref('1'); const radioHorizontal = ref('1');
const radioLeftLabel = ref('1'); const radioLeftLabel = ref('1');
@ -76,10 +77,18 @@ const inactiveIcon = cdnURL('user-inactive.png');
</demo-block> </demo-block>
<demo-block :title="t('customShape')"> <demo-block :title="t('customShape')">
<van-radio-group v-model="radioShape" class="demo-radio-group"> <van-radio-group v-model="radioSquare" class="demo-radio-group">
<van-radio name="1" shape="square">{{ t('radio') }} 1</van-radio> <van-radio name="1" shape="square">{{ t('radio') }} 1</van-radio>
<van-radio name="2" shape="square">{{ t('radio') }} 2</van-radio> <van-radio name="2" shape="square">{{ t('radio') }} 2</van-radio>
</van-radio-group> </van-radio-group>
<van-radio-group
v-model="radioDot"
class="demo-radio-group"
style="margin-top: 20px"
>
<van-radio name="1" shape="dot">{{ t('radio') }} 1</van-radio>
<van-radio name="2" shape="dot">{{ t('radio') }} 2</van-radio>
</van-radio-group>
</demo-block> </demo-block>
<demo-block :title="t('customColor')"> <demo-block :title="t('customColor')">

View File

@ -1,5 +1,6 @@
:root { :root {
--van-radio-size: 20px; --van-radio-size: 20px;
--van-radio-dot-size: 8px;
--van-radio-border-color: var(--van-gray-5); --van-radio-border-color: var(--van-gray-5);
--van-radio-duration: var(--van-duration-fast); --van-radio-duration: var(--van-duration-fast);
--van-radio-label-margin: var(--van-padding-xs); --van-radio-label-margin: var(--van-padding-xs);
@ -56,12 +57,42 @@
} }
} }
&--dot {
position: relative;
border-radius: 100%;
box-sizing: border-box;
width: var(--van-radio-size);
height: var(--van-radio-size);
border: 1px solid var(--van-radio-border-color);
transition-duration: var(--van-radio-duration);
transition-property: border-color;
&__icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 100%;
height: calc(100% - var(--van-radio-dot-size));
width: calc(100% - var(--van-radio-dot-size));
transition-duration: var(--van-radio-duration);
transition-property: border-color, background-color;
}
}
&--checked { &--checked {
.van-icon { .van-icon {
color: var(--van-white); color: var(--van-white);
background-color: var(--van-radio-checked-icon-color); background-color: var(--van-radio-checked-icon-color);
border-color: var(--van-radio-checked-icon-color); border-color: var(--van-radio-checked-icon-color);
} }
&.van-radio__icon--dot {
border-color: var(--van-radio-checked-icon-color);
.van-radio__icon--dot__icon {
background: var(--van-radio-checked-icon-color);
}
}
} }
&--disabled { &--disabled {

View File

@ -191,6 +191,50 @@ exports[`should render demo and match snapshot 1`] = `
</span> </span>
</div> </div>
</div> </div>
<div class="van-radio-group demo-radio-group"
role="radiogroup"
style="margin-top:20px;"
>
<!--[-->
<div role="radio"
class="van-radio"
tabindex="0"
aria-checked="true"
>
<!--[-->
<div class="van-radio__icon van-radio__icon--dot van-radio__icon--checked"
style
>
<div class="van-radio__icon--dot__icon"
style
>
</div>
</div>
<span class="van-radio__label">
<!--[-->
Radio 1
</span>
</div>
<div role="radio"
class="van-radio"
tabindex="0"
aria-checked="false"
>
<!--[-->
<div class="van-radio__icon van-radio__icon--dot"
style
>
<div class="van-radio__icon--dot__icon"
style
>
</div>
</div>
<span class="van-radio__label">
<!--[-->
Radio 2
</span>
</div>
</div>
</div> </div>
<div> <div>
<!--[--> <!--[-->

View File

@ -126,6 +126,37 @@ exports[`should render demo and match snapshot 1`] = `
</span> </span>
</div> </div>
</div> </div>
<div class="van-radio-group demo-radio-group"
role="radiogroup"
style="margin-top: 20px;"
>
<div role="radio"
class="van-radio"
tabindex="0"
aria-checked="true"
>
<div class="van-radio__icon van-radio__icon--dot van-radio__icon--checked">
<div class="van-radio__icon--dot__icon">
</div>
</div>
<span class="van-radio__label">
Radio 1
</span>
</div>
<div role="radio"
class="van-radio"
tabindex="0"
aria-checked="false"
>
<div class="van-radio__icon van-radio__icon--dot">
<div class="van-radio__icon--dot__icon">
</div>
</div>
<span class="van-radio__label">
Radio 2
</span>
</div>
</div>
</div> </div>
<div> <div>
<div class="van-radio-group demo-radio-group" <div class="van-radio-group demo-radio-group"

View File

@ -1,5 +1,6 @@
export type RadioThemeVars = { export type RadioThemeVars = {
radioSize?: string; radioSize?: string;
radioDotSize?: string;
radioBorderColor?: string; radioBorderColor?: string;
radioDuration?: string; radioDuration?: string;
radioLabelMargin?: string; radioLabelMargin?: string;