mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
docs: update
This commit is contained in:
@@ -1,94 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
import * as theme from '#build/ui'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const componentTheme = theme[camelName]
|
||||
const componentMeta = await useComponentMeta(name as any)
|
||||
|
||||
const meta: ComputedRef<ComponentMeta> = computed(() => {
|
||||
const meta = componentMeta.value.meta
|
||||
|
||||
if (meta.props?.length) {
|
||||
meta.props = meta.props.map((prop) => {
|
||||
prop.default = prop.default ?? componentTheme.defaultVariants?.[prop.name]
|
||||
return prop
|
||||
})
|
||||
}
|
||||
|
||||
return meta
|
||||
})
|
||||
|
||||
const { data: ast } = await useAsyncData(`${name}-api`, () => parseMarkdown(`
|
||||
## API
|
||||
|
||||
${section('props')}
|
||||
${section('slots')}
|
||||
${section('events')}
|
||||
`))
|
||||
|
||||
function section(type: string) {
|
||||
const columns = {
|
||||
props: [{ key: 'name', label: 'Prop' }, { key: 'default', label: 'Default' }, { key: 'type', addKey: 'description', label: 'Type' }],
|
||||
slots: [{ key: 'name', label: 'Slot' }, { key: 'type', addKey: 'description', label: 'Type' }],
|
||||
events: [{ key: 'name', label: 'Event' }, { key: 'type', addKey: 'description', label: 'Type' }]
|
||||
}
|
||||
|
||||
const items = meta.value[type]
|
||||
if (!items?.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `
|
||||
### ${upperFirst(type)}
|
||||
|
||||
${table(items, columns[type])}
|
||||
`
|
||||
}
|
||||
|
||||
function table(items: any[], columns?: { key: string, addKey?: string, label: string }[], align: string = 'left') {
|
||||
let table = ''
|
||||
const separator = {
|
||||
left: ':---',
|
||||
right: '---:',
|
||||
center: '---'
|
||||
}
|
||||
|
||||
// Generate columns
|
||||
const cols = columns?.length ? columns : Object.keys(items[0]).map(key => ({ key, label: upperFirst(key) }))
|
||||
|
||||
// Generate table headers
|
||||
table += cols.map(col => col.label).join(' | ')
|
||||
table += '\r\n'
|
||||
|
||||
// Generate table separator
|
||||
table += cols.map(() => {
|
||||
return separator[align] || separator.center
|
||||
}).join(' | ')
|
||||
table += '\r\n'
|
||||
|
||||
// Generate table body
|
||||
items.forEach((item) => {
|
||||
table += cols.map((col) => {
|
||||
let code = item[col.key] ? `<code>${item[col.key].replaceAll('|', '|')}</code>` : ''
|
||||
|
||||
if (col.addKey) {
|
||||
code += item[col.addKey] ? `<p class="!mt-2">${item[col.addKey].replaceAll('\n\n', '<br>').replaceAll('\n', '<br>')}</p>` : ''
|
||||
}
|
||||
|
||||
return code
|
||||
}).join(' | ') + '\r\n'
|
||||
})
|
||||
|
||||
return table
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<MDCRenderer v-if="ast && (meta.props?.length || meta.slots?.length || meta.events?.length)" :body="ast.body" :data="ast.data" />
|
||||
</template>
|
||||
@@ -1,15 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const componentMeta = await useComponentMeta(name as any)
|
||||
|
||||
const meta: ComputedRef<ComponentMeta> = computed(() => componentMeta.value.meta)
|
||||
const meta = await fetchComponentMeta(name as any)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -25,7 +22,7 @@ const meta: ComputedRef<ComponentMeta> = computed(() => componentMeta.value.meta
|
||||
</ProseTr>
|
||||
</ProseThead>
|
||||
<ProseTbody>
|
||||
<ProseTr v-for="event in meta.events" :key="event.name">
|
||||
<ProseTr v-for="event in meta.meta.events" :key="event.name">
|
||||
<ProseTd>
|
||||
<ProseCodeInline class="text-primary-500 dark:text-primary-400">
|
||||
{{ event.name }}
|
||||
|
||||
@@ -9,22 +9,18 @@ const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const componentTheme = theme[camelName]
|
||||
const componentMeta = await useComponentMeta(name as any)
|
||||
const meta = await fetchComponentMeta(name as any)
|
||||
|
||||
const meta: ComputedRef<ComponentMeta> = computed(() => {
|
||||
const meta = componentMeta.value.meta
|
||||
|
||||
if (meta.props?.length) {
|
||||
meta.props = meta.props.map((prop) => {
|
||||
prop.default = prop.default ?? componentTheme.defaultVariants?.[prop.name]
|
||||
return prop
|
||||
})
|
||||
const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
if (!meta?.meta?.props?.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
return meta
|
||||
return meta.meta.props.map((prop) => {
|
||||
prop.default = prop.default ?? componentTheme?.defaultVariants?.[prop.name]
|
||||
return prop
|
||||
})
|
||||
})
|
||||
|
||||
console.log('meta.value', meta.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -43,9 +39,9 @@ console.log('meta.value', meta.value)
|
||||
</ProseTr>
|
||||
</ProseThead>
|
||||
<ProseTbody>
|
||||
<ProseTr v-for="prop in meta.props" :key="prop.name">
|
||||
<ProseTr v-for="prop in metaProps" :key="prop.name">
|
||||
<ProseTd>
|
||||
<ProseCodeInline class="text-primary-500 dark:text-primary-400">
|
||||
<ProseCodeInline>
|
||||
{{ prop.name }}
|
||||
</ProseCodeInline>
|
||||
</ProseTd>
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { upperFirst, camelCase } from 'scule'
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const camelName = camelCase(route.params.slug[route.params.slug.length - 1])
|
||||
const name = `U${upperFirst(camelName)}`
|
||||
|
||||
const componentMeta = await useComponentMeta(name as any)
|
||||
|
||||
const meta: ComputedRef<ComponentMeta> = computed(() => componentMeta.value.meta)
|
||||
const meta = await fetchComponentMeta(name as any)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -25,9 +22,9 @@ const meta: ComputedRef<ComponentMeta> = computed(() => componentMeta.value.meta
|
||||
</ProseTr>
|
||||
</ProseThead>
|
||||
<ProseTbody>
|
||||
<ProseTr v-for="slot in meta.slots" :key="slot.name">
|
||||
<ProseTr v-for="slot in meta.meta.slots" :key="slot.name">
|
||||
<ProseTd>
|
||||
<ProseCodeInline class="text-primary-500 dark:text-primary-400">
|
||||
<ProseCodeInline>
|
||||
{{ slot.name }}
|
||||
</ProseCodeInline>
|
||||
</ProseTd>
|
||||
|
||||
32
docs/app/composables/fetchComponentMeta.ts
Normal file
32
docs/app/composables/fetchComponentMeta.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ComponentMeta } from 'vue-component-meta'
|
||||
|
||||
const useComponentsMetaState = () => useState('components-meta', () => ({}))
|
||||
|
||||
export async function fetchComponentMeta(name: string): Promise<{ meta: ComponentMeta }> {
|
||||
const state = useComponentsMetaState()
|
||||
|
||||
if (state.value[name]?.then) {
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
if (state.value[name]) {
|
||||
return state.value[name]
|
||||
}
|
||||
|
||||
// Store promise to avoid multiple calls
|
||||
|
||||
// add to nitro prerender
|
||||
if (import.meta.server) {
|
||||
const event = useRequestEvent()
|
||||
event.node.res.setHeader(
|
||||
'x-nitro-prerender',
|
||||
[event.node.res.getHeader('x-nitro-prerender'), `/api/component-meta/${name}.json`].filter(Boolean).join(',')
|
||||
)
|
||||
}
|
||||
state.value[name] = $fetch(`/api/component-meta/${name}.json`).then((meta) => {
|
||||
state.value[name] = meta
|
||||
})
|
||||
|
||||
await state.value[name]
|
||||
return state.value[name]
|
||||
}
|
||||
@@ -11,4 +11,12 @@ links:
|
||||
|
||||
## Examples
|
||||
|
||||
:component-api
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
@@ -21,10 +21,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -19,14 +19,6 @@ links:
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -20,10 +20,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -21,10 +21,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -21,10 +21,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -20,10 +20,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -20,10 +20,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -20,10 +20,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
---
|
||||
title: DropdownMenu
|
||||
description: Display a list of actions in a dropdown menu.
|
||||
links:
|
||||
- label: Radix Vue
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: FormGroup
|
||||
title: FormField
|
||||
description: Display a label and additional informations around a form element.
|
||||
links:
|
||||
- label: GitHub
|
||||
@@ -7,260 +7,6 @@ links:
|
||||
to: https://github.com/nuxt/ui/blob/dev/src/runtime/components/FormGroup.vue
|
||||
---
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Use the FormGroup component around an [Input](/components/input), [Textarea](/components/textarea), [Select](/components/select) or a [SelectMenu](/components/select-menu) with a `label`. The `<label>` will automatically be associated with the form element so it gets focused on click.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Required
|
||||
|
||||
Use the `required` prop to indicate that the form element is required.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
required: true
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Description
|
||||
|
||||
Use the `description` prop to display a description below the label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
description: "We'll only use this for spam."
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Hint
|
||||
|
||||
Use the `hint` prop to display a hint above the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
hint: 'Optional'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Help
|
||||
|
||||
Use the `help` prop to display an help message below the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
help: 'We will never share your email with anyone else.'
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
### Error
|
||||
|
||||
Use the `error` prop to display an error message below the form element.
|
||||
|
||||
When used together with the `help` prop, the `error` prop will take precedence.
|
||||
|
||||
:component-example{component="form-group-error-example"}
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
The `error` prop will automatically set the `color` prop of the form element to `red`.
|
||||
::
|
||||
|
||||
You can also use the `error` prop as a boolean to mark the form element as invalid.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
label: 'Email'
|
||||
error: true
|
||||
excludedProps:
|
||||
- ui
|
||||
- error
|
||||
- label
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb" to="/components/form"}
|
||||
Learn more about form validation in the `Form` component.
|
||||
::
|
||||
|
||||
### Size
|
||||
|
||||
Use the `size` prop to change the size of the label and the form element.
|
||||
|
||||
::component-card
|
||||
---
|
||||
props:
|
||||
size: 'xl'
|
||||
label: 'Email'
|
||||
hint: 'Optional'
|
||||
description: "We'll only use this for spam."
|
||||
help: 'We will never share your email with anyone else.'
|
||||
excludedProps:
|
||||
- label
|
||||
- hint
|
||||
- description
|
||||
- help
|
||||
code: >-
|
||||
|
||||
<UInput placeholder="you@example.com" icon="i-heroicons-envelope" />
|
||||
---
|
||||
|
||||
#default
|
||||
:u-input{placeholder="you@example.com" icon="i-heroicons-envelope"}
|
||||
::
|
||||
|
||||
::callout{icon="i-heroicons-exclamation-triangle"}
|
||||
This will only work with form elements that support the `size` prop.
|
||||
::
|
||||
|
||||
### Eager Validation
|
||||
|
||||
By default, validation is only triggered after the initial `blur` event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the `eager-validation` prop to `true`
|
||||
|
||||
:component-example{component="form-group-eager-validation-example"}
|
||||
|
||||
## Slots
|
||||
|
||||
### `label`
|
||||
|
||||
Use the `#label` slot to set the custom content for label.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
label: <UAvatar src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
---
|
||||
|
||||
#label
|
||||
:u-avatar{src="https://avatars.githubusercontent.com/u/739984?v=4" size="3xs" class="mr-2 inline-flex"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `description`
|
||||
|
||||
Use the `#description` slot to set the custom content for description.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
description: Write only valid email address <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Email'
|
||||
---
|
||||
|
||||
#description
|
||||
Write only valid email address :u-icon{name="i-heroicons-information-circle" class="align-middle"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `hint`
|
||||
|
||||
Use the `#hint` slot to set the custom content for hint.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
hint: <UIcon name="i-heroicons-arrow-right-20-solid" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Step 1'
|
||||
---
|
||||
|
||||
#hint
|
||||
:u-icon{name="i-heroicons-arrow-right-20-solid"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `help`
|
||||
|
||||
Use the `#help` slot to set the custom content for help.
|
||||
|
||||
::component-card
|
||||
---
|
||||
slots:
|
||||
help: Here are some examples <UIcon name="i-heroicons-information-circle" />
|
||||
default: <UInput model-value="benjamincanac" placeholder="you@example.com" />
|
||||
props:
|
||||
label: 'Email'
|
||||
---
|
||||
|
||||
#help
|
||||
Here are some examples :u-icon{name="i-heroicons-information-circle" class="align-middle"}
|
||||
|
||||
#default
|
||||
:u-input{model-value="benjamincanac" placeholder="you@example.com"}
|
||||
::
|
||||
|
||||
### `error`
|
||||
|
||||
Use the `#error` slot to set the custom content for error.
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-group-error-slot-example'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Usage
|
||||
|
||||
## Examples
|
||||
@@ -275,10 +21,6 @@ componentProps:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -8,223 +8,32 @@ links:
|
||||
|
||||
## Usage
|
||||
|
||||
Use the Form component to validate form data using schema libraries such as [Yup](https://github.com/jquense/yup), [Zod](https://github.com/colinhacks/zod), [Joi](https://github.com/hapijs/joi), [Valibot](https://valibot.dev/), or your own validation logic.
|
||||
|
||||
It works with the [FormGroup](/components/input) component to display error messages around form elements automatically.
|
||||
|
||||
The form component requires two props:
|
||||
- `state` - a reactive object holding the form's state.
|
||||
- `schema` - a schema object from [Yup](#yup), [Zod](#zod), [Joi](#joi), or [Valibot](#valibot).
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note that **no validation library is included** by default, so ensure you **install the one you need**.
|
||||
::
|
||||
|
||||
::tabs
|
||||
::component-example{label="Yup"}
|
||||
---
|
||||
component: 'form-example-yup'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Zod"}
|
||||
---
|
||||
component: 'form-example-zod'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Joi"}
|
||||
---
|
||||
component: 'form-example-joi'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
::component-example{label="Valibot"}
|
||||
---
|
||||
component: 'form-example-valibot'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
::
|
||||
|
||||
## Custom validation
|
||||
|
||||
Use the `validate` prop to apply your own validation logic.
|
||||
|
||||
The validation function must return a list of errors with the following attributes:
|
||||
- `message` - Error message for display.
|
||||
- `path` - Path to the form element corresponding to the `name` attribute.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note: this can be used alongside the `schema` prop to handle complex use cases.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-basic'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
This can also be used to integrate with other validation libraries. Here is an example with [Vuelidate](https://github.com/vuelidate/vuelidate):
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
|
||||
const props = defineProps({
|
||||
rules: { type: Object, required: true },
|
||||
model: { type: Object, required: true }
|
||||
})
|
||||
|
||||
const form = ref();
|
||||
const v = useVuelidate(props.rules, props.model)
|
||||
|
||||
async function validateWithVuelidate() {
|
||||
v.value.$touch()
|
||||
await v.value.$validate()
|
||||
return v.value.$errors.map((error) => ({
|
||||
message: error.$message,
|
||||
path: error.$propertyPath,
|
||||
}))
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
validate: async () => {
|
||||
await form.value.validate()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :model="model" :validate="validateWithVuelidate">
|
||||
<slot />
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
|
||||
## Backend validation
|
||||
|
||||
You can manually set errors after form submission if required. To do this, simply use the `form.setErrors` function to set the errors as needed.
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
import type { FormError, FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const state = reactive({
|
||||
email: undefined,
|
||||
password: undefined
|
||||
})
|
||||
|
||||
const form = ref()
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<any>) {
|
||||
form.value.clear()
|
||||
try {
|
||||
const response = await $fetch('...')
|
||||
// ...
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
form.value.setErrors(err.data.errors.map((err) => ({
|
||||
// Map validation errors to { path: string, message: string }
|
||||
message: err.message,
|
||||
path: err.path,
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UForm ref="form" :state="state" @submit="onSubmit">
|
||||
<UFormGroup label="Email" name="email">
|
||||
<UInput v-model="state.email" />
|
||||
</UFormGroup>
|
||||
|
||||
<UFormGroup label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" />
|
||||
</UFormGroup>
|
||||
|
||||
<UButton type="submit">
|
||||
Submit
|
||||
</UButton>
|
||||
</UForm>
|
||||
</template>
|
||||
```
|
||||
## Input events
|
||||
|
||||
The Form component automatically triggers validation upon `submit`, `input`, `blur` or `change` events.
|
||||
|
||||
This ensures that any errors are displayed as soon as the user interacts with the form elements. You can control when validation happens this using the `validate-on` prop.
|
||||
|
||||
::callout{icon="i-heroicons-light-bulb"}
|
||||
Note that the `input` event is not triggered until after the initial `blur` event. This is to prevent the form from being validated as the user is typing. You can override this behavior by setting the [`eager-validation`](/components/form-group#eager-validation) prop on [`FormGroup`](/components/form-group) to `true`.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-elements'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
hiddenCode: true
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/blob/dev/docs/components/content/examples/FormExampleElements.vue" target="_blank"}
|
||||
Take a look at the component!
|
||||
::
|
||||
|
||||
## Error event
|
||||
|
||||
You can listen to the `@error` event to handle errors. This event is triggered when the form is validated and contains an array of `FormError` objects with the following fields:
|
||||
|
||||
- `id` - the identifier of the form element.
|
||||
- `path` - the path to the form element matching the `name`.
|
||||
- `message` - the error message to display.
|
||||
|
||||
Here is an example of how to focus the first form element with an error:
|
||||
|
||||
::component-example
|
||||
---
|
||||
component: 'form-example-on-error'
|
||||
componentProps:
|
||||
class: 'w-60'
|
||||
---
|
||||
::
|
||||
|
||||
## Props
|
||||
|
||||
:component-props
|
||||
## Examples
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
### Exposed
|
||||
|
||||
When accessing the component via a template ref, you can use the following:
|
||||
|
||||
::field-group
|
||||
::field{name="submit ()" type="Promise<void>"}
|
||||
Triggers form submission.
|
||||
::
|
||||
::field{name="validate (path?: string | string[], opts: { silent?: boolean })" type="Promise<T>"}
|
||||
Triggers form validation. Will raise any errors unless `opts.silent` is set to true.
|
||||
::
|
||||
::field{name="clear (path?: string)"}
|
||||
Clears form errors associated with a specific path. If no path is provided, clears all form errors.
|
||||
::
|
||||
::field{name="getErrors (path?: string)" type="FormError[]"}
|
||||
Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.
|
||||
::
|
||||
::field{name="setErrors (errors: FormError[], path?: string)"}
|
||||
Sets form errors for a given path. If no path is provided, overrides all errors.
|
||||
::
|
||||
::field{name="errors" type="Ref<FormError[]>"}
|
||||
A reference to the array containing validation errors. Use this to access or manipulate the error information.
|
||||
::
|
||||
::
|
||||
| Name | Type |
|
||||
| ---- | ---- |
|
||||
| `submit()` | `Promise<void>` <br> [Triggers form submission.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `validate(path?: string \| string[], opts: { silent?: boolean })` | `Promise<T>` <br> [Triggers form validation. Will raise any errors unless `opts.silent` is set to true.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `clear(path?: string)` | `void` <br> [Clears form errors associated with a specific path. If no path is provided, clears all form errors.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `getErrors(path?: string)` | `FormError[]` <br> [Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `setErrors(errors: FormError[], path?: string)` | `void` <br> [Sets form errors for a given path. If no path is provided, overrides all errors.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `errors` | `Ref<FormError[]>` <br> [A reference to the array containing validation errors. Use this to access or manipulate the error information.]{class="text-gray-600 dark:text-gray-300 mt-1"} |
|
||||
| `disabled` | `Ref<boolean>` |
|
||||
|
||||
@@ -23,10 +23,6 @@ navigation:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -34,6 +34,7 @@ It also renders an `<a>` tag when a `to` prop is provided, otherwise it defaults
|
||||
It is used underneath by the [Button](/components/button), [Dropdown](/components/dropdown) and [VerticalNavigation](/components/vertical-navigation) components.
|
||||
|
||||
## IntelliSense
|
||||
|
||||
If you're using VSCode and wish to get autocompletion for the classes `active-class` and `inactive-class`, you can add the following settings to your `.vscode/settings.json`:
|
||||
|
||||
```json [.vscode/settings.json]
|
||||
@@ -45,6 +46,12 @@ If you're using VSCode and wish to get autocompletion for the classes `active-cl
|
||||
}
|
||||
```
|
||||
|
||||
## Props
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
@@ -23,10 +23,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -20,10 +20,6 @@ links:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -19,10 +19,6 @@ links:
|
||||
|
||||
:component-props
|
||||
|
||||
### Slots
|
||||
|
||||
:component-slots
|
||||
|
||||
### Events
|
||||
|
||||
:component-events
|
||||
|
||||
@@ -82,7 +82,6 @@ export default defineNuxtConfig({
|
||||
'/components': { redirect: '/components/app', prerender: false }
|
||||
},
|
||||
componentMeta: {
|
||||
debug: 2,
|
||||
exclude: [
|
||||
'@nuxt/content',
|
||||
'@nuxt/icon',
|
||||
|
||||
Reference in New Issue
Block a user