Адаптация под дизайн-системы

Inputmask часто используется не только как утилита для форматирования ввода, но и как слой интеграции между пользовательским интерфейсом и дизайн-системой. В современных интерфейсах маска поля ввода перестала быть чисто функциональным инструментом — она становится частью визуального языка продукта, подчиняется токенам, состояниям компонентов и правилам взаимодействия.

В дизайн-системах поле ввода рассматривается как компонент с чётко определёнными состояниями: default, focus, disabled, error, success, warning. Маска ввода должна не конфликтовать с этими состояниями, а дополнять их.

Inputmask работает на уровне DOM, изменяя значение поля, но не управляя стилями напрямую. Это создаёт разделение ответственности:

  • дизайн-система отвечает за визуальные состояния;
  • Inputmask отвечает за структуру вводимых данных;
  • связующий слой синхронизирует состояния маски и UI.

Ключевой принцип интеграции — маска не должна ломать семантику компонента.

Синхронизация состояний Inputmask с UI-классами

Типичный подход — привязка событий Inputmask к CSS-классам компонента.

import Inputmask from "inputmask";

const el = document.querySelector(".input");

const mask = new Inputmask({
  mask: "+7 (999) 999-99-99",
  showMaskOnHover: false,
  showMaskOnFocus: true
});

mask.mask(el);

Дальше добавляется слой синхронизации:

el.addEventListener("complete", () => {
  el.classList.add("is-complete");
  el.classList.remove("is-error");
});

el.addEventListener("incomplete", () => {
  el.classList.add("is-incomplete");
});

В дизайн-системе эти классы обычно соответствуют токенам состояния:

  • is-complete → success state
  • is-error → error state
  • is-incomplete → neutral/active state

Важно, что Inputmask не навязывает структуру классов — она проектируется отдельно и должна быть согласована с системой токенов.

Привязка к design tokens

Современные дизайн-системы используют токены вместо фиксированных значений:

  • цвет границы
  • цвет текста
  • фон состояния
  • отступы
  • анимации

Inputmask косвенно влияет на токены через состояние компонента. Например, маска даты может переключать визуальное состояние в зависимости от валидности.

const setState = (el, state) => {
  el.dataset.state = state;
};

el.addEventListener("complete", () => setState(el, "success"));
el.addEventListener("incomplete", () => setState(el, "default"));

CSS:

.input[data-state="success"] {
  border-color: var(--color-success);
}

.input[data-state="error"] {
  border-color: var(--color-danger);
}

Таким образом Inputmask становится триггером для переключения токенизированных состояний.

Управление визуальным отображением маски

Одна из проблем интеграции — различие между реальным значением и отображаемой маской. Дизайн-система должна определять, как именно отображается placeholder, заполненные символы и плейсхолдерные сегменты.

Inputmask поддерживает параметры визуального поведения:

new Inputmask({
  mask: "99-99-9999",
  placeholder: "_",
  showMaskOnHover: false,
  showMaskOnFocus: true
});

В дизайн-системах часто требуется:

  • скрывать маску вне фокуса
  • показывать подсказки при фокусе
  • использовать приглушённый стиль для placeholder-символов

CSS может быть расширен через псевдоклассы и data-атрибуты:

.input:not(:focus) {
  letter-spacing: 0.5px;
  color: var(--color-text-muted);
}

.input:focus {
  color: var(--color-text-primary);
}

Поддержка темизации (light/dark/contrast)

Inputmask не зависит от темы, но его поведение влияет на визуальную плотность интерфейса. Например, символы маски _ или должны быть адаптированы под контрастные темы.

Рекомендуется избегать фиксированных символов в пользу токенов:

new Inputmask({
  mask: "9999 9999 9999 9999",
  placeholder: "•"
});

В дизайн-системе placeholder должен быть частью токенов:

:root {
  --mask-placeholder-color: rgba(0,0,0,0.3);
}

[data-theme="dark"] {
  --mask-placeholder-color: rgba(255,255,255,0.4);
}

.input::placeholder {
  color: var(--mask-placeholder-color);
}

Согласование масок с компонентной моделью

В компонентах UI библиотек (React/Vue/Angular) Inputmask обычно внедряется через обёртки.

Пример React-интеграции:

import { useEffect, useRef } from "react";
import Inputmask from "inputmask";

export function MaskedInput(props) {
  const ref = useRef(null);

  useEffect(() => {
    const im = new Inputmask(props.mask);
    im.mask(ref.current);
  }, [props.mask]);

  return <input ref={ref} className="input" />;
}

В дизайн-системах это оборачивается в абстракцию компонента:

  • MaskedInput
  • DateInput
  • PhoneInput

Каждый из них наследует визуальные токены, но имеет свою конфигурацию маски.

Обработка ошибок и визуальная консистентность

Ошибки маски часто возникают не на уровне Inputmask, а на уровне бизнес-логики. Поэтому важно разделять:

  • структурную валидность (Inputmask)
  • семантическую валидность (доменная логика)

Пример:

el.addEventListener("complete", () => {
  if (!isValidNumber(el.value)) {
    el.classList.add("is-error");
  }
});

В дизайн-системе ошибка должна быть визуально единообразной:

.input.is-error {
  border-color: var(--color-error);
  box-shadow: 0 0 0 2px var(--color-error-soft);
}

Маски и плотность интерфейса

Разные дизайн-системы поддерживают несколько режимов плотности:

  • compact
  • default
  • comfortable

Inputmask влияет на плотность через:

  • количество символов
  • визуальные разделители
  • ширину поля
new Inputmask({
  mask: "9999 9999 9999 9999",
  greedy: false
});

CSS адаптация:

.input {
  padding: var(--space-sm);
  font-size: var(--font-size-md);
}

[data-density="compact"] .input {
  padding: var(--space-xs);
  font-size: var(--font-size-sm);
}

Интеграция с валидационными системами дизайн-систем

В современных UI-архитектурах Inputmask редко работает изолированно. Он подключается к слоям валидации:

  • schema validation (Zod, Yup)
  • backend validation
  • UI validation

Роль Inputmask — обеспечить корректный формат до отправки.

const value = el.value.replace(/\s/g, "");

if (!schema.validate(value)) {
  el.dataset.state = "error";
}

Поведение при автозаполнении и paste

Дизайн-системы часто требуют предсказуемого поведения при вставке данных:

el.addEventListener("paste", () => {
  setTimeout(() => {
    if (el.inputmask?.isComplete()) {
      el.classList.add("is-complete");
    }
  }, 0);
});

Inputmask автоматически нормализует вставку, но UI должен реагировать визуально:

  • мгновенное обновление состояния
  • отсутствие «скачков» layout
  • сохранение стабильной ширины поля

Анимации и переходы состояний

Inputmask не управляет анимациями, но его события могут запускать transition слои дизайн-системы.

.input {
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
el.addEventListener("complete", () => {
  el.classList.add("animate-success");
});

Ключевой принцип — анимация должна быть отделена от логики маски.

Расширение Inputmask через дизайн-системные абстракции

В зрелых системах Inputmask не используется напрямую. Он инкапсулируется:

export function createPhoneMask(el) {
  return new Inputmask({
    mask: "+7 (999) 999-99-99",
    showMaskOnFocus: true
  }).mask(el);
}

Это позволяет:

  • централизовать правила масок
  • синхронизировать UX между продуктами
  • исключить расхождения в форматах ввода

Такой слой становится частью дизайн-системы на уровне поведения, а не только стилей.