Input.vue 3.05 KB
<template>
  <div :class="['w-full', className]">
    <label v-if="label" :for="inputId" class="block text-sm font-medium text-gray-700 mb-1">
      {{ label }}
      <span v-if="required" class="text-red-500 ml-1">*</span>
    </label>

    <div class="relative rounded-md shadow-sm">
      <div v-if="leftIcon" class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
        <span class="text-gray-500 sm:text-sm">
          <slot name="leftIcon">
            {{ leftIcon }}
          </slot>
        </span>
      </div>

      <input
        :id="inputId"
        :type="type"
        :name="name"
        :placeholder="placeholder"
        :value="modelValue"
        :required="required"
        :disabled="disabled"
        @input="$emit('update:modelValue', $event.target.value)"
        @blur="$emit('blur', $event)"
        :class="[
          'block w-full border-gray-300 rounded-md shadow-sm',
          'focus:ring-green-500 focus:border-green-500 sm:text-sm',
          'disabled:bg-gray-100 disabled:text-gray-500 disabled:cursor-not-allowed',
          sizeClass,
          error ? 'border-red-300 text-red-900 placeholder-red-300 focus:ring-red-500 focus:border-red-500' : '',
          leftIcon ? 'pl-10' : '',
          rightIcon ? 'pr-10' : ''
        ]"
        v-bind="$attrs"
      />

      <div v-if="rightIcon" class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
        <span class="text-gray-500 sm:text-sm">
          <slot name="rightIcon">
            {{ rightIcon }}
          </slot>
        </span>
      </div>
    </div>

    <p v-if="error || helperText" :class="['mt-1 text-sm', error ? 'text-red-600' : 'text-gray-500']">
      {{ error || helperText }}
    </p>
  </div>
</template>

<script>
import { computed } from 'vue'

const inputSizes = {
  sm: 'px-2 py-1 text-sm',
  md: 'px-3 py-2',
  lg: 'px-4 py-3 text-lg'
}

export default {
  name: 'Input',
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    },
    label: {
      type: String,
      default: ''
    },
    name: {
      type: String,
      required: true
    },
    type: {
      type: String,
      default: 'text'
    },
    placeholder: {
      type: String,
      default: ''
    },
    error: {
      type: String,
      default: ''
    },
    size: {
      type: String,
      default: 'md',
      validator: (value) => Object.keys(inputSizes).includes(value)
    },
    required: {
      type: Boolean,
      default: false
    },
    disabled: {
      type: Boolean,
      default: false
    },
    className: {
      type: String,
      default: ''
    },
    helperText: {
      type: String,
      default: ''
    },
    leftIcon: {
      type: [String, Object],
      default: null
    },
    rightIcon: {
      type: [String, Object],
      default: null
    }
  },
  emits: ['update:modelValue', 'blur'],
  setup(props) {
    const inputId = computed(() => `input-${props.name}`)
    const sizeClass = computed(() => inputSizes[props.size] || inputSizes.md)

    return {
      inputId,
      sizeClass
    }
  }
}
</script>