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