feat(input): Setting validation status and message. (#172)
* feat(input): Setting validation status and message. * Update src/components/Input/composables/useInputClasses.ts * Update docs/components/input/examples/InputValidationExample.vue * Update docs/components/input/examples/InputValidationExample.vue * Update docs/components/input.md * Update docs/components/input.md --------- Co-authored-by: Ilya Artamonov <ilya.sosidka@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import InputHelperExample from './input/examples/InputHelperExample.vue';
|
|||||||
import InputPrefixExample from './input/examples/InputPrefixExample.vue';
|
import InputPrefixExample from './input/examples/InputPrefixExample.vue';
|
||||||
import InputSuffixExample from './input/examples/InputSuffixExample.vue'
|
import InputSuffixExample from './input/examples/InputSuffixExample.vue'
|
||||||
import InputRequiredExample from './input/examples/InputRequiredExample.vue'
|
import InputRequiredExample from './input/examples/InputRequiredExample.vue'
|
||||||
|
import InputValidationExample from './input/examples/InputValidationExample.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
# Vue Input - Flowbite
|
# Vue Input - Flowbite
|
||||||
@@ -124,3 +125,24 @@ import { Input, Button } from 'flowbite-vue'
|
|||||||
```
|
```
|
||||||
|
|
||||||
<InputSuffixExample />
|
<InputSuffixExample />
|
||||||
|
|
||||||
|
## Slot - Validation
|
||||||
|
|
||||||
|
- Set validation status via `validationStatus` props, which accepts `'success'` or `'error'`.
|
||||||
|
- Add validation message via `validationMessage` slot.
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { Input } from 'flowbite-vue'
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<Input v-model='email' required placeholder="enter your email address" label="Email" validation-status='success' />
|
||||||
|
<Input v-model='email' required placeholder="enter your email address" label="Email" validation-status='error'>
|
||||||
|
<template #validationMessage>
|
||||||
|
Please enter a valid email address
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
<InputValidationExample />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vp-raw">
|
<div class="vp-raw">
|
||||||
<Input placeholder="enter your first name" label="First name">
|
<Input v-model='name' placeholder="enter your first name" label="First name">
|
||||||
<template #helper>
|
<template #helper>
|
||||||
We’ll never share your details. Read our <a href="#" class="font-medium text-blue-600 hover:underline dark:text-blue-500">Privacy Policy</a>.
|
We’ll never share your details. Read our <a href="#" class="font-medium text-blue-600 hover:underline dark:text-blue-500">Privacy Policy</a>.
|
||||||
</template>
|
</template>
|
||||||
@@ -8,5 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
import { Input } from '../../../../src/index'
|
import { Input } from '../../../../src/index'
|
||||||
|
|
||||||
|
const name = ref('')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vp-raw">
|
<div class="vp-raw">
|
||||||
<Input placeholder="enter your search query" label="Search">
|
<Input v-model='name' placeholder="enter your search query" label="Search">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||||
</template>
|
</template>
|
||||||
@@ -8,5 +8,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
import { Input } from '../../../../src/index'
|
import { Input } from '../../../../src/index'
|
||||||
|
|
||||||
|
const name = ref('')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vp-raw flex flex-col align-center gap-2 flex-wrap">
|
<div class="vp-raw flex flex-col align-center gap-2 flex-wrap">
|
||||||
<Input size="sm" placeholder="enter your first name" label="Small" />
|
<Input v-model='name' size="sm" placeholder="enter your first name" label="Small" />
|
||||||
<Input size="md" placeholder="enter your last name" label="Medium" />
|
<Input v-model='name' size="md" placeholder="enter your last name" label="Medium" />
|
||||||
<Input size="lg" placeholder="enter your second name" label="Large" />
|
<Input v-model='name' size="lg" placeholder="enter your second name" label="Large" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
import { Input } from '../../../../src/index'
|
import { Input } from '../../../../src/index'
|
||||||
|
const name = ref('')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vp-raw">
|
<div class="vp-raw">
|
||||||
<Input size="lg" placeholder="enter your search query" label="Search">
|
<Input v-model='name' size="lg" placeholder="enter your search query" label="Search">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
||||||
</template>
|
</template>
|
||||||
@@ -11,5 +11,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
import { Input, Button } from '../../../../src/index'
|
import { Input, Button } from '../../../../src/index'
|
||||||
|
|
||||||
|
const name = ref('')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
17
docs/components/input/examples/InputValidationExample.vue
Normal file
17
docs/components/input/examples/InputValidationExample.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<div class="vp-raw">
|
||||||
|
<Input v-model='email' required placeholder="enter your email address" label="Email" validation-status='success' />
|
||||||
|
<hr class='mt-4 border-0'>
|
||||||
|
<Input v-model='email' required placeholder="enter your email address" label="Email" validation-status='error'>
|
||||||
|
<template #validationMessage>
|
||||||
|
Please enter a valid email address
|
||||||
|
</template>
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Input } from '../../../../src/index'
|
||||||
|
|
||||||
|
const email = ref('')
|
||||||
|
</script>
|
||||||
@@ -18,15 +18,18 @@
|
|||||||
<slot name="suffix" />
|
<slot name="suffix" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<p v-if="$slots.validationMessage" class="mt-2 text-sm" :class="validationWrapperClasses">
|
||||||
|
<slot name="validationMessage" />
|
||||||
|
</p>
|
||||||
<p v-if="$slots.helper" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
<p v-if="$slots.helper" class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||||
<slot name="helper" />
|
<slot name="helper" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { InputSize } from '@/components/Input/types'
|
import { ValidationStatus, type InputSize } from '@/components/Input/types'
|
||||||
import { useInputClasses } from '@/components/Input/composables/useInputClasses'
|
import { useInputClasses } from '@/components/Input/composables/useInputClasses'
|
||||||
import { toRefs } from 'vue'
|
import { computed, toRefs } from 'vue'
|
||||||
import { useVModel } from '@vueuse/core'
|
import { useVModel } from '@vueuse/core'
|
||||||
|
|
||||||
interface InputProps {
|
interface InputProps {
|
||||||
@@ -36,6 +39,7 @@ interface InputProps {
|
|||||||
size?: InputSize;
|
size?: InputSize;
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
modelValue: string;
|
modelValue: string;
|
||||||
|
validationStatus?: ValidationStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<InputProps>(), {
|
const props = withDefaults(defineProps<InputProps>(), {
|
||||||
@@ -45,9 +49,12 @@ const props = withDefaults(defineProps<InputProps>(), {
|
|||||||
size: 'md',
|
size: 'md',
|
||||||
required: false,
|
required: false,
|
||||||
modelValue: '',
|
modelValue: '',
|
||||||
|
validationStatus: undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
const model = useVModel(props, 'modelValue')
|
const model = useVModel(props, 'modelValue')
|
||||||
|
|
||||||
const { inputClasses, labelClasses } = useInputClasses(toRefs(props))
|
const { inputClasses, labelClasses } = useInputClasses(toRefs(props))
|
||||||
|
|
||||||
|
const validationWrapperClasses = computed(() => props.validationStatus === ValidationStatus.Success ? 'text-green-600 dark:text-green-500' : (props.validationStatus === ValidationStatus.Error ? 'text-red-600 dark:text-red-500' : ''))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { InputSize } from '@/components/Input/types'
|
import { ValidationStatus, type InputSize } from '@/components/Input/types'
|
||||||
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
import { simplifyTailwindClasses } from '@/utils/simplifyTailwindClasses'
|
||||||
|
|
||||||
// LABEL
|
// LABEL
|
||||||
const defaultLabelClasses = 'block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'
|
const baseLabelClasses = 'block mb-2 text-sm font-medium'
|
||||||
|
|
||||||
// INPUT
|
// INPUT
|
||||||
const defaultInputClasses = 'bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
const defaultInputClasses = 'bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'
|
||||||
@@ -15,9 +15,13 @@ const inputSizeClasses: Record<InputSize, string> = {
|
|||||||
sm: 'p-2 text-sm',
|
sm: 'p-2 text-sm',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const successInputClasses = 'bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500'
|
||||||
|
const errorInputClasses = 'bg-red-50 border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 focus:border-red-500 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500'
|
||||||
|
|
||||||
export type UseInputClassesProps = {
|
export type UseInputClassesProps = {
|
||||||
size: Ref<InputSize>
|
size: Ref<InputSize>
|
||||||
disabled: Ref<boolean>
|
disabled: Ref<boolean>
|
||||||
|
validationStatus: Ref<ValidationStatus | undefined>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useInputClasses(props: UseInputClassesProps): {
|
export function useInputClasses(props: UseInputClassesProps): {
|
||||||
@@ -25,11 +29,15 @@ export function useInputClasses(props: UseInputClassesProps): {
|
|||||||
labelClasses: Ref<string>
|
labelClasses: Ref<string>
|
||||||
} {
|
} {
|
||||||
const inputClasses = computed(() => {
|
const inputClasses = computed(() => {
|
||||||
return simplifyTailwindClasses(defaultInputClasses, inputSizeClasses[props.size.value], props.disabled.value ? disabledInputClasses : '')
|
const vs = props.validationStatus.value
|
||||||
|
const classByStatus = vs === ValidationStatus.Success ? successInputClasses : (vs == ValidationStatus.Error ? errorInputClasses : '')
|
||||||
|
return simplifyTailwindClasses(defaultInputClasses, classByStatus, inputSizeClasses[props.size.value], props.disabled.value ? disabledInputClasses : '')
|
||||||
})
|
})
|
||||||
|
|
||||||
const labelClasses = computed(() => {
|
const labelClasses = computed(() => {
|
||||||
return defaultLabelClasses
|
const vs = props.validationStatus.value
|
||||||
|
const classByStatus = vs === ValidationStatus.Success ? 'text-green-700 dark:text-green-500' : (vs == ValidationStatus.Error ? 'text-red-700 dark:text-red-500' : 'text-gray-900 dark:text-gray-300')
|
||||||
|
return baseLabelClasses + ' ' + classByStatus
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1 +1,6 @@
|
|||||||
export type InputSize = 'sm' | 'md' | 'lg'
|
export type InputSize = 'sm' | 'md' | 'lg'
|
||||||
|
|
||||||
|
export enum ValidationStatus {
|
||||||
|
Success = 'success',
|
||||||
|
Error = 'error',
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user