Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(FormGroup): simplify bindings between input and form group p… #704

Merged
merged 12 commits into from
Sep 21, 2023
Merged
2 changes: 1 addition & 1 deletion docs/components/content/examples/FormExampleElements.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ async function submit (event: FormSubmitEvent<Schema>) {
</UFormGroup>

<UFormGroup name="checkbox" label="Checkbox">
<UCheckbox v-model="state.checkbox" />
<UCheckbox v-model="state.checkbox" label="Check me" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add a label here? This should with UFormGroup right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checkbox components and Radio are a bit different from the others because a label is included in the component:

https://github.com/nuxt/ui/pull/704/files#diff-eeb89ce166ecb860614d9cb79ad8b64476eaf9a919c31dfccb89c50052c2c728R21

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but both behaviour should work, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@benjamincanac benjamincanac Sep 21, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does clicking on the Checkbox label (one from FormGroup) works for you? it doesn't for me.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sorry I could not see straight, just fixed it!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label of the Radio components doesn't seem to work while the one from the FormGroup does.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, note that clicking on the FormGroup's label for Radio groups does not work properly, I'll fix this in another PR introducing a wrapper component (will also fix #684).

</UFormGroup>

<UFormGroup name="radio" label="Radio">
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 11 additions & 4 deletions src/runtime/components/forms/Checkbox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div :class="ui.wrapper">
<div class="flex items-center h-5">
<input
:id="name"
:id="id"
v-model="toggle"
:name="name"
:required="required"
Expand All @@ -18,7 +18,7 @@
>
</div>
<div v-if="label || $slots.label" class="ms-3 text-sm">
<label :for="name" :class="ui.label">
<label :for="id" :class="ui.label">
<slot name="label">{{ label }}</slot>
<span v-if="required" :class="ui.required">*</span>
</label>
Expand All @@ -36,6 +36,7 @@ import { twMerge, twJoin } from 'tailwind-merge'
import { useUI } from '../../composables/useUI'
import { useFormGroup } from '../../composables/useFormGroup'
import { mergeConfig } from '../../utils'
import { uid } from '../../utils/uid'
import type { Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
Expand All @@ -47,6 +48,11 @@ const config = mergeConfig<typeof checkbox>(appConfig.ui.strategy, appConfig.ui.
export default defineComponent({
inheritAttrs: false,
props: {
id: {
type: String,
// A default value is needed here to bind the label
default: () => uid()
},
value: {
type: [String, Number, Boolean, Object],
default: null
Expand Down Expand Up @@ -103,8 +109,7 @@ export default defineComponent({
setup (props, { emit }) {
const { ui, attrs } = useUI('ui.checkbox', props.ui, config, { mergeWrapper: true })

const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const { emitFormChange, color, name } = useFormGroup(props)

const toggle = computed({
get () {
Expand Down Expand Up @@ -137,6 +142,8 @@ export default defineComponent({
attrs,
toggle,
// eslint-disable-next-line vue/no-dupe-keys
name,
// eslint-disable-next-line vue/no-dupe-keys
inputClass,
onChange
}
Expand Down
4 changes: 2 additions & 2 deletions src/runtime/components/forms/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { ValidationError as JoiError, Schema as JoiSchema } from 'joi'
import type { ObjectSchema as YupObjectSchema, ValidationError as YupError } from 'yup'
import type { ObjectSchemaAsync as ValibotObjectSchema } from 'valibot'
import type { FormError, FormEvent, FormEventType, FormSubmitEvent, Form } from '../../types/form'
import { uid } from '../../utils/uid'

export default defineComponent({
props: {
Expand Down Expand Up @@ -40,8 +41,7 @@ export default defineComponent({
},
emits: ['submit'],
setup (props, { expose, emit }) {
const seed = Math.random().toString(36).substring(7)
const bus = useEventBus<FormEvent>(`form-${seed}`)
const bus = useEventBus<FormEvent>(`form-${uid()}`)

bus.on(async (event) => {
if (event.type !== 'submit' && props.validateOn?.includes(event.type)) {
Expand Down
11 changes: 5 additions & 6 deletions src/runtime/components/forms/FormGroup.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div :class="ui.wrapper" v-bind="attrs">
<div v-if="label" :class="[ui.label.wrapper, size]">
<label :for="labelFor" :class="[ui.label.base, required ? ui.label.required : '']">{{ label }}</label>
<label :for="inputId" :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]">
Expand All @@ -28,11 +28,10 @@ import type { FormError, InjectedFormGroupValue, Strategy } from '../../types'
// @ts-expect-error
import appConfig from '#build/app.config'
import { formGroup } from '#ui/ui.config'
import { uid } from '../../utils/uid'

const config = mergeConfig<typeof formGroup>(appConfig.ui.strategy, appConfig.ui.formGroup, formGroup)

let increment = 0

export default defineComponent({
inheritAttrs: false,
props: {
Expand Down Expand Up @@ -88,11 +87,11 @@ export default defineComponent({
})

const size = computed(() => ui.value.size[props.size ?? config.default.size])
const labelFor = ref(`${props.name || 'lf'}-${increment = increment < 1000000 ? increment + 1 : 0}`)
const inputId = ref(uid())

provide<InjectedFormGroupValue>('form-group', {
error,
labelFor,
inputId,
name: computed(() => props.name),
size: computed(() => props.size)
})
Expand All @@ -101,7 +100,7 @@ export default defineComponent({
// eslint-disable-next-line vue/no-dupe-keys
ui,
attrs,
labelFor,
inputId,
// eslint-disable-next-line vue/no-dupe-keys
size,
// eslint-disable-next-line vue/no-dupe-keys
Expand Down
12 changes: 5 additions & 7 deletions src/runtime/components/forms/Input.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div :class="ui.wrapper">
<input
:id="id"
:id="inputId"
ref="input"
:name="name"
:value="modelValue"
Expand Down Expand Up @@ -119,7 +119,7 @@ export default defineComponent({
},
size: {
type: String as PropType<keyof typeof config.size>,
default: () => config.default.size,
default: null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@romhml Why did you move the default to the useFormGroup composable?

validator (value: string) {
return Object.keys(config.size).includes(value)
}
Expand Down Expand Up @@ -154,10 +154,7 @@ export default defineComponent({
setup (props, { emit, slots }) {
const { ui, attrs } = useUI('ui.input', props.ui, config, { mergeWrapper: true })

const { emitFormBlur, emitFormInput, formGroup } = useFormGroup(props)
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const size = computed(() => formGroup?.size?.value ?? props.size)
const id = formGroup?.labelFor
const { emitFormBlur, emitFormInput, size, color, inputId, name } = useFormGroup(props, config)

const input = ref<HTMLInputElement | null>(null)

Expand Down Expand Up @@ -261,7 +258,8 @@ export default defineComponent({
ui,
attrs,
// eslint-disable-next-line vue/no-dupe-keys
id,
name,
inputId,
input,
isLeading,
isTrailing,
Expand Down
15 changes: 11 additions & 4 deletions src/runtime/components/forms/Radio.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div :class="ui.wrapper">
<div class="flex items-center h-5">
<input
:id="`${name}-${value}`"
:id="id"
v-model="pick"
:name="name"
:required="required"
Expand All @@ -15,7 +15,7 @@
>
</div>
<div v-if="label || $slots.label" class="ms-3 text-sm">
<label :for="`${name}-${value}`" :class="ui.label">
<label :for="id" :class="ui.label">
<slot name="label">{{ label }}</slot>
<span v-if="required" :class="ui.required">*</span>
</label>
Expand All @@ -38,12 +38,18 @@ import type { Strategy } from '../../types'
import appConfig from '#build/app.config'
import { radio } from '#ui/ui.config'
import colors from '#ui-colors'
import { uid } from '../../utils/uid'

const config = mergeConfig<typeof radio>(appConfig.ui.strategy, appConfig.ui.radio, radio)

export default defineComponent({
inheritAttrs: false,
props: {
id: {
type: String,
// A default value is needed here to bind the label
default: () => uid()
},
value: {
type: [String, Number, Boolean],
default: null
Expand Down Expand Up @@ -92,8 +98,7 @@ export default defineComponent({
setup (props, { emit }) {
const { ui, attrs } = useUI('ui.radio', props.ui, config, { mergeWrapper: true })

const { emitFormChange, formGroup } = useFormGroup()
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const { emitFormChange, color, name } = useFormGroup(props)

const pick = computed({
get () {
Expand Down Expand Up @@ -123,6 +128,8 @@ export default defineComponent({
attrs,
pick,
// eslint-disable-next-line vue/no-dupe-keys
name,
// eslint-disable-next-line vue/no-dupe-keys
inputClass
}
}
Expand Down
12 changes: 5 additions & 7 deletions src/runtime/components/forms/Range.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div :class="wrapperClass">
<input
:id="id"
:id="inputId"
ref="input"
v-model.number="value"
:name="name"
Expand Down Expand Up @@ -67,7 +67,7 @@ export default defineComponent({
},
size: {
type: String as PropType<keyof typeof config.size>,
default: () => config.default.size,
default: null,
validator (value: string) {
return Object.keys(config.size).includes(value)
}
Expand All @@ -92,10 +92,7 @@ export default defineComponent({
setup (props, { emit }) {
const { ui, attrs, attrsClass } = useUI('ui.range', props.ui, config)

const { emitFormChange, formGroup } = useFormGroup(props)
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const size = computed(() => formGroup?.size?.value ?? props.size)
const id = formGroup?.labelFor
const { emitFormChange, inputId, color, size, name } = useFormGroup(props, config)

const value = computed({
get () {
Expand Down Expand Up @@ -171,7 +168,8 @@ export default defineComponent({
ui,
attrs,
// eslint-disable-next-line vue/no-dupe-keys
id,
name,
inputId,
value,
wrapperClass,
// eslint-disable-next-line vue/no-dupe-keys
Expand Down
12 changes: 5 additions & 7 deletions src/runtime/components/forms/Select.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<template>
<div :class="ui.wrapper">
<select
:id="id"
:id="inputId"
:name="name"
:value="modelValue"
:required="required"
Expand Down Expand Up @@ -137,7 +137,7 @@ export default defineComponent({
},
size: {
type: String as PropType<keyof typeof config.size>,
default: () => config.default.size,
default: null,
validator (value: string) {
return Object.keys(config.size).includes(value)
}
Expand Down Expand Up @@ -180,10 +180,7 @@ export default defineComponent({
setup (props, { emit, slots }) {
const { ui, attrs } = useUI('ui.select', props.ui, config, { mergeWrapper: true })

const { emitFormChange, formGroup } = useFormGroup(props)
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const size = computed(() => formGroup?.size?.value ?? props.size)
const id = formGroup?.labelFor
const { emitFormChange, inputId, color, size, name } = useFormGroup(props, config)

const onInput = (event: InputEvent) => {
emit('update:modelValue', (event.target as HTMLInputElement).value)
Expand Down Expand Up @@ -323,7 +320,8 @@ export default defineComponent({
ui,
attrs,
// eslint-disable-next-line vue/no-dupe-keys
id,
name,
inputId,
normalizedOptionsWithPlaceholder,
normalizedValue,
isLeading,
Expand Down
12 changes: 5 additions & 7 deletions src/runtime/components/forms/SelectMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class="inline-flex w-full"
>
<slot :open="open" :disabled="disabled" :loading="loading">
<button :id="id" :class="selectClass" :disabled="disabled || loading" type="button" v-bind="attrs">
<button :id="inputId" :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" />
Expand Down Expand Up @@ -254,7 +254,7 @@ export default defineComponent({
},
size: {
type: String as PropType<keyof typeof config.size>,
default: () => config.default.size,
default: null,
validator (value: string) {
return Object.keys(config.size).includes(value)
}
Expand Down Expand Up @@ -314,10 +314,7 @@ 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(props)
const color = computed(() => formGroup?.error?.value ? 'red' : props.color)
const size = computed(() => formGroup?.size?.value ?? props.size)
const id = formGroup?.labelFor
const { emitFormBlur, emitFormChange, inputId, color, size, name } = useFormGroup(props, config)

const query = ref('')
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
Expand Down Expand Up @@ -446,7 +443,8 @@ export default defineComponent({
uiMenu,
attrs,
// eslint-disable-next-line vue/no-dupe-keys
id,
name,
inputId,
trigger,
container,
isLeading,
Expand Down
Loading