From 681f0e5684feaad0c711130404751f2fd65ddbe4 Mon Sep 17 00:00:00 2001 From: Aditio Pangestu <138564696+aditio-eka@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:06:23 +0700 Subject: [PATCH] fix(FormGroup): use explicit label instead of implicit label (#638) --- src/runtime/components/forms/FormGroup.vue | 40 ++++++++++++--------- src/runtime/components/forms/Input.vue | 11 ++++-- src/runtime/components/forms/Range.vue | 11 ++++-- src/runtime/components/forms/Select.vue | 11 ++++-- src/runtime/components/forms/SelectMenu.vue | 12 +++++-- src/runtime/components/forms/Textarea.vue | 13 ++++--- src/runtime/components/forms/Toggle.vue | 11 ++++-- src/runtime/composables/useFormGroup.ts | 14 ++++++-- src/runtime/types/form.d.ts | 7 ++++ 9 files changed, 95 insertions(+), 35 deletions(-) diff --git a/src/runtime/components/forms/FormGroup.vue b/src/runtime/components/forms/FormGroup.vue index baf0b62e33..465fbcda14 100644 --- a/src/runtime/components/forms/FormGroup.vue +++ b/src/runtime/components/forms/FormGroup.vue @@ -1,20 +1,21 @@ - - - {{ label }} - {{ hint }} - - - {{ description }} - - - - - {{ error }} - {{ help }} - - + + {{ label }} + {{ hint }} + + + {{ description }} + + + + + {{ error }} + + + {{ help }} + + @@ -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('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, diff --git a/src/runtime/components/forms/Input.vue b/src/runtime/components/forms/Input.vue index 197f04a891..3321332223 100644 --- a/src/runtime/components/forms/Input.vue +++ b/src/runtime/components/forms/Input.vue @@ -1,6 +1,7 @@ >(() => 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(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, diff --git a/src/runtime/components/forms/Range.vue b/src/runtime/components/forms/Range.vue index b8ae37f59d..c5815778eb 100644 --- a/src/runtime/components/forms/Range.vue +++ b/src/runtime/components/forms/Range.vue @@ -1,6 +1,7 @@ >(() => 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, diff --git a/src/runtime/components/forms/Select.vue b/src/runtime/components/forms/Select.vue index a19e76adc2..aa09ae3173 100644 --- a/src/runtime/components/forms/Select.vue +++ b/src/runtime/components/forms/Select.vue @@ -1,6 +1,7 @@ >(() => 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, diff --git a/src/runtime/components/forms/SelectMenu.vue b/src/runtime/components/forms/SelectMenu.vue index ebb6f7a28d..a1f243f21d 100644 --- a/src/runtime/components/forms/SelectMenu.vue +++ b/src/runtime/components/forms/SelectMenu.vue @@ -28,7 +28,7 @@ class="inline-flex w-full" > - + @@ -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(() => 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>() @@ -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, diff --git a/src/runtime/components/forms/Textarea.vue b/src/runtime/components/forms/Textarea.vue index 48e617886d..80f54a01b1 100644 --- a/src/runtime/components/forms/Textarea.vue +++ b/src/runtime/components/forms/Textarea.vue @@ -1,6 +1,7 @@ >(() => 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 diff --git a/src/runtime/components/forms/Toggle.vue b/src/runtime/components/forms/Toggle.vue index acb3050393..0d6ef32f38 100644 --- a/src/runtime/components/forms/Toggle.vue +++ b/src/runtime/components/forms/Toggle.vue @@ -1,5 +1,6 @@ >(() => 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, diff --git a/src/runtime/composables/useFormGroup.ts b/src/runtime/composables/useFormGroup.ts index f511a37f3b..713a03f781 100644 --- a/src/runtime/composables/useFormGroup.ts +++ b/src/runtime/composables/useFormGroup.ts @@ -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 | undefined>('form-events', undefined) - const formGroup = inject('form-group', undefined) + const formGroup = inject('form-group', undefined) + + if (formGroup) { + formGroup.labelFor.value = inputAttrs?.id ?? formGroup?.labelFor.value + } const blurred = ref(false) diff --git a/src/runtime/types/form.d.ts b/src/runtime/types/form.d.ts index 148fe3c49a..195a25d99b 100644 --- a/src/runtime/types/form.d.ts +++ b/src/runtime/types/form.d.ts @@ -19,3 +19,10 @@ export interface FormEvent { type: FormEventType path: string } + +export interface InjectedFormGroupValue { + labelFor: Ref + name: Ref + size: Ref + error: Ref +} \ No newline at end of file
{{ label }}
{{ description }}
{{ error }}
{{ help }}
+ {{ description }} +
+ {{ error }} +
+ {{ help }} +