mirror of
https://github.com/YunoHost/yunohost-portal.git
synced 2024-09-03 20:06:23 +02:00
add base form components
This commit is contained in:
parent
aed5f62fef
commit
c281a579e3
5 changed files with 176 additions and 0 deletions
25
components/BaseForm.vue
Normal file
25
components/BaseForm.vue
Normal 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
76
components/FormField.vue
Normal 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
43
components/TextInput.vue
Normal 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
28
components/YButton.vue
Normal 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
4
composables/form.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const formGroupExtras = Symbol('form-group-extras') as InjectionKey<{
|
||||
invalid: Ref<boolean>
|
||||
describedBy: Ref<string | undefined>
|
||||
}>
|
Loading…
Add table
Reference in a new issue