Provided validation and helper slot for FwbSelect #242 (#254)

* feat(fwb-select): Add validation and helper slot

* docs(docs-select): Add validation and helper slot examples
This commit is contained in:
imanmalekian31
2023-12-05 19:32:36 +03:30
committed by GitHub
parent 32d9c4a5bc
commit f530e24a41
6 changed files with 275 additions and 53 deletions

View File

@@ -1,40 +1,55 @@
<template>
<label>
<span
v-if="label"
:class="labelClasses"
>
{{ label }}
</span>
<select
v-model="model"
:disabled="disabled"
:class="selectClasses"
>
<option
disabled
selected
value=""
<div>
<label>
<span
v-if="label"
:class="labelClasses"
>
{{ placeholder }}
</option>
<option
v-for="(option, index) in options"
:key="index"
:value="option.value"
{{ label }}
</span>
<select
v-model="model"
:disabled="disabled"
:class="selectClasses"
>
{{ option.name }}
</option>
</select>
</label>
<option
disabled
selected
value=""
>
{{ placeholder }}
</option>
<option
v-for="(option, index) in options"
:key="index"
:value="option.value"
>
{{ option.name }}
</option>
</select>
</label>
<p
v-if="$slots.validationMessage"
:class="validationWrapperClasses"
>
<slot name="validationMessage" />
</p>
<p
v-if="$slots.helper"
class="mt-2 text-sm text-gray-500 dark:text-gray-400"
>
<slot name="helper" />
</p>
</div>
</template>
<script lang="ts" setup>
import type { InputSize } from './../FwbInput/types'
import type { OptionsType } from './types'
import { computed } from 'vue'
import { computed, toRefs } from 'vue'
import { useVModel } from '@vueuse/core'
import { twMerge } from 'tailwind-merge'
import { useSelectClasses } from './composables/useSelectClasses'
import type { InputSize } from './../FwbInput/types'
import { type OptionsType, type ValidationStatus, validationStatusMap } from './types'
interface InputProps {
modelValue?: string;
@@ -44,6 +59,7 @@ interface InputProps {
disabled?: boolean;
underline?: boolean;
size?: InputSize;
validationStatus?: ValidationStatus
}
const props = withDefaults(defineProps<InputProps>(), {
modelValue: '',
@@ -53,34 +69,17 @@ const props = withDefaults(defineProps<InputProps>(), {
disabled: false,
underline: false,
size: 'md',
validationStatus: undefined,
})
const emit = defineEmits(['update:modelValue'])
const model = useVModel(props, 'modelValue', emit)
// LABEL
const defaultLabelClasses = 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'
const { selectClasses, labelClasses } = useSelectClasses(toRefs(props))
// SELECT
const defaultSelectClasses = 'w-full text-gray-900 bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500'
const disabledSelectClasses = 'cursor-not-allowed bg-gray-100'
const underlineSelectClasses = 'bg-transparent dark:bg-transparent border-b-2 border-gray-200 appearance-none dark:border-gray-700 focus:outline-none focus:ring-0 focus:border-gray-200 peer'
const selectSizeClasses: Record<InputSize, string> = {
lg: 'p-4',
md: 'p-2.5 text-sm',
sm: 'p-2 text-sm',
}
const selectClasses = computed(() => {
return twMerge(
defaultSelectClasses,
selectSizeClasses[props.size],
props.disabled && disabledSelectClasses,
props.underline ? underlineSelectClasses : 'border border-gray-300 rounded-lg',
)
})
const labelClasses = computed(() => {
return defaultLabelClasses
})
const validationWrapperClasses = computed(() => twMerge(
'mt-2 text-sm',
props.validationStatus === validationStatusMap.Success ? 'text-green-600 dark:text-green-500' : '',
props.validationStatus === validationStatusMap.Error ? 'text-red-600 dark:text-red-500' : '',
))
</script>

View File

@@ -0,0 +1,76 @@
import { computed, type Ref } from 'vue'
import { twMerge } from 'tailwind-merge'
import {
type InputSize,
type ValidationStatus,
validationStatusMap,
} from '../types'
// LABEL
const baseLabelClasses = 'block mb-2 text-sm font-medium'
// INPUT
const defaultSelectClasses = 'w-full text-gray-900 bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500'
const disabledSelectClasses = 'cursor-not-allowed bg-gray-100'
const underlineSelectClasses = 'bg-transparent dark:bg-transparent border-b-2 border-gray-200 appearance-none dark:border-gray-700 focus:outline-none focus:ring-0 focus:border-gray-200 peer'
const selectSizeClasses: Record<InputSize, string> = {
lg: 'p-4',
md: 'p-2.5 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 UseSelectClassesProps = {
size: Ref<InputSize>,
disabled: Ref<boolean>
underline: Ref<boolean>,
validationStatus: Ref<ValidationStatus | undefined>
}
export function useSelectClasses (props: UseSelectClassesProps): {
selectClasses: Ref<string>
labelClasses: Ref<string>
} {
const selectClasses = computed(() => {
const vs = props.validationStatus.value
const classByStatus = vs === validationStatusMap.Success
? successInputClasses
: vs === validationStatusMap.Error
? errorInputClasses
: ''
const underlineByStatus = vs === validationStatusMap.Success
? 'focus:border-green-500'
: vs === validationStatusMap.Error
? 'focus:border-red-500'
: ''
return twMerge(
defaultSelectClasses,
classByStatus,
selectSizeClasses[props.size.value],
props.disabled.value && disabledSelectClasses,
props.underline.value ? underlineSelectClasses : 'border border-gray-300 rounded-lg',
props.underline.value && underlineByStatus,
)
})
const labelClasses = computed(() => {
const vs = props.validationStatus.value
const classByStatus = vs === validationStatusMap.Success
? 'text-green-700 dark:text-green-500'
: vs === validationStatusMap.Error
? 'text-red-700 dark:text-red-500'
: 'text-gray-900 dark:text-white'
return twMerge(baseLabelClasses, classByStatus)
})
return {
selectClasses,
labelClasses,
}
}

View File

@@ -1,4 +1,13 @@
export type InputSize = 'sm' | 'md' | 'lg'
export type OptionsType = {
name: string,
value: string,
}
export const validationStatusMap = {
Success: 'success',
Error: 'error',
} as const
export type ValidationStatus = typeof validationStatusMap[keyof typeof validationStatusMap]