mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
fix(FormGroup): use explicit label instead of implicit label (#638)
This commit is contained in:
@@ -1,20 +1,21 @@
|
||||
<template>
|
||||
<div :class="wrapperClass" v-bind="attrs">
|
||||
<label>
|
||||
<div v-if="label" :class="[ui.label.wrapper, size]">
|
||||
<p :class="[ui.label.base, required ? ui.label.required : '']">{{ label }}</p>
|
||||
<span v-if="hint" :class="[ui.hint]">{{ hint }}</span>
|
||||
</div>
|
||||
|
||||
<p v-if="description" :class="[ui.description, size]">{{ description }}</p>
|
||||
|
||||
<div :class="[label ? ui.container : '']" @click="$event.preventDefault()">
|
||||
<slot v-bind="{ error }" />
|
||||
|
||||
<p v-if="error && typeof error !== 'boolean'" :class="[ui.error, size]">{{ error }}</p>
|
||||
<p v-else-if="help" :class="[ui.help, size]">{{ help }}</p>
|
||||
</div>
|
||||
</label>
|
||||
<div v-if="label" :class="[ui.label.wrapper, size]">
|
||||
<label :for="labelFor" :class="[ui.label.base, required ? ui.label.required : '']">{{ label }}</label>
|
||||
<span v-if="hint" :class="[ui.hint]">{{ hint }}</span>
|
||||
</div>
|
||||
<p v-if="description" :class="[ui.description, size]">
|
||||
{{ description }}
|
||||
</p>
|
||||
<div :class="[label ? ui.container : '']">
|
||||
<slot v-bind="{ error }" />
|
||||
<p v-if="typeof error === 'string' && error" :class="[ui.error, size]">
|
||||
{{ error }}
|
||||
</p>
|
||||
<p v-else-if="help" :class="[ui.help, size]">
|
||||
{{ help }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -23,7 +24,7 @@ import { computed, defineComponent, provide, inject } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { omit } from '../../utils/lodash'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { FormError } from '../../types/form'
|
||||
import type { FormError, InjectedFormGroupValue } from '../../types/form'
|
||||
import { defuTwMerge } from '../../utils'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
@@ -32,6 +33,8 @@ import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
let increment = 0
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
@@ -92,14 +95,17 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
const size = computed(() => ui.value.size[props.size ?? appConfig.ui.input.default.size])
|
||||
const labelFor = ref(`${props.name || 'lf'}-${increment = increment < 1000000 ? increment + 1 : 0}`)
|
||||
|
||||
provide('form-group', {
|
||||
provide<InjectedFormGroupValue>('form-group', {
|
||||
error,
|
||||
labelFor,
|
||||
name: computed(() => props.name),
|
||||
size: computed(() => props.size)
|
||||
})
|
||||
|
||||
return {
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<input
|
||||
:id="labelFor"
|
||||
ref="input"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
@@ -59,6 +60,10 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'text'
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -151,9 +156,10 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.input>>(() => defuTwMerge({}, props.ui, appConfig.ui.input))
|
||||
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
const input = ref<HTMLInputElement | null>(null)
|
||||
|
||||
@@ -255,7 +261,8 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
input,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<input
|
||||
:id="labelFor"
|
||||
ref="input"
|
||||
v-model.number="value"
|
||||
:name="name"
|
||||
@@ -37,6 +38,10 @@ export default defineComponent({
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -87,9 +92,10 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.range>>(() => defuTwMerge({}, props.ui, appConfig.ui.range))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const { emitFormChange, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
const value = computed({
|
||||
get () {
|
||||
@@ -161,7 +167,8 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
value,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<select
|
||||
:id="labelFor"
|
||||
:name="name"
|
||||
:value="modelValue"
|
||||
:required="required"
|
||||
@@ -77,6 +78,10 @@ export default defineComponent({
|
||||
type: [String, Number, Object],
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -177,9 +182,10 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.select>>(() => defuTwMerge({}, props.ui, appConfig.ui.select))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const { emitFormChange, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
|
||||
const onInput = (event: InputEvent) => {
|
||||
@@ -318,9 +324,10 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
labelFor,
|
||||
normalizedOptionsWithPlaceholder,
|
||||
normalizedValue,
|
||||
isLeading,
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
class="inline-flex w-full"
|
||||
>
|
||||
<slot :open="open" :disabled="disabled" :loading="loading">
|
||||
<button :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
|
||||
<button :id="labelFor" :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
|
||||
<span v-if="(isLeading && leadingIconName) || $slots.leading" :class="leadingWrapperIconClass">
|
||||
<slot name="leading" :disabled="disabled" :loading="loading">
|
||||
<UIcon :name="leadingIconName" :class="leadingIconClass" />
|
||||
@@ -174,6 +174,10 @@ export default defineComponent({
|
||||
type: Array as PropType<{ [key: string]: any, disabled?: boolean }[] | string[]>,
|
||||
default: () => []
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -310,9 +314,10 @@ export default defineComponent({
|
||||
const popper = computed<PopperOptions>(() => defu({}, props.popper, uiMenu.value.popper as PopperOptions))
|
||||
|
||||
const [trigger, container] = usePopper(popper.value)
|
||||
const { emitFormBlur, emitFormChange, formGroup } = useFormGroup()
|
||||
const { emitFormBlur, emitFormChange, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
const query = ref('')
|
||||
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
|
||||
@@ -437,7 +442,8 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
uiMenu,
|
||||
trigger,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div :class="wrapperClass">
|
||||
<textarea
|
||||
:id="labelFor"
|
||||
ref="textarea"
|
||||
:value="modelValue"
|
||||
:name="name"
|
||||
@@ -38,6 +39,10 @@ export default defineComponent({
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -116,9 +121,10 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.textarea>>(() => defuTwMerge({}, props.ui, appConfig.ui.textarea))
|
||||
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup()
|
||||
const { emitFormBlur, emitFormInput, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const size = computed(() => formGroup?.size?.value ?? props.size)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
const autoFocus = () => {
|
||||
if (props.autofocus) {
|
||||
@@ -194,9 +200,8 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
textarea,
|
||||
wrapperClass,
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<HSwitch
|
||||
:id="labelFor"
|
||||
v-model="active"
|
||||
:name="name"
|
||||
:disabled="disabled"
|
||||
@@ -40,6 +41,10 @@ export default defineComponent({
|
||||
},
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: null
|
||||
@@ -79,8 +84,9 @@ export default defineComponent({
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.toggle>>(() => defuTwMerge({}, props.ui, appConfig.ui.toggle))
|
||||
|
||||
const { emitFormChange, formGroup } = useFormGroup()
|
||||
const { emitFormChange, formGroup } = useFormGroup(props)
|
||||
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
|
||||
const labelFor = formGroup?.labelFor
|
||||
|
||||
const active = computed({
|
||||
get () {
|
||||
@@ -114,7 +120,8 @@ export default defineComponent({
|
||||
})
|
||||
|
||||
return {
|
||||
attrs: computed(() => omit(attrs, ['class'])),
|
||||
labelFor,
|
||||
attrs: computed(() => omit(attrs, ['class', labelFor ? 'id' : null ])),
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
active,
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { inject, ref } from 'vue'
|
||||
import { type UseEventBusReturn, useDebounceFn } from '@vueuse/core'
|
||||
import type { FormEvent, FormEventType } from '../types/form'
|
||||
import type { FormEvent, FormEventType, InjectedFormGroupValue } from '../types/form'
|
||||
|
||||
export const useFormGroup = () => {
|
||||
type InputAttrs = {
|
||||
id?: string
|
||||
}
|
||||
|
||||
export const useFormGroup = (inputAttrs?: InputAttrs) => {
|
||||
const formBus = inject<UseEventBusReturn<FormEvent, string> | undefined>('form-events', undefined)
|
||||
const formGroup = inject('form-group', undefined)
|
||||
const formGroup = inject<InjectedFormGroupValue>('form-group', undefined)
|
||||
|
||||
if (formGroup) {
|
||||
formGroup.labelFor.value = inputAttrs?.id ?? formGroup?.labelFor.value
|
||||
}
|
||||
|
||||
const blurred = ref(false)
|
||||
|
||||
|
||||
7
src/runtime/types/form.d.ts
vendored
7
src/runtime/types/form.d.ts
vendored
@@ -19,3 +19,10 @@ export interface FormEvent {
|
||||
type: FormEventType
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface InjectedFormGroupValue {
|
||||
labelFor: Ref<string>
|
||||
name: Ref<string>
|
||||
size: Ref<string>
|
||||
error: Ref<string | boolean>
|
||||
}
|
||||
Reference in New Issue
Block a user