import { makeComponentProps } from '@/composables/component' import { makeSizeProps, Sizes } from '@/composables/size' import { InputRulesType } from '@/types/inputRules' import InputVariant from '@/types/inputVariants' import { computed, ref } from 'vue' import { useEventListener } from '@vueuse/core' import { UIcon } from '@/components/UIcon/UIcon' import { UTooltip } from '@/components/UTooltip/UTooltip' import { genericComponent, propsFactory, useRender } from '@/utils' export const makeUInputProps = propsFactory( { modelValue: { type: String, default: '', required: true, }, type: { type: String, default: 'text', }, label: String, placeholder: String, hint: String, tooltipTitle: String, tooltipText: String, tooltipTheme: { type: String, default: 'light', required: false, }, tooltipPosition: String, appendIcon: String, prependIcon: String, rules: { type: Array as () => InputRulesType, default: undefined, required: false, }, validateOn: { type: String, default: 'submit', required: false, }, variant: { type: String, default: InputVariant.default, required: false, }, disabled: { type: Boolean, default: false, required: false, }, autocomplete: Boolean, name: String, error: Boolean, errorMessages: Array as () => string[], ...makeComponentProps(), ...makeSizeProps({ size: Sizes.sm }), }, 'UInput' ) export type UInputSlots = { default: never } export const UInput = genericComponent()({ name: 'UInput', props: makeUInputProps(), emits: { 'update:modelValue': (value: string) => true, validationSuccess: () => true, validationError: () => true, blur: () => true, input: () => true, change: () => true, submit: () => true, click: (e: MouseEvent) => true, 'click:append': () => true, 'click:prepend': () => true, 'keydown:enter': () => true, }, setup(props, { emit, slots }) { const prependIcon = computed(() => { if (props.prependIcon) { return props.prependIcon } return '' }) const appendIcon = computed(() => { if (props.appendIcon) { return props.appendIcon } return '' }) const hasLabel = !!props.label const hasPrepend = !!props.prependIcon const hasAppend = !!props.appendIcon const isActive = ref(false) const isHover = ref(false) const inputValue = ref('') const handleKeyDown = (event: KeyboardEvent) => { if (isActive.value && event.key === 'Enter' && !props.disabled) { emit('keydown:enter') } } useEventListener('keydown', handleKeyDown) function onClick(e: MouseEvent) { emit('click', e) } function clickPrependHandler() { emit('click:prepend') } function clickAppendHandler() { emit('click:append') } function blurHandler() { emit('blur') isActive.value = false if (props.validateOn === 'blur') inputValidation() } function inputHandler() { emit('input') emit('update:modelValue', inputValue.value) if (props.validateOn === 'input') inputValidation() } function changeHandler() { emit('change') if (props.validateOn === 'change') inputValidation() } function submitHandler() { emit('submit') if (props.validateOn === 'submit') inputValidation() } function inputValidation() { const rulesFunctions = props.rules if (!rulesFunctions) return const isValid = rulesFunctions.every((rule) => { try { return rule(inputValue.value) } catch (error) { console.error(`Validation error: ${error}`) return false } }) if (isValid) { emit('validationSuccess') } else { emit('validationError') } } const classes = computed(() => ({ // eslint-disable-next-line max-len 'overflow-x-auto box-border flex items-center gap-2 font-regular rounded-md border': true, 'px-3': props.size === 'sm', 'px-3.5': props.size === 'md' || props.size === 'lg', ...(isActive.value === false && props.error === false && !props.disabled === true && { 'border-gray-300': true, }), ...(isActive.value === true && props.error === false && !props.disabled === true && { 'border-primary-300 shadow-xs-btn shadow-primary-50': true, }), ...(isActive.value === true && props.error === true && !props.disabled === true && { 'shadow-xs-btn shadow-error-50': true, }), ...(props.error === true && !props.disabled === true && { 'border-error-300': true, }), ...(!props.disabled === false && { 'border-gray-300 bg-gray-50 cursor-not-allowed': true, }), })) const inputClasses = computed(() => ({ // eslint-disable-next-line max-len 'box-border min-w-[140px] w-full flex-grow bg-transparent font-regular text-gray-900 text-text-md': true, ...(!props.disabled === false && { 'cursor-not-allowed': true, }), 'py-[7px]': props.size === 'sm', 'py-[9px]': props.size === 'md' || props.size === 'lg', })) const fieldClasses = computed(() => ({ 'box-border flex flex-col gap-1.5 w-full': true, })) const labelClasses = computed(() => ({ 'label font-medium text-gray-700 text-text-sm': true, })) const hintClasses = computed(() => ({ 'font-regular text-gray-600 text-text-sm': true, })) const errorMessageClasses = computed(() => ({ 'font-regular text-error-500 text-text-sm': true, })) const appendIconColor = computed(() => { if (!props.error) { return 'gray-400' } return 'error-500' }) const errorsList = props.errorMessages?.map((item, index) => { return (
{item}
) }) useRender(() => (
{hasLabel ?
{props.label}
: null}
{hasPrepend ? (
) : null} {slots.default?.()} { (isActive.value = true)} onBlur={blurHandler} onInput={inputHandler} onChange={changeHandler} onSubmit={submitHandler} /> } {hasAppend && (props.tooltipTitle || props.tooltipText) ? ( {{ tooltipToggle: () => ( ), text: () => <>{props.tooltipTitle}, supportingText: () => <>{props.tooltipText}, }} ) : null} {hasAppend && !props.tooltipTitle ? ( ) : null}
{!props.error && props.hint ? (
{props.hint}
) : null} {props.error && props.errorMessages ? (
{errorsList}
) : null}
)) return { isActive, } }, }) export type UInput = InstanceType