mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-18 14:08:06 +01:00
Compare commits
2 Commits
v3
...
feat/form-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af25f65e81 | ||
|
|
5829aebe7d |
@@ -5,7 +5,9 @@ const sizes = Object.keys(theme.variants.size) as Array<keyof typeof theme.varia
|
|||||||
|
|
||||||
const feedbacks = [
|
const feedbacks = [
|
||||||
{ description: 'This is a description' },
|
{ description: 'This is a description' },
|
||||||
|
{ error: true },
|
||||||
{ error: 'This is an error' },
|
{ error: 'This is an error' },
|
||||||
|
{ errors: ['This is an error', 'This is another error', 'This one is not visible'], maxErrors: 2 },
|
||||||
{ hint: 'This is a hint' },
|
{ hint: 'This is a hint' },
|
||||||
{ help: 'Help! I need somebody!' },
|
{ help: 'Help! I need somebody!' },
|
||||||
{ required: true }
|
{ required: true }
|
||||||
@@ -14,7 +16,7 @@ const feedbacks = [
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col items-center gap-4">
|
<div class="flex flex-col items-center gap-4">
|
||||||
<div class="flex flex-col gap-4 ms-[-38px]">
|
<div class="flex flex-col gap-4 ms-[-92px]">
|
||||||
<div v-for="(feedback, count) in feedbacks" :key="count" class="flex items-center">
|
<div v-for="(feedback, count) in feedbacks" :key="count" class="flex items-center">
|
||||||
<UFormField v-bind="feedback" label="Email" name="email">
|
<UFormField v-bind="feedback" label="Email" name="email">
|
||||||
<UInput placeholder="john@lennon.com" />
|
<UInput placeholder="john@lennon.com" />
|
||||||
|
|||||||
@@ -19,6 +19,20 @@ export interface FormFieldProps {
|
|||||||
description?: string
|
description?: string
|
||||||
help?: string
|
help?: string
|
||||||
error?: string | boolean
|
error?: string | boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An array of errors for this field.
|
||||||
|
* Note that only one error is displayed by default. You can use `maxErrors` to control the number of displayed errors.
|
||||||
|
* @defaultValue `1`
|
||||||
|
*/
|
||||||
|
errors?: string[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of errors to display. If `false` or negative, display all available errors.
|
||||||
|
* @defaultValue `1`
|
||||||
|
*/
|
||||||
|
maxErrors?: number | false
|
||||||
|
|
||||||
hint?: string
|
hint?: string
|
||||||
/**
|
/**
|
||||||
* @defaultValue 'md'
|
* @defaultValue 'md'
|
||||||
@@ -42,7 +56,7 @@ export interface FormFieldSlots {
|
|||||||
description(props: { description?: string }): any
|
description(props: { description?: string }): any
|
||||||
help(props: { help?: string }): any
|
help(props: { help?: string }): any
|
||||||
error(props: { error?: string | boolean }): any
|
error(props: { error?: string | boolean }): any
|
||||||
default(props: { error?: string | boolean }): any
|
default(props: { error?: string | boolean, errors?: string[] }): any
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -54,7 +68,7 @@ import { formFieldInjectionKey, inputIdInjectionKey } from '../composables/useFo
|
|||||||
import { tv } from '../utils/tv'
|
import { tv } from '../utils/tv'
|
||||||
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
import type { FormError, FormFieldInjectedOptions } from '../types/form'
|
||||||
|
|
||||||
const props = defineProps<FormFieldProps>()
|
const props = withDefaults(defineProps<FormFieldProps>(), { maxErrors: 1 })
|
||||||
const slots = defineSlots<FormFieldSlots>()
|
const slots = defineSlots<FormFieldSlots>()
|
||||||
|
|
||||||
const appConfig = useAppConfig() as FormField['AppConfig']
|
const appConfig = useAppConfig() as FormField['AppConfig']
|
||||||
@@ -66,7 +80,24 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.formField ||
|
|||||||
|
|
||||||
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
const formErrors = inject<Ref<FormError[]> | null>('form-errors', null)
|
||||||
|
|
||||||
const error = computed(() => props.error || formErrors?.value?.find(error => error.name && (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))))?.message)
|
const errors = computed(() =>
|
||||||
|
(props.error && typeof props.error === 'string' ? [props.error] : props.errors)
|
||||||
|
|| formErrors?.value?.flatMap((error) => {
|
||||||
|
if (!error.name) return []
|
||||||
|
if (error.name === props.name || (props.errorPattern && error.name.match(props.errorPattern))) {
|
||||||
|
return [error.message]
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}))
|
||||||
|
|
||||||
|
const error = computed(() => errors.value?.[0] ?? props.error)
|
||||||
|
|
||||||
|
const displayedErrors = computed(() =>
|
||||||
|
props.maxErrors === false
|
||||||
|
|| (!!props.maxErrors && props.maxErrors < 0)
|
||||||
|
? errors.value
|
||||||
|
: errors.value?.slice(0, props.maxErrors)
|
||||||
|
)
|
||||||
|
|
||||||
const id = ref(useId())
|
const id = ref(useId())
|
||||||
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
// Copies id's initial value to bind aria-attributes such as aria-describedby.
|
||||||
@@ -113,9 +144,16 @@ provide(formFieldInjectionKey, computed(() => ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
<div :class="[(label || !!slots.label || description || !!slots.description) && ui.container({ class: props.ui?.container })]">
|
||||||
<slot :error="error" />
|
<slot :error="error" :errors="errors" />
|
||||||
|
|
||||||
<div v-if="(typeof error === 'string' && error) || !!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
|
<template v-if="(typeof error === 'string' && error)">
|
||||||
|
<div v-for="err in displayedErrors" :id="`${ariaId}-error`" :key="err" :class="ui.error({ class: props.ui?.error })">
|
||||||
|
<slot name="error" :error="err">
|
||||||
|
{{ err }}
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else-if="!!slots.error" :id="`${ariaId}-error`" :class="ui.error({ class: props.ui?.error })">
|
||||||
<slot name="error" :error="error">
|
<slot name="error" :error="error">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</slot>
|
</slot>
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ describe('FormField', () => {
|
|||||||
['with required', { props: { label: 'Username', required: true } }],
|
['with required', { props: { label: 'Username', required: true } }],
|
||||||
['with help', { props: { help: 'Username must be unique' } }],
|
['with help', { props: { help: 'Username must be unique' } }],
|
||||||
['with error', { props: { error: 'Username is already taken' } }],
|
['with error', { props: { error: 'Username is already taken' } }],
|
||||||
|
['with multiple errors', { props: { errors: ['Username is already taken', 'This should not be visible'] } }],
|
||||||
|
['with maxErrors', { props: { maxErrors: 2, errors: ['Username is already taken', 'This should be visible'] } }],
|
||||||
|
['with maxErrors negative', { props: { maxErrors: -1, errors: ['Username is already taken', 'This should be visible', 'This should be visible'] } }],
|
||||||
|
['with maxErrors false', { props: { maxErrors: false, errors: ['Username is already taken', 'This should be visible', 'This should be visible'] } }],
|
||||||
['with hint', { props: { hint: 'Use letters, numbers, and special characters' } }],
|
['with hint', { props: { hint: 'Use letters, numbers, and special characters' } }],
|
||||||
...sizes.map((size: string) => [`with size ${size}`, { props: { label: 'Username', description: 'Enter your username', size } }]),
|
...sizes.map((size: string) => [`with size ${size}`, { props: { label: 'Username', description: 'Enter your username', size } }]),
|
||||||
['with as', { props: { as: 'section' } }],
|
['with as', { props: { as: 'section' } }],
|
||||||
|
|||||||
@@ -148,6 +148,59 @@ exports[`FormField > renders with label slot correctly 1`] = `
|
|||||||
</div>"
|
</div>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors false correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors negative correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with multiple errors correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`FormField > renders with required correctly 1`] = `
|
exports[`FormField > renders with required correctly 1`] = `
|
||||||
"<div class="text-sm">
|
"<div class="text-sm">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
@@ -148,6 +148,59 @@ exports[`FormField > renders with label slot correctly 1`] = `
|
|||||||
</div>"
|
</div>"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors false correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with maxErrors negative correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">This should be visible</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`FormField > renders with multiple errors correctly 1`] = `
|
||||||
|
"<div class="text-sm">
|
||||||
|
<div class="">
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</div>
|
||||||
|
<div class="">
|
||||||
|
<div id="v-0-0-error" class="mt-2 text-error">Username is already taken</div>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`FormField > renders with required correctly 1`] = `
|
exports[`FormField > renders with required correctly 1`] = `
|
||||||
"<div class="text-sm">
|
"<div class="text-sm">
|
||||||
<div class="">
|
<div class="">
|
||||||
|
|||||||
Reference in New Issue
Block a user