Skip to content

CTextField

CTextField — основной компонент текстового поля ввода. Построен поверх CInput и предоставляет полностью стилизованный, доступный и валидируемый <input> с плавающим лейблом, иконками, подсказками и поддержкой тем.

Использование

Показать код
vue
<template>
  <CTextField
    v-model="value"
    id="basic-email"
    label="Email"
    placeholder="Enter your email"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
const value = ref('')
</script>

Состояния

CTextField поддерживает стандартные состояния: обычное, отключённое (disabled), только для чтения (readonly) и с очисткой (clearable).

Hint text below the field
Показать код
vue
<template>
  <CTextField v-model="value" label="Default" />
  <CTextField v-model="value" label="Disabled" disabled />
  <CTextField v-model="readonly" label="Readonly" readonly />
  <CTextField v-model="value" label="Clearable" clearable />
</template>

Валидация

Передайте массив функций-правил в prop rules. Каждое правило принимает текущее значение и возвращает объект { valid: boolean, message: string }. Prop validate-on управляет моментом запуска: 'input' (по умолчанию) или 'blur'.

We'll never share your email
Показать код
vue
<template>
  <CTextField
    v-model="email"
    label="Email"
    :rules="emailRules"
    validate-on="blur"
    details="We'll never share your email"
  />
  <CTextField
    v-model="password"
    label="Password"
    type="password"
    :rules="passwordRules"
    validate-on="blur"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'

const email = ref('')
const password = ref('')

const emailRules = [
  (v: string) => ({ valid: !!v, message: 'Email is required' }),
  (v: string) => ({ valid: /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), message: 'Invalid email' }),
]

const passwordRules = [
  (v: string) => ({ valid: !!v, message: 'Password is required' }),
  (v: string) => ({ valid: v.length >= 8, message: 'Minimum 8 characters' }),
]
</script>

Слоты prepend, append и details

Слоты prepend и append позволяют разместить иконку или текст внутри поля. Слот details полностью заменяет блок подсказки/ошибки.

@
USD
0/20 characters
Показать код
vue
<template>
  <!-- Prepend icon -->
  <CTextField v-model="search" label="Search">
    <template #prepend>
      <CIcon name="mdi-magnify" />
    </template>
  </CTextField>

  <!-- Append text -->
  <CTextField v-model="amount" label="Amount" type="number">
    <template #append>
      <span style="opacity: .6; font-size: 13px">USD</span>
    </template>
  </CTextField>

  <!-- Custom details slot -->
  <CTextField
    v-model="nickname"
    label="Nickname"
    :rules="nicknameRules"
    validate-on="input"
  >
    <template #details="{ errorMessage, hasError }">
      <span :style="{ color: hasError ? 'var(--c-app-error-color)' : 'inherit' }">
        {{ errorMessage || `${nickname.length}/20 characters` }}
      </span>
    </template>
  </CTextField>
</template>

Асинхронная валидация

Правила могут возвращать Promise. Пока идёт проверка, слот details получает validating: true.

Min 3 characters, must be unique
We'll send a verification link

Try: admin, user, root (taken) or test@taken.com

Показать код
vue
<template>
  <CTextField
    v-model="username"
    label="Username"
    :rules="usernameRules"
    validate-on="blur"
  >
    <template #details="{ errorMessage, hasError, validating }">
      <span v-if="validating" style="color: var(--c-app-primary-color)">
        Checking availability…
      </span>
      <span v-else-if="hasError" style="color: var(--c-app-error-color)">
        {{ errorMessage }}
      </span>
      <span v-else style="opacity: .6">Must be unique</span>
    </template>
  </CTextField>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const username = ref('')
const taken = ['admin', 'user', 'root']

const usernameRules = [
  (v: string) => ({ valid: v.length >= 3, message: 'Minimum 3 characters' }),
  async (v: string) => {
    await new Promise(resolve => setTimeout(resolve, 800))
    return { valid: !taken.includes(v.toLowerCase()), message: `"${v}" is already taken` }
  },
]
</script>

Пресеты

Пресеты позволяют задать внешний вид поля (цвет лейбла, рамки) централизованно — один раз при инициализации плагина, а потом использовать по имени через prop preset.

Показать код
vue
<template>
  <CTextField v-model="value" label="Email" preset="input.blue">
    <template #prepend><CIcon name="fas:envelope" :size="16" source="fa" /></template>
  </CTextField>
</template>

Регистрация пресетов при инициализации плагина:

ts
import { createVuelandUI } from '@vueland/ui'
import type { CInputPreset } from '@vueland/ui/types'

createVuelandUI({
  presets: {
    input: {
      blue: {
        base:    { label: ['text-blue'] },
        focused: { label: ['text-blue'], field: ['text-blue'] },
        filled:  { label: ['text-blue'] },
        error:   { label: ['text-red'], details: ['text-red'] },
      } satisfies CInputPreset,
    },
  },
})

Структура CInputPreset

Пресет — это набор плоских пресетов по состояниям: base плюс опциональные оверрайды по состояниям. Без составных состояний и вложенности:

ts
type CInputZone = 'root' | 'field' | 'input' | 'label' | 'details' | 'prepend' | 'append'
type CInputState = 'focused' | 'filled' | 'error' | 'disabled' | 'readonly'

type ZonePreset = Partial<Record<CInputZone, string[]>>
type CInputPreset = Partial<Record<'base' | CInputState, ZonePreset>>

Компонент всегда в одном текущем состоянии — применяется пресет этого состояния, его зоны подменяют base пер-зонно, без стека и без приоритетов. Полная модель — в разделе CInput → Система пресетов.

Пресет распределяется автоматически: CInput применяет root и details и раздаёт набор в CField через provide/inject, а тот применяет field, input, label, prepend, append.


API

Props

PropТипПо умолчаниюОписание
modelValuestring | number | nullundefinedЗначение поля (v-model)
idstringautoID для <input>. Если не передан, генерируется с префиксом input-
labelstringПлавающий лейбл поля
placeholderstringPlaceholder нативного <input> (attr)
detailsstringПодсказка под полем
noDetailsbooleanfalseСкрыть блок details полностью
clearablebooleanfalseКнопка очистки при наличии значения и фокуса
disabledbooleanfalseОтключить поле. Блокирует фокус и ввод
readonlybooleanfalseТолько для чтения. Фокус разрешён, редактирование — нет
focusedbooleanfalseНачальное состояние фокуса
rulesValidateFn[][]Массив функций валидации
validateOn'input' | 'blur''input'Момент запуска автоматической валидации
presetstringИмя пресета из конфигурации Vueland UI
typestring'text'Тип <input> — передаётся как нативный attr
namestringname нативного <input>
autocompletestringautocomplete нативного <input>
requiredbooleanrequired нативного <input>
inputmodestringinputmode нативного <input>
patternstringpattern нативного <input>
maxlengthnumbermaxlength нативного <input>
minlengthnumberminlength нативного <input>
minnumbermin для type="number"
maxnumbermax для type="number"
stepnumberstep для type="number"
tabindexnumbertabindex нативного <input>
enterkeyhintstringПодпись кнопки Enter на мобильной клавиатуре
aria-labelstringДополнительная aria-метка (attr)

Slots

СлотПропсыОписание
prependКонтент слева внутри поля (иконка, текст)
appendКонтент справа внутри поля (иконка, текст)
detailsCInputDetailsSlotPropsЗамена блока подсказки/ошибки

Пропсы слота details

ПропТипОписание
errorMessagestring | undefinedТекущее сообщение об ошибке
hasErrorbooleanЕсть ли активная ошибка
validatingbooleanИдёт ли async-валидация прямо сейчас
uidstringID поля (совпадает с id нативного <input>)
detailsstring | undefinedЗначение prop details

События

СобытиеАргументыОписание
update:modelValuestring | number | undefinedИзменение значения (v-model)
focusПоле получило фокус
blurПоле потеряло фокус

Expose

Методы, доступные через template ref:

МетодСигнатураОписание
validate() => Promise<boolean>Запустить валидацию вручную
reset() => voidСбросить состояние ошибки
focus() => voidПрограммно сфокусировать поле
blur() => voidПрограммно убрать фокус
vue
<template>
  <CTextField ref="fieldRef" v-model="value" label="Name" :rules="rules" />
  <CBtn @click="fieldRef?.validate()">Validate</CBtn>
</template>

<script setup lang="ts">
import { ref } from 'vue'
const fieldRef = ref()
const value = ref('')
const rules = [(v: string) => ({ valid: !!v, message: 'Required' })]
</script>

Тип ValidateFn

ts
type ValidateResult = { valid: boolean; message: string }
type ValidateFn = (value: any) => ValidateResult | Promise<ValidateResult>

CSS-переменные

CInput (корневой элемент)

ПеременнаяПо умолчаниюОписание
--c-input-background-colorvar(--c-app-surface-color)Фон компонента
--c-input-primary-colorvar(--c-app-primary-color)Цвет текста в состоянии default
--c-input-error-colorvar(--c-app-error-color)Цвет текста при ошибке
--c-input-disabled-colorvar(--c-app-disabled-color)Цвет текста при disabled
--c-input-readonly-colorvar(--c-app-primary-color)Цвет текста при readonly
--c-input-readonly-bg-colorgrey lighten-4Фон поля при readonly
--c-input-field-border-radiusvar(--c-app-border-radius)Скругление поля
--c-input-details-height24pxВысота блока details

CField (рамка и лейбл)

ПеременнаяПо умолчаниюОписание
--c-field-border-colorvar(--c-app-border-color, #e5e5e5)Цвет рамки
--c-field-disabled-opacity0.5Прозрачность при disabled

Пример переопределения

vue
<CTextField
  v-model="value"
  label="Custom styled"
  style="
    --c-input-primary-color: #7c3aed;
    --c-field-border-color: #ddd6fe;
  "
/>

CSS-классы состояний

КлассУсловие
c-input--defaultНет ошибки, не disabled, не readonly
c-input--focusedПоле в фокусе
c-input--has-errorЕсть ошибка валидации
c-input--disableddisabled = true
c-input--readonlyreadonly = true
c-input--clearableclearable = true
c-input--validatingИдёт async-валидация
c-field--focusedРамка в фокусе
c-field--filledПоле имеет значение (лейбл поднят)
c-field--disabledРамка disabled
c-field--readonlyРамка readonly (пунктирная рамка)
c-field--has-prependЕсть слот prepend