I have Vue 3 app with Vuetify. I want to wrap Vuetify's v-row and v-col in one component.
The code without wrapping looks as following:
<v-row>
<v-col cols="4">
<v-text-field
v-model="person.name_last"
:rules="[validation.nameLastRules.required, validation.nameLastRules.counter]"
minlength="4"
maxlength="30"
>
<template #label>Фамилия<span class="text-red">*</span> </template>
</v-text-field>
</v-col>
<v-col cols="4">
<v-text-field
v-model="person.name_first"
:rules="[validation.nameFirstRules.required, validation.nameFirstRules.counter]"
minlength="4"
maxlength="30"
>
<template #label>Имя<span class="text-red">*</span> </template>
</v-text-field>
</v-col>
The validation code:
export const nameFirstRules = {
required: (value: string) => {
if (value) return true
return 'Обязательное поле'
},
counter: (value: string) =>
(value.length >= 4 && value.length <= 30) || 'Допустимое количество символов от 4 до 30',
}
As you can see on the screenshot below everything is fine.
The code with wrapping looks as following:
<v-form-row :cols="3">
<v-text-field
v-model="person.name_last"
:rules="[validation.nameLastRules.required, validation.nameLastRules.counter]"
minlength="4"
maxlength="30"
>
<template #label>Фамилия<span class="text-red">*</span> </template>
</v-text-field>
<v-text-field
v-model="person.name_first"
:rules="[validation.nameFirstRules.required, validation.nameFirstRules.counter]"
minlength="4"
maxlength="30"
>
<template #label>Имя<span class="text-red">*</span> </template>
</v-text-field>
As you can see on the screenshot below, first text field's length is longer that is not fine. I'm doing validation of string's length that should be longer or equal 4 characters.
The wrapped component looks as following:
<template>
<v-row :class="columnsClass">
<slot />
</v-row>
</template>
<script setup lang="ts">
const props = defineProps<{
cols?: number
}>()
const columnsClass = computed(() => {
if (props.cols == null) {
return null
}
const colCount = props.cols != null ? props.cols : 12
const columnClassname = `md4 pa-${colCount} ga-${colCount + 3}`
return {
[columnClassname]: true,
}
})
</script>
How can I make it work properly?
EDIT 1:
I've tried the code below with different flex settings, but got the same wrong behaviour:
<template>
<v-row>
<v-col class="setup" cols="12">
<slot />
</v-col>
</v-row>
</template>
<style lang="css" scoped>
.setup {
display: flex;
gap: 25px;
}
</style>
EDIT 2:
The code:
<v-form-row :cols="3">
<!-- <v-col cols="4"> -->
<v-text-field
v-model="person.name_last"
:rules="[validation.nameLastRules.required, validation.nameLastRules.counter]"
minlength="4"
maxlength="30"
:readonly="readMode"
>
<template #label>Фамилия<span class="text-red">*</span> </template>
</v-text-field>
<!-- </v-col>
<v-col cols="4"> -->
<v-text-field
v-model="person.name_first"
:rules="[validation.nameFirstRules.required, validation.nameFirstRules.counter]"
minlength="4"
maxlength="30"
:readonly="readMode"
>
<template #label>Имя<span class="text-red">*</span> </template>
</v-text-field>
<!-- </v-col>
<v-col cols="4"> -->
<v-text-field
v-model="person.name_middle"
:rules="[validation.nameMiddleRules.required, validation.nameMiddleRules.counter]"
minlength="4"
maxlength="30"
:readonly="readMode"
>
<template #label>Отчество<span class="text-red">*</span> </template>
</v-text-field>
<!-- </v-col>
</v-row> -->
</v-form-row>
...
<template>
<v-row>
<v-col v-for="colNode in $slots.default()" :cols="cols">
<component :is="colNode" />
</v-col>
</v-row>
</template>
<script setup lang="ts">
defineProps<{
cols?: number
}>()
</script>
It looks as following:
As @yoduh said, you have to add the VCol elements to get proper grid formatting. The HTML should look like this:
<div class="v-container v-locale--is-ltr">
<div class="v-row">
<div class="v-col">first name input</div>
<div class="v-col">last name input</div>
<div class="v-col">third input</div>
</div>
</div>
Otherwise, the inputs won't be restricted in width, and when the validation error message is longer than a third of the width, its container will extend, leading to the input taking up more space than the others.
However, to add the VCols, you have to know how many items are in the slot, since each of them has to be wrapped individually (in your approach, you add all of them into the same VCol, which gives you the same behavior as before).
To wrap them individually, build the slot manually and wrap the returned nodes in a v-for
:
<template>
<v-row>
<v-col v-for="columnNode in $slots.default()">
<component :is="columnNode" />
</v-col>
</v-row>
</template>
Note that this will get unfeasible quickly when you want to configure the columns (set different widths for example).
Here it is in a playground