u
This commit is contained in:
1 parent
ae0ad2bd63
commit
5a7e96db02
12 files changed
+466
-85
No files matched your search
@@ -6,7 +6,8 @@ export { default as Icon } from './icon'
|
||||
export { default as Input } from './input'
|
||||
export { default as Popconfirm } from './popconfirm'
|
||||
export { default as Popup } from './popup'
|
||||
export { default as Radio } from './radio'
|
||||
export { default as Radio } from './radio/radio'
|
||||
export { default as RadioGroup } from './radio/radio-group'
|
||||
export { default as Separator } from './separator'
|
||||
export { default as Space } from './space'
|
||||
export { default as Spin } from './spin'
|
||||
|
||||
@@ -22,7 +22,17 @@
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
font-family:
|
||||
'Helvetica', 'Arial', 'PingFangSC-Regular', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', 'Microsoft JhengHei';
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
Segoe UI,
|
||||
PingFang SC,
|
||||
Hiragino Sans GB,
|
||||
Microsoft YaHei,
|
||||
Helvetica Neue,
|
||||
Helvetica,
|
||||
Arial,
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
html,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { InjectionKey, Slots } from 'vue'
|
||||
|
||||
export interface RadioGroupContext {
|
||||
name: 'ArcoRadioGroup'
|
||||
name: 'AoRadioGroup'
|
||||
value: string | number | boolean
|
||||
disabled: boolean
|
||||
slots: Slots
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@use '../_styles/_variables.scss' as *;
|
||||
|
||||
$radio-prefix: '#{$prefix}-radio';
|
||||
$radio-group-prefix: '#{$prefix}-radio-group';
|
||||
|
||||
.#{$radio-prefix} {
|
||||
color: $text-color-base;
|
||||
@@ -10,6 +11,11 @@ $radio-prefix: '#{$prefix}-radio';
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&.#{$radio-prefix}-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:hover .#{$radio-prefix}-main .#{$radio-prefix}-inner {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
@@ -42,8 +48,8 @@ $radio-prefix: '#{$prefix}-radio';
|
||||
.#{$radio-prefix}-inner {
|
||||
border: 1px solid $border-color;
|
||||
border-radius: 100%;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-color: $bg-color-container;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@@ -52,8 +58,8 @@ $radio-prefix: '#{$prefix}-radio';
|
||||
outline: 0;
|
||||
&::after {
|
||||
box-sizing: border-box;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 999px;
|
||||
background-color: transparent;
|
||||
content: '';
|
||||
@@ -62,8 +68,8 @@ $radio-prefix: '#{$prefix}-radio';
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(0);
|
||||
transition:
|
||||
transform 0.15s ease-in,
|
||||
background-color 0.15s ease-in;
|
||||
transform 0.1s ease-in,
|
||||
background-color 0.1s ease-in;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +78,8 @@ $radio-prefix: '#{$prefix}-radio';
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$radio-group-prefix} {
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { defineComponent, inject } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import { radioGroupKey } from './context'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Radio',
|
||||
props: {
|
||||
checked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
// private
|
||||
uninjectGroupContext: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['change', 'update:checked'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('radio')
|
||||
|
||||
const radioGroupCtx = !props.uninjectGroupContext
|
||||
? inject(radioGroupKey, undefined)
|
||||
: undefined
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.disabled)
|
||||
return
|
||||
const newChecked = !props.checked
|
||||
emit('update:checked', newChecked)
|
||||
emit('change', {
|
||||
value: props.value,
|
||||
checked: newChecked,
|
||||
})
|
||||
}
|
||||
|
||||
return () => (
|
||||
<label
|
||||
class={[
|
||||
prefixCls,
|
||||
props.checked ? `${prefixCls}-checked` : '',
|
||||
props.disabled ? `${prefixCls}-disabled` : '',
|
||||
]}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span class={`${prefixCls}-main`}>
|
||||
<span class={`${prefixCls}-inner`} />
|
||||
<input
|
||||
class={`${prefixCls}-input`}
|
||||
type="radio"
|
||||
checked={props.checked}
|
||||
disabled={props.disabled}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
</span>
|
||||
<span class={`${prefixCls}-content`}>
|
||||
{slots.default ? slots.default() : null}
|
||||
</span>
|
||||
</label>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,112 @@
|
||||
import type { PropType, RenderFunction } from 'vue'
|
||||
import { computed, defineComponent, provide, reactive, ref, watch } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import Space from '../space/index'
|
||||
import { radioGroupKey } from './context'
|
||||
import Radio from './radio'
|
||||
|
||||
interface RadioOption {
|
||||
label?: string | RenderFunction
|
||||
value?: string | number
|
||||
extra?: string | RenderFunction
|
||||
disabled?: boolean
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RadioGroup',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||
default: undefined,
|
||||
},
|
||||
defaultValue: {
|
||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||
default: '',
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
labelField: {
|
||||
type: String,
|
||||
default: 'label',
|
||||
},
|
||||
valueField: {
|
||||
type: String,
|
||||
default: 'value',
|
||||
},
|
||||
options: {
|
||||
type: Array as PropType<Array<string | number | RadioOption>>,
|
||||
default: [],
|
||||
},
|
||||
spaceProps: {
|
||||
type: Object as PropType<Record<string, any>>,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { emit, slots }) {
|
||||
const prefixCls = getPrefixCls('radio-group')
|
||||
|
||||
// 内部值,优先使用 v-model
|
||||
const _value = ref(props.modelValue ?? props.defaultValue)
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal !== undefined) {
|
||||
_value.value = newVal
|
||||
}
|
||||
})
|
||||
|
||||
const handleChange = (val: string | number | boolean, e: Event) => {
|
||||
_value.value = val
|
||||
emit('update:modelValue', val)
|
||||
emit('change', val, e)
|
||||
}
|
||||
const groupDisabled = computed(() => props.disabled)
|
||||
|
||||
// 提供 context 给 Radio 使用
|
||||
provide(radioGroupKey, reactive({
|
||||
name: 'AoRadioGroup' as const,
|
||||
value: _value,
|
||||
disabled: groupDisabled,
|
||||
slots,
|
||||
handleChange,
|
||||
}))
|
||||
|
||||
const renderOptions = () => {
|
||||
return props.options.map((option) => {
|
||||
// 标准化 option
|
||||
const opt: RadioOption
|
||||
= typeof option === 'string' || typeof option === 'number'
|
||||
? { [props.labelField]: String(option), [props.valueField]: option }
|
||||
: option
|
||||
|
||||
const labelContent = slots.label
|
||||
? slots.label({ data: opt })
|
||||
: typeof opt[props.labelField] === 'function'
|
||||
? opt[props.labelField]()
|
||||
: opt[props.labelField]
|
||||
|
||||
return (
|
||||
<Radio
|
||||
key={opt[props.valueField]}
|
||||
value={opt[props.valueField]}
|
||||
disabled={opt.disabled ?? props.disabled}
|
||||
modelValue={_value.value === opt[props.valueField]}
|
||||
>
|
||||
{labelContent}
|
||||
</Radio>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div class={[prefixCls]}>
|
||||
<Space {...props.spaceProps}>
|
||||
{props.options.length > 0 ? renderOptions() : slots.default?.()}
|
||||
</Space>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { PropType, VNode } from 'vue'
|
||||
import type { RadioGroupContext } from './context'
|
||||
import { computed, defineComponent, inject, ref } from 'vue'
|
||||
import { getPrefixCls } from '../_utils/getPrefixCls'
|
||||
import { radioGroupKey } from './context'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Radio',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
defaultChecked: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Boolean] as PropType<string | number | boolean>,
|
||||
default: true,
|
||||
},
|
||||
allowUncheck: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
extra: {
|
||||
type: [String, Object, Function] as unknown as () => string | VNode | (() => VNode),
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue', 'change'],
|
||||
setup(props, { slots, emit }) {
|
||||
const prefixCls = getPrefixCls('radio')
|
||||
|
||||
const radioGroupCtx = inject<RadioGroupContext | undefined>(radioGroupKey, undefined)
|
||||
const inGroup = computed(() => !!radioGroupCtx)
|
||||
|
||||
const isDisabled = computed(() => props.disabled || radioGroupCtx?.disabled)
|
||||
const _checked = ref(props.defaultChecked)
|
||||
|
||||
// 判断是否选中
|
||||
const isChecked = computed(() => {
|
||||
if (inGroup.value)
|
||||
return props.value === radioGroupCtx!.value
|
||||
return _checked.value
|
||||
})
|
||||
|
||||
const handleClick = (e: Event) => {
|
||||
if (isDisabled.value)
|
||||
return
|
||||
if (isChecked.value && !props.allowUncheck)
|
||||
return
|
||||
|
||||
if (inGroup.value) {
|
||||
const newValue = isChecked.value && props.allowUncheck ? '' : props.value
|
||||
radioGroupCtx?.handleChange(newValue, e)
|
||||
}
|
||||
else {
|
||||
const newValue = props.allowUncheck ? !isChecked.value : true
|
||||
|
||||
_checked.value = newValue
|
||||
emit('update:modelValue', newValue)
|
||||
emit('change', props.value, e)
|
||||
}
|
||||
}
|
||||
|
||||
return () => (
|
||||
<label
|
||||
class={[
|
||||
prefixCls,
|
||||
isDisabled.value ? `${prefixCls}-disabled` : '',
|
||||
isChecked.value ? `${prefixCls}-checked` : '',
|
||||
]}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<span class={`${prefixCls}-main`}>
|
||||
<span class={`${prefixCls}-inner`} />
|
||||
<input
|
||||
class={`${prefixCls}-input`}
|
||||
type="radio"
|
||||
disabled={isDisabled.value}
|
||||
onClick={e => e.stopPropagation()}
|
||||
/>
|
||||
</span>
|
||||
<span class={`${prefixCls}-content`}>
|
||||
{slots.default?.()}
|
||||
</span>
|
||||
|
||||
</label>
|
||||
)
|
||||
},
|
||||
})
|
||||
Reference in new issue
Block a user