feat(radio): clsPrefix

This commit is contained in:
07akioni 2021-04-18 00:07:55 +08:00
parent 9d60bb5270
commit e53f4109fd
5 changed files with 135 additions and 94 deletions

View File

@ -1,4 +1,6 @@
/* istanbul ignore file */
export { default as NRadio } from './src/Radio'
export { default as NRadioGroup } from './src/RadioGroup'
export { default as NRadioButton } from './src/RadioButton'
export type { RadioProps } from './src/Radio'
export type { RadioGroupProps } from './src/RadioGroup'
export type { RadioButtonProps } from './src/RadioButton'

View File

@ -1,10 +1,13 @@
import { h, defineComponent, computed, CSSProperties } from 'vue'
import { useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { createKey } from '../../_utils'
import type { ExtractPublicPropTypes } from '../../_utils'
import { radioLight, RadioTheme } from '../styles'
import useRadio from './use-radio'
import style from './styles/radio.cssr'
import { createKey } from '../../_utils'
export type RadioProps = ExtractPublicPropTypes<typeof useRadio.props>
export default defineComponent({
name: 'Radio',
@ -13,8 +16,15 @@ export default defineComponent({
...useRadio.props
},
setup (props) {
const themeRef = useTheme('Radio', 'Radio', style, radioLight, props)
const radio = useRadio(props)
const themeRef = useTheme(
'Radio',
'Radio',
style,
radioLight,
props,
radio.cPrefix
)
return Object.assign(radio, {
cssVars: computed(() => {
const {
@ -60,15 +70,15 @@ export default defineComponent({
})
},
render () {
const { $slots } = this
const { $slots, cPrefix } = this
return (
<div
class={[
'n-radio',
`${cPrefix}-radio`,
{
'n-radio--disabled': this.mergedDisabled,
'n-radio--checked': this.renderSafeChecked,
'n-radio--focus': this.focus
[`${cPrefix}-radio--disabled`]: this.mergedDisabled,
[`${cPrefix}-radio--checked`]: this.renderSafeChecked,
[`${cPrefix}-radio--focus`]: this.focus
}
]}
style={this.cssVars as CSSProperties}
@ -79,7 +89,7 @@ export default defineComponent({
<input
ref="inputRef"
type="radio"
class="n-radio__radio-input"
class={`${cPrefix}-radio__radio-input`}
value={this.value}
name={this.mergedName}
checked={this.renderSafeChecked}
@ -90,14 +100,12 @@ export default defineComponent({
/>
<div
class={[
'n-radio__dot',
{
'n-radio__dot--checked': this.renderSafeChecked
}
`${cPrefix}-radio__dot`,
this.renderSafeChecked && `${cPrefix}-radio__dot--checked`
]}
/>
{$slots.default ? (
<div ref="labelRef" class="n-radio__label">
<div ref="labelRef" class={`${cPrefix}-radio__label`}>
{$slots.default()}
</div>
) : null}

View File

@ -1,6 +1,9 @@
import { h, defineComponent } from 'vue'
import type { ExtractPublicPropTypes } from '../../_utils'
import useRadio from './use-radio'
export type RadioButtonProps = ExtractPublicPropTypes<typeof useRadio.props>
export default defineComponent({
name: 'RadioButton',
props: useRadio.props,
@ -8,14 +11,15 @@ export default defineComponent({
return useRadio(props)
},
render () {
const { cPrefix } = this
return (
<div
class={[
'n-radio-button',
`${cPrefix}-radio-button`,
{
'n-radio-button--disabled': this.mergedDisabled,
'n-radio-button--checked': this.renderSafeChecked,
'n-radio-button--focus': this.focus
[`${cPrefix}-radio-button--disabled`]: this.mergedDisabled,
[`${cPrefix}-radio-button--checked`]: this.renderSafeChecked,
[`${cPrefix}-radio-button--focus`]: this.focus
}
]}
onKeyup={this.handleKeyUp}
@ -25,7 +29,7 @@ export default defineComponent({
<input
ref="inputRef"
type="radio"
class="n-radio-button__radio-input"
class={`${cPrefix}-radio-button__radio-input`}
value={this.value}
name={this.mergedName}
checked={this.renderSafeChecked}
@ -34,7 +38,7 @@ export default defineComponent({
onFocus={this.handleRadioInputFocus}
onBlur={this.handleRadioInputBlur}
/>
<div class="n-radio-button__state-border" />
<div class={`${cPrefix}-radio-button__state-border`} />
<span ref="labelRef">{this.$slots}</span>
</div>
)

View File

@ -7,22 +7,24 @@ import {
provide,
ref,
toRef,
reactive,
VNodeChild,
CSSProperties
} from 'vue'
import { useMergedState } from 'vooks'
import { useTheme, useFormItem } from '../../_mixins'
import { useTheme, useFormItem, useConfig } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { getSlot, warn, createKey, call, flatten } from '../../_utils'
import type { ExtractPublicPropTypes } from '../../_utils'
import { radioLight } from '../styles'
import type { RadioTheme } from '../styles'
import type { RadioProps, RadioGroupInjection } from './use-radio'
import type { RadioProps } from './use-radio'
import { radioGroupInjectionKey } from './use-radio'
import style from './styles/radio-group.cssr'
function mapSlot (
defaultSlot: VNode[],
value: string | number | null
value: string | number | null,
clsPrefix: string
): {
children: VNodeChild[]
isButtonGroup: boolean
@ -68,19 +70,19 @@ function mapSlot (
const currentInstancePriority =
(currentInstanceChecked ? 2 : 0) + (!currentInstanceDisabled ? 1 : 0)
const lastInstanceClass = {
'n-radio-group__splitor--disabled': lastInstanceDisabled,
'n-radio-group__splitor--checked': lastInstanceChecked
[`${clsPrefix}-radio-group__splitor--disabled`]: lastInstanceDisabled,
[`${clsPrefix}-radio-group__splitor--checked`]: lastInstanceChecked
}
const currentInstanceClass = {
'n-radio-group__splitor--disabled': currentInstanceDisabled,
'n-radio-group__splitor--checked': currentInstanceChecked
[`${clsPrefix}-radio-group__splitor--disabled`]: currentInstanceDisabled,
[`${clsPrefix}-radio-group__splitor--checked`]: currentInstanceChecked
}
const splitorClass =
lastInstancePriority < currentInstancePriority
? currentInstanceClass
: lastInstanceClass
children.push(
<div class={['n-radio-group__splitor', splitorClass]}></div>,
<div class={[`${clsPrefix}-radio-group__splitor`, splitorClass]}></div>,
wrappedInstance
)
}
@ -91,49 +93,61 @@ function mapSlot (
}
}
const radioGroupProps = {
...(useTheme.props as ThemeProps<RadioTheme>),
name: String,
value: {
type: [String, Number] as PropType<string | number | undefined | null>
},
defaultValue: {
type: [String, Number] as PropType<string | number | null>,
default: null
},
size: {
type: String as PropType<'small' | 'medium' | 'large' | undefined>,
default: undefined
},
disabled: {
type: Boolean,
default: false
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': Function as PropType<(value: string | number) => void>,
onUpdateValue: Function as PropType<(value: string | number) => void>,
// deprecated
onChange: {
type: (Function as unknown) as PropType<
((value: string | number) => void) | undefined
>,
validator: () => {
if (__DEV__) {
warn(
'radio-group',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
} as const
export type RadioGroupProps = ExtractPublicPropTypes<typeof radioGroupProps>
export default defineComponent({
name: 'RadioGroup',
props: {
...(useTheme.props as ThemeProps<RadioTheme>),
name: String,
value: {
type: [String, Number] as PropType<string | number | undefined | null>
},
defaultValue: {
type: [String, Number] as PropType<string | number | null>,
default: null
},
size: {
type: String as PropType<'small' | 'medium' | 'large' | undefined>,
default: undefined
},
disabled: {
type: Boolean,
default: false
},
// eslint-disable-next-line vue/prop-name-casing
'onUpdate:value': Function as PropType<(value: string | number) => void>,
onUpdateValue: Function as PropType<(value: string | number) => void>,
// deprecated
onChange: {
type: (Function as unknown) as PropType<
((value: string | number) => void) | undefined
>,
validator: () => {
if (__DEV__) {
warn(
'radio-group',
'`on-change` is deprecated, please use `on-update:value` instead.'
)
}
return true
},
default: undefined
}
},
props: radioGroupProps,
setup (props) {
const formItem = useFormItem(props)
const themeRef = useTheme('Radio', 'RadioGroup', style, radioLight, props)
const { mergedClsPrefix } = useConfig(props)
const themeRef = useTheme(
'Radio',
'RadioGroup',
style,
radioLight,
props,
mergedClsPrefix
)
const { mergedSize: mergedSizeRef } = formItem
const uncontrolledValueRef = ref(props.defaultValue)
const controlledValueRef = toRef(props, 'value')
@ -158,17 +172,16 @@ export default defineComponent({
}
uncontrolledValueRef.value = value
}
provide<RadioGroupInjection>(
'NRadioGroup',
reactive({
name: toRef(props, 'name'),
value: mergedValueRef,
doUpdateValue,
disabled: toRef(props, 'disabled'),
mergedSize: formItem.mergedSize
})
)
provide(radioGroupInjectionKey, {
cPrefixRef: mergedClsPrefix,
nameRef: toRef(props, 'name'),
valueRef: mergedValueRef,
disabledRef: toRef(props, 'disabled'),
mergedSizeRef: formItem.mergedSize,
doUpdateValue
})
return {
cPrefix: mergedClsPrefix,
mergedValue: mergedValueRef,
cssVars: computed(() => {
const { value: size } = mergedSizeRef
@ -208,16 +221,17 @@ export default defineComponent({
}
},
render () {
const { mergedValue } = this
const { mergedValue, cPrefix } = this
const { children, isButtonGroup } = mapSlot(
flatten(getSlot(this)),
mergedValue
mergedValue,
cPrefix
)
return (
<div
class={[
'n-radio-group',
isButtonGroup && 'n-radio-group--button-group'
`${cPrefix}-radio-group`,
isButtonGroup && `${cPrefix}-radio-group--button-group`
]}
style={this.cssVars as CSSProperties}
>

View File

@ -5,11 +5,13 @@ import {
ExtractPropTypes,
PropType,
Ref,
ComputedRef
ComputedRef,
InjectionKey
} from 'vue'
import { useMemo, useMergedState } from 'vooks'
import { useFormItem } from '../../_mixins'
import { warn, call, MaybeArray } from '../../_utils'
import { useConfig, useFormItem } from '../../_mixins'
import { warn, call } from '../../_utils'
import type { MaybeArray } from '../../_utils'
const radioProps = {
name: String,
@ -48,14 +50,20 @@ const radioProps = {
} as const
export interface RadioGroupInjection {
name: string | undefined
value: string | number | null
mergedSize: 'small' | 'medium' | 'large'
disabled: boolean
cPrefixRef: Ref<string>
nameRef: Ref<string | undefined>
valueRef: Ref<string | number | null>
mergedSizeRef: Ref<'small' | 'medium' | 'large'>
disabledRef: Ref<boolean>
doUpdateValue: (value: string | number) => void
}
export const radioGroupInjectionKey: InjectionKey<RadioGroupInjection> = Symbol(
'radioGroup'
)
export interface UseRadio {
cPrefix: Ref<string>
inputRef: Ref<HTMLElement | null>
labelRef: Ref<HTMLElement | null>
mergedName: Ref<string | undefined>
@ -78,7 +86,9 @@ function setup (props: ExtractPropTypes<typeof radioProps>): UseRadio {
const { size } = props
if (size !== undefined) return size
if (NRadioGroup) {
const { mergedSize } = NRadioGroup
const {
mergedSizeRef: { value: mergedSize }
} = NRadioGroup
if (mergedSize !== undefined) {
return mergedSize
}
@ -91,7 +101,7 @@ function setup (props: ExtractPropTypes<typeof radioProps>): UseRadio {
})
const inputRef = ref<HTMLElement | null>(null)
const labelRef = ref<HTMLElement | null>(null)
const NRadioGroup = inject<RadioGroupInjection | null>('NRadioGroup', null)
const NRadioGroup = inject(radioGroupInjectionKey, null)
const uncontrolledCheckedRef = ref(props.defaultChecked)
const controlledCheckedRef = toRef(props, 'checked')
const mergedCheckedRef = useMergedState(
@ -99,16 +109,16 @@ function setup (props: ExtractPropTypes<typeof radioProps>): UseRadio {
uncontrolledCheckedRef
)
const renderSafeCheckedRef = useMemo(() => {
if (NRadioGroup) return NRadioGroup.value === props.value
if (NRadioGroup) return NRadioGroup.valueRef.value === props.value
return mergedCheckedRef.value
})
const mergedNameRef = useMemo(() => {
const { name } = props
if (name !== undefined) return name
if (NRadioGroup) return NRadioGroup.name
if (NRadioGroup) return NRadioGroup.nameRef.value
})
const mergedDisabledRef = useMemo(() => {
return NRadioGroup?.disabled || props.disabled
return NRadioGroup?.disabledRef.value || props.disabled
})
const focusRef = ref(false)
function doUpdateChecked (): void {
@ -158,6 +168,9 @@ function setup (props: ExtractPropTypes<typeof radioProps>): UseRadio {
inputRef.value?.click()
}
return {
cPrefix: NRadioGroup
? NRadioGroup.cPrefixRef
: useConfig(props).mergedClsPrefix,
inputRef,
labelRef,
mergedName: mergedNameRef,