add base form components

This commit is contained in:
axolotle 2023-07-25 19:19:27 +02:00
parent aed5f62fef
commit c281a579e3
5 changed files with 176 additions and 0 deletions

25
components/BaseForm.vue Normal file
View file

@ -0,0 +1,25 @@
<script setup lang="ts">
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/yup'
import * as yup from 'yup'
const props = defineProps<{
schema: yup.ObjectShape
}>()
const emit = defineEmits(['submit'])
const { handleSubmit } = useForm({
validationSchema: toTypedSchema(yup.object(props.schema)),
})
const onSubmit = handleSubmit((values) => {
emit('submit', values)
})
</script>
<template>
<form novalidate @submit.prevent="onSubmit">
<slot name="default" />
</form>
</template>

76
components/FormField.vue Normal file
View file

@ -0,0 +1,76 @@
<script setup lang="ts">
import { useField } from 'vee-validate'
import { formGroupExtras } from '@/composables/form'
const props = defineProps<{
name: string
label: string
icon?: string
description?: string
}>()
const { errorMessage } = useField(() => props.name)
const invalid = computed(() => !!errorMessage.value)
const describedBy = computed(() => {
return (
[
props.description ? props.name + '__description' : null,
invalid.value ? props.name + '__feedback_invalid' : null,
]
.filter((x) => x)
.join(' ') || undefined
)
})
provide(formGroupExtras, {
describedBy,
invalid,
})
</script>
<template>
<div>
<div
role="group"
:aria-invalid="invalid"
:class="{ 'is-invalid': invalid }"
class="flex"
>
<slot name="label">
<label :id="name + '__label'" :for="name" class="block">
<Icon
v-if="icon"
:name="icon"
size="2em"
aria-hidden="true"
class="m-2"
/>
<span :class="{ 'sr-only': !!icon }">{{ label }}</span>
</label>
</slot>
<div>
<slot name="default" />
<div
v-show="invalid"
:id="name + '__feedback_invalid'"
tabindex="-1"
aria-live="assertive"
aria-atomic="true"
class="text-error mt-1"
>
{{ errorMessage }}
</div>
<small
v-if="description"
:id="name + '__description'"
tabindex="-1"
class="block text-gray-400 mt-1"
>
{{ description }}
</small>
</div>
</div>
</div>
</template>

43
components/TextInput.vue Normal file
View file

@ -0,0 +1,43 @@
<script setup lang="ts">
import { useField } from 'vee-validate'
import { formGroupExtras } from '@/composables/form'
const props = defineProps<{
name: string
type: HTMLInputElement['type']
placeholder?: string
}>()
const attrs = useAttrs()
const { describedBy, invalid } = inject(formGroupExtras, {
describedBy: ref(undefined),
invalid: ref(false),
})
const { value, handleBlur, handleChange, errorMessage } = useField(
() => props.name,
{
validateOnValueUpdate: false,
},
)
const validationListeners = {
blur: (e: FocusEvent) => handleBlur(e, true),
change: handleChange,
input: (e: InputEvent) => handleChange(e, !!errorMessage.value),
}
</script>
<template>
<input
:id="name"
:value="value"
:name="name"
:type="type"
:placeholder="placeholder"
:aria-invalid="invalid"
:aria-describedby="describedBy"
class="input input-bordered"
:class="{ 'input-error': invalid }"
v-bind="attrs"
v-on="validationListeners"
/>
</template>

28
components/YButton.vue Normal file
View file

@ -0,0 +1,28 @@
<script setup lang="ts">
const props = withDefaults(
defineProps<{
variant?: string
type?: HTMLButtonElement['type']
block?: boolean
}>(),
{
variant: 'primary',
type: 'button',
block: false,
},
)
const variantClass = computed(() => {
return {
primary: 'btn-primary',
success: 'btn-success',
info: 'btn-info',
}[props.variant]
})
</script>
<template>
<button class="btn" :class="[variantClass, { 'btn-block': block }]">
<slot name="default" />
</button>
</template>

4
composables/form.ts Normal file
View file

@ -0,0 +1,4 @@
export const formGroupExtras = Symbol('form-group-extras') as InjectionKey<{
invalid: Ref<boolean>
describedBy: Ref<string | undefined>
}>