# ivyforms/0.3/frontend/src/views/_components/input/IvyTextInput.vue

The Innovative Form Builder – IvyForms, version 0.3. 338 lines.

- Page: https://pluginprobe.com/plugins/ivyforms/0.3/code/frontend/src/views/_components/input/IvyTextInput.vue
- Raw: https://pluginprobe.com/plugins/ivyforms/0.3/raw/frontend/src/views/_components/input/IvyTextInput.vue
- Modified: 2025-08-20T10:20:12+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/ivyforms/0.3/code/frontend/src/views/_components/input/IvyTextInput.vue#L10-L20`.

```vue
<template>
  <ElInput
    ref="elInputRef"
    v-bind="{ ...$attrs, ...$props }"
    v-model="localModelValue"
    :class="[
      type === 'textarea' ? 'ivyforms-textarea' : 'ivyforms-input',
      { 'is-filled': props.filled, 'is-secondary': props.secondary },
    ]"
    :type="localInputType"
    @focus="isFocused = true"
    @blur="isFocused = false"
  >
    <template v-if="!!slots['prefix'] || iconStart" #prefix>
      <slot name="prefix">
        <IvyIcon
          v-if="iconStart"
          :name="iconStart"
          :category="iconStartCategory"
          :type="iconStartType"
          class="ivyforms-input__icon-start"
          size="l"
          color="var(--map-base-dusk-symbol-2)"
        />
      </slot>
    </template>
    <template v-if="!!slots['suffix'] || iconEnd || props.clearable" #suffix>
      <slot name="suffix">
        <IvyIcon
          v-if="props.iconEnd"
          :name="props.iconEnd"
          :category="props.iconEndCategory"
          :type="props.iconEndType"
          class="ivyforms-input__icon-end"
          size="l"
          color="var(--map-base-dusk-symbol-2)"
        />
        <IvyIcon
          v-if="props.clearable && localModelValue"
          name="close-circle"
          class="ivyforms-input__clear"
          size="l"
          color="var(--map-base-dusk-symbol-2)"
          type="fill-duo"
          @click.stop="clearInput"
        />
      </slot>
    </template>
  </ElInput>
</template>

<script setup lang="ts">
import { ref, watch, useSlots } from 'vue'
import type { IconCategory } from '@/types/icons/icon-category'
import type { IconType } from '@/types/icons/icon-type'

interface Props {
  type?:
    | 'text'
    | 'textarea'
    | 'number'
    | 'select'
    | 'multiple-select'
    | 'phone'
    | 'email'
    | 'website'
  placeholder?: string
  iconStart?: string
  iconEnd?: string
  iconStartCategory?: IconCategory
  iconEndCategory?: IconCategory
  avatar?: string
  iconStartType?: IconType
  iconEndType?: IconType
  filled?: boolean
  secondary?: boolean
  prefix?: string
  suffix?: string
  clearable?: boolean
  modelValue: string | number | null | undefined
}

const props = withDefaults(defineProps<Props>(), {
  type: 'text',
  placeholder: '',
  iconStart: '',
  iconEnd: '',
  iconStartCategory: 'global',
  iconEndCategory: 'global',
  iconStartType: 'outline',
  iconEndType: 'outline',
  avatar: '',
  prefix: '',
  suffix: '',
})

const emit = defineEmits(['update:modelValue'])

const localModelValue = ref(props.modelValue)
const localInputType = ref(props.type)

const slots = useSlots()

const elInputRef = ref()
const isFocused = ref(false)

// Sync localModelValue with props.modelValue
watch(
  () => props.modelValue,
  (newValue) => {
    localModelValue.value = newValue
  },
)

// Emit update:modelValue when localModelValue changes
watch(localModelValue, (newValue) => {
  emit('update:modelValue', newValue)
})

const clearInput = () => {
  localModelValue.value = ''
}

const focus = () => {
  elInputRef.value?.focus()
}

defineExpose({
  focus,
})
</script>

<style lang="scss">
// Input
.ivyforms-input {
  &.el-input {
    height: 40px;

    .el-input__wrapper {
      display: flex;
      align-items: center;
      gap: var(--Spacing-sm, 8px);
      padding: var(--Spacing-sm, 4px) var(--Spacing-lg, 12px);
      align-self: stretch;
      border-radius: var(--Radius-radius-md, 8px);
      border: 1px solid var(--map-base-dusk-stroke--2);
      background: var(--map-base-dusk-o05);
      box-shadow: none;

      .el-input__prefix-inner > :last-child {
        margin-right: 0;
      }

      // Hover
      &:hover,
      &.is-hover {
        border: 1px solid var(--map-base-dusk-stroke-0);
        background: var(--map-base-dusk-o05);
      }

      // Inner input
      input {
        padding: 0;
        font-size: 16px;
        font-style: normal;
        font-weight: 400;
        line-height: 22px;
        color: var(--map-base-text-0);
        border: none;
        box-shadow: none;
        background-color: transparent;
        display: flex;
        align-items: center;
        gap: 4px;
        flex: 1 0 0;
      }

      // Active & Focus
      &:active,
      &:focus,
      &.is-focus,
      &.is-active {
        border: 1px solid var(--map-base-brand-stroke-0);
        background: var(--map-base-dusk-o05);
        box-shadow: none;
      }

      // Clear Icon
      .ivyforms-input__clear {
        cursor: pointer;
        width: 20px;
        height: 20px;
        transform: scale(1);
        margin: 0;
      }

      .el-input__clear {
        display: none !important;

        &::before {
          display: none !important;
        }
      }
    }

    // Secondary
    &.is-secondary {
      .el-input__wrapper {
        // Active & Focus
        &:active,
        &:focus,
        &.is-focus,
        &.is-active {
          border: 1px solid var(--map-base-purple-stroke-0);
        }
      }
    }
  }
}

// Form Item
.el-form-item {
  &.ivyforms-form-item-input {
    // Secondary
    &.is-secondary {
      .ivyforms-input {
        &.el-input {
          .el-input__wrapper {
            // Active & Focus
            &:active,
            &:focus,
            &.is-focus,
            &.is-active {
              border: 1px solid var(--map-base-purple-stroke-0);
            }
          }
        }
      }
    }
  }

  // Error and warning states
  &.is-error {
    .ivyforms-input {
      &.el-input {
        .el-input__wrapper {
          border: 1px solid var(--map-status-error-symbol-0);
        }
      }
    }
  }

  &.is-warning {
    .ivyforms-input {
      &.el-input {
        .el-input__wrapper {
          border: 1px solid var(--map-status-warning-stroke-0);
        }
      }
    }
  }

  // Textarea
  &.ivyforms-form-item-textarea {
    .ivyforms-textarea {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      font-size: 16px;
      font-style: normal;
      font-weight: 400;
      line-height: 22px;

      // Element Textarea
      .el-textarea__inner {
        border-radius: var(--Radius-radius-md, 8px);
        border: 1px solid var(--map-base-dusk-stroke--2);
        background-color: var(--map-base-dusk-o05);
        display: flex;
        align-items: center;
        gap: var(--Spacing-sm, 8px);
        align-self: stretch;
        box-shadow: none;
        color: var(--map-base-text-0);

        // Hover
        &:hover {
          border: 1px solid var(--map-base-dusk-stroke-0);
          background: var(--map-base-dusk-o05);
        }

        // Active & Focus
        &:active,
        &:focus,
        &.is-focus,
        &.is-active {
          border: 1px solid var(--map-base-brand-stroke-0);
          background: var(--map-base-dusk-o05);
          box-shadow: none;
        }
      }
    }

    // Secondary
    &.is-secondary {
      .ivyforms-textarea {
        .el-textarea__inner {
          // Active & Focus
          &:active,
          &:focus,
          &.is-focus,
          &.is-active {
            border: 1px solid var(--map-base-purple-stroke-0);
          }
        }
      }
    }
  }

  // Error and warning states
  &.is-error {
    .ivyforms-textarea {
      .el-textarea__inner {
        border: 1px solid var(--map-status-error-symbol-0);
      }
    }
  }

  &.is-warning {
    .ivyforms-textarea {
      .el-textarea__inner {
        border: 1px solid var(--map-status-warning-stroke-0);
      }
    }
  }
}
</style>

```
