# Дизайн-система ServiceTitan Anvil

## Предварительный просмотр интерфейса дизайн-системы ServiceTitan Anvil

Переключайте светлую и тёмную темы, выбирайте доступных специалистов и проверяйте синхронизированный локальный план.

## Справочник по дизайн-системе ServiceTitan Anvil

Экспортируемые токены основной темы @servicetitan/hammer-token 3.5.0 и документация по темам Anvil2.

### Семантические пары для светлой и тёмной тем

| Название / правило | Значение / конфигурация | Применение |
| --- | --- | --- |
| BackgroundColorDefault | light #ffffff / dark #1a1a1a | Основная поверхность приложения. |
| BackgroundColorDefaultHover | light #f9f9f9 / dark #292929 | Фон при наведении. |
| BackgroundColorPrimary | light #0265dc / dark #70b1ff | Заливка основного действия. |
| BackgroundColorPrimaryHover | light #1d4ca3 / dark #9ecaff | Нативная роль при наведении для основного действия. |
| BackgroundColorPrimaryFaint | light #fafcff / dark #000b19 | Сдержанная поверхность выбранного элемента. |
| ForegroundColorSubdued | light #707070 / dark #c4c4c4 | Вспомогательный текст. |
| ForegroundColorOnPrimary | light #ffffff / dark #1a1a1a | Текст в паре с основной заливкой. |
| ForegroundColorPrimary | light #0265dc / dark #70b1ff | Интерактивный передний план. |
| BorderColorSubdued | light #e6e6e6 / dark #404040 | Границы с низким визуальным акцентом. |
| BorderColorPrimary | light #0265dc / dark #9ecaff | Интерактивная граница. |
| StatusColorInfo | light #0265dc / dark #70b1ff | Информационное состояние. |
| StatusColorDanger | light #d62100 / dark #ff5b3d | Требует немедленного внимания. |
| StatusColorSuccess | light #077e50 / dark #0bd085 | Локальная проверка завершена. |
| StatusColorWarning | light #ffbe00 / dark #ffe278 | Предупреждение, не требующее немедленных действий. |
### Примитивная геометрия и адаптивные значения

| Название / правило | Значение / конфигурация | Применение |
| --- | --- | --- |
| BreakpointSm | 640px | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| BreakpointMd | 768px | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| BreakpointLg | 1024px | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| BreakpointXl | 1280px | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| BreakpointXxl | 1536px | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| DurationDefault | 200ms | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| DurationInstant | 0ms | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| DurationFast | 100ms | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| DurationSlow | 300ms | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius0 | 0rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius1 | 0.1875rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius2 | 0.375rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius3 | 0.5625rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius4 | 0.75rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius5 | 0.9375rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius6 | 1.125rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius7 | 1.3125rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
| Radius8 | 1.5rem | Опубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов. |
### Контракт переопределения ThemeProvider

| Название / правило | Значение / конфигурация | Применение |
| --- | --- | --- |
| theme.semantic | semantic token overrides | Измените роль, например BackgroundColorPrimary. |
| theme.component | component token overrides | Ограничьте изменение ролью компонента, например ButtonPrimaryBackgroundColor. |
| value | default/light token value | Укажите значение для светлой темы. |
| extensions.appearance.dark.value | dark appearance value | Задайте соответствующее значение для тёмной темы. |
| CSS output | --a2-<kebab-case-name> | ThemeProvider сериализует ключ токена в переменную CSS. |
| Primitive cascade | resolved at build time | Переопределение примитива во время выполнения не пересчитывает семантические алиасы. |
## Полное руководство по дизайн-системе ServiceTitan Anvil

Практическое руководство по выбору, применению и оценке этой системы для реального интерфейса.

Подробные рекомендации, отправная точка для реализации и ссылки на источники.

## Трёхуровневая архитектура токенов дизайн-системы ServiceTitan Anvil

### Располагаются примитивы под семантическими ролями

> В Anvil2 отдельно представлены исходные примитивы, семантические роли и алиасы для конкретных компонентов. Обычно продукту следует выбирать роль, описывающую смысл элемента интерфейса, а не копировать примитивный синий цвет в каждый элемент управления. В справочной таблице приведены точные экспортируемые значения и соответствующие им значения для тёмной темы, чтобы изменения темы можно было проверять попарно.

### Не рассчитывайте на пересчёт алиасов во время выполнения

В официальном руководстве по темам указано, что ссылки на примитивы разрешаются во время сборки. Изменение примитива через поставщик во время выполнения не приводит к автоматической генерации семантического слоя. Чтобы изменить цвета приложения, переопределите нужную семантическую роль; чтобы изменить один компонент, используйте уровень компонентов. Это предотвращает частичное изменение темы, при котором связанные элементы управления начинают выглядеть несогласованно.

## Светлая и тёмная темы дизайн-системы ServiceTitan Anvil

### Меняйте цвета текста и фона парами

В основном действии используются BackgroundColorPrimary и ForegroundColorOnPrimary. В светлой теме это синий фон и белый текст, а в тёмной — более светлый синий фон и тёмная подпись. Изменение только заливки нарушило бы согласованность пары. В полном учебном примере HTML используется light-dark() вместе с переключателем color-scheme, чтобы показать эти значения, не выдавая пример за ThemeProvider.

### Используйте цвета статусов с явно указанным значением

В Anvil2 значение статуса связано с цветовыми семействами. В локальной проверке используется роль успешного выполнения, но при этом точно указано, что произошло. Недоступный специалист обозначен текстом и имеет отключённый переключатель radio. Зелёный оттенок не используется для создания впечатления, будто выездное обслуживание отправлено: пример не подключается к системе диспетчеризации.

## Назначение обслуживания и состояния в дизайн-системе ServiceTitan Anvil

### Свяжите доступность со следующим выбором

При выборе специалиста варианты времени начала заменяются с учётом вымышленного расписания этого человека. Недоступный специалист остаётся видимым, но выбрать его нельзя. Это зависимость между двумя элементами управления, поэтому при изменении первого нужно сбросить второй до допустимого значения, а не сохранять время, доступное только другому специалисту.

### Отделяйте планирование от выполнения

Проверка назначения сохраняет локальный план и явно сообщает, что выезд не назначен. Для выполнения рабочего процесса в реальной системе перед подтверждением потребуются проверка разрешений, актуальный ответ о расписании и обработка конфликтов. В примере эта граница остаётся очевидной, но взаимодействие при этом полностью функционально и его можно изучить и адаптировать.

## Плотность интерфейса и читаемость дизайн-системы ServiceTitan Anvil

### Размещайте контекст задания рядом с выбором

Краткая сводка задания содержит описание работы, её длительность, район и приоритет. Варианты специалистов представлены в виде удобного для чтения вертикального списка, а предлагаемый выезд вынесен в отдельную панель. В интерфейсе используются нейтральные границы и сдержанный акцентный цвет, чтобы выбор и основное действие было легко найти. Полосы расписания обозначены как иллюстративные; фактическое время задаётся в элементе select.

### Сохраняйте порядок чтения на мобильных устройствах

При ширине экрана менее 768px блок предлагаемого выезда располагается после задания и списка специалистов. Элементы управления сохраняют удобный размер, а подписи переносятся по строкам естественным образом. В предварительном просмотре нет панелей с фиксированной высотой, поэтому под коротким содержимым не появляется большая пустая область. В одноколоночной версии сохраняется та же последовательность принятия решений, что и в широком макете.

## Нативная интеграция дизайн-системы ServiceTitan Anvil и полноценная альтернатива HTML

### Используйте документированную структуру поставщика

ThemeProvider в Anvil2 принимает переопределения семантических токенов и токенов компонентов с полями value и расширениями для тёмной темы. Ниже приведён полный пример поставщика, основанный на общедоступной документации. Это справочник по нативной интеграции, отличный от независимого примера HTML, который запускался локально.

### Не заявляйте о работе пакета без проверки

На этой странице приведена рабочая реализация HTML и проверенные данные токенов. Отдельный пример нативного поставщика предназначен для справки по интеграции и не используется в среде выполнения, показанной в предварительном просмотре. При внедрении компонентов React установите совместимую версию Anvil2, подключите необходимые стили и проверьте API компонентов, прежде чем заменять нативные элементы управления HTML.

## Доступность и проверка состояний дизайн-системы ServiceTitan Anvil

### Используйте нативную семантику форм

Специалисты представлены нативными переключателями radio с подписями, время начала выбирается в элементе select с подписью, а состояние элемента управления темой отображается через aria-pressed. Сообщение о проверке размещается в области статуса. За выбор radio отвечает встроенная в браузер модель клавиатурной навигации; видимое оформление фокуса не зависит от оформления выбранного элемента или состояния при наведении.

### Проверяйте изменения после подтверждения

При изменении специалиста или времени прежнее сообщение о проверке сбрасывается. Переключение темы сохраняет выбранных специалиста и время, поскольку визуальное изменение темы не должно стирать план. Тесты должны охватывать оба перехода, а также недоступность выбора и переполнение на узком экране, а не только проверять, что на кнопку можно нажать.

## Передача агенту и реализация дизайн-системы ServiceTitan Anvil

### Начните с полной модели состояний

Сохраните и запустите полный пример HTML, чтобы изучить его поведение. Пары значений для светлой и тёмной тем, полученные из токенов, показаны в верхней части CSS, а заданные в примере данные о доступности находятся в скрипте. Нативная реализация React может сохранить те же переходы состояний, заменив элементы управления на проверенные компоненты Anvil2.

1. Запустите полный пример HTML.

2. Выберите второго специалиста и убедитесь, что варианты времени начала изменились.

3. Проверьте план, затем измените время и убедитесь, что сообщение сброшено.

4. Переключите тему и убедитесь, что текущий выбор сохранился.

### Уточните, для чего нужны реальные данные

Попросите агента сохранить зависимый список времени, отключённого сотрудника, сброс проверки при изменении назначения и поведение оформления. Заменяйте вымышленные задание и сотрудников только при наличии реального контракта данных. Не выдавайте локальную проверку за успешную отправку, просто изменив текст кнопки.

- Перед реальной отправкой проверьте доступность.

- Не смешивайте семантические переопределения с переопределениями компонентов.

- Проверьте пары цветов текста и фона в светлой и тёмной темах.

## ServiceTitan Anvil Примеры компонентов дизайн-системы

### Полный пример локального назначения

Исходный HTML/CSS/JavaScript с оформлением на основе токенов и зависимым состоянием формы.

```
<!doctype html>
<html lang="en">
  <meta charset="utf-8" /><meta
    name="viewport"
    content="width=device-width,initial-scale=1"
  /><title>Dispatch assignment study</title
  ><style>
    :root {
      color-scheme: light;
      --surface: light-dark(#fff, #1a1a1a);
      --hover: light-dark(#f9f9f9, #292929);
      --text: light-dark(#040404, #fff);
      --muted: light-dark(#707070, #c4c4c4);
      --line: light-dark(#e6e6e6, #404040);
      --action: light-dark(#0265dc, #70b1ff);
      --on-action: light-dark(#fff, #1a1a1a);
      --faint: light-dark(#fafcff, #000b19);
      --success: light-dark(#077e50, #0bd085);
    }
    * {
      box-sizing: border-box;
    }
    body {
      margin: 0;
      padding: 24px;
      background: var(--surface);
      color: var(--text);
      font:
        16px/1.5 system-ui,
        sans-serif;
    }
    h1 {
      font-size: 28px;
      margin: 8px 0;
    }
    h2 {
      font-size: 20px;
      margin: 0 0 16px;
    }
    p {
      margin: 8px 0 16px;
    }
    .small {
      font-size: 13px;
      color: var(--muted);
    }
    header {
      display: flex;
      justify-content: space-between;
      gap: 24px;
      border-bottom: 1px solid var(--line);
      padding-bottom: 24px;
    }
    button,
    select {
      font: inherit;
      color: inherit;
      background: var(--surface);
      border: 1px solid var(--line);
      border-radius: 4px;
      padding: 10px 16px;
      min-height: 44px;
    }
    button {
      cursor: pointer;
    }
    button:hover {
      background: var(--hover);
    }
    button:focus-visible,
    select:focus-visible,
    input:focus-visible {
      outline: 3px solid var(--action);
      outline-offset: 3px;
    }
    main {
      display: grid;
      grid-template-columns: 1.4fr 1fr;
      gap: 24px;
      margin-top: 24px;
    }
    .board {
      border: 1px solid var(--line);
      border-radius: 8px;
      padding: 24px;
    }
    .job {
      padding: 16px;
      background: var(--faint);
      border-left: 4px solid var(--action);
      margin-bottom: 24px;
    }
    .job strong {
      display: block;
    }
    .people {
      display: grid;
      gap: 12px;
    }
    .person {
      display: grid;
      grid-template-columns: 24px 1fr auto;
      gap: 12px;
      padding: 16px;
      border: 1px solid var(--line);
      border-radius: 4px;
      align-items: center;
    }
    .person:has(:checked) {
      border-color: var(--action);
      background: var(--faint);
    }
    input {
      accent-color: var(--action);
      width: 18px;
      height: 18px;
    }
    .person small {
      display: block;
      color: var(--muted);
    }
    .person:has(:disabled) {
      background: var(--hover);
    }
    .timeline {
      display: flex;
      gap: 4px;
      margin: 20px 0;
    }
    .timeline span {
      height: 40px;
      flex: 1;
      background: var(--line);
      border-radius: 4px;
    }
    .timeline .open {
      background: var(--action);
    }
    .timeline-label {
      display: flex;
      justify-content: space-between;
      font-size: 13px;
      color: var(--muted);
    }
    label[for="time"] {
      display: block;
      margin: 16px 0 8px;
    }
    .primary {
      background: var(--action);
      color: var(--on-action);
      border-color: var(--action);
      margin-top: 24px;
    }
    .primary:hover {
      filter: brightness(0.95);
      background: var(--action);
    }
    #feedback {
      color: var(--success);
      padding-top: 16px;
    }
    footer {
      border-top: 1px solid var(--line);
      padding-top: 16px;
      margin-top: 24px;
      font-size: 13px;
      color: var(--muted);
    }
    @media (max-width: 767px) {
      body {
        padding: 16px;
      }
      main {
        grid-template-columns: 1fr;
      }
      .board {
        padding: 16px;
      }
      header {
        display: block;
      }
      header button {
        margin-top: 8px;
      }
      .person {
        grid-template-columns: 24px 1fr;
      }
      .person > span:last-child {
        grid-column: 2;
      }
    }
  </style>
  <body>
    <header>
      <div>
        <div class="small">SERVICE OPERATIONS / LOCAL PLANNING STUDY</div>
        <h1>Assign a service visit</h1>
        <p>Match a sample job with an available technician.</p>
      </div>
      <div>
        <button id="theme" aria-pressed="false">Dark appearance</button>
      </div>
    </header>
    <main>
      <section class="board">
        <h2>Job and technician</h2>
        <div class="job">
          <strong>Sample job #204 · Heating inspection</strong
          ><span>90 minutes · North district · Routine priority</span>
        </div>
        <div class="people" id="people"></div>
      </section>
      <aside class="board">
        <h2>Proposed visit</h2>
        <p id="technician"></p>
        <label for="time">Available start time</label
        ><select id="time"></select>
        <div class="timeline" aria-hidden="true">
          <span></span><span class="open"></span><span class="open"></span
          ><span></span><span></span><span></span>
        </div>
        <div class="timeline-label">
          <span>08:00</span><span>Illustrative day</span><span>17:00</span>
        </div>
        <p class="small">
          The time list is the source of available choices. The bars are a
          decorative schedule summary.
        </p>
        <button id="assign" class="primary">Review assignment</button>
        <p id="feedback" role="status"></p>
      </aside>
    </main>
    <footer>
      Original HTML using Anvil2 Hammer token 3.5.0 light/dark roles. Fictional
      technicians and schedules; no dispatch service connected.
    </footer>
    <script>
      const staff = [
        {
          id: "alex",
          name: "Alex Morgan",
          skill: "Heating specialist",
          slots: ["09:00", "11:00"],
        },
        {
          id: "sam",
          name: "Sam Rivera",
          skill: "General maintenance",
          slots: ["13:00", "15:00"],
        },
        { id: "lee", name: "Lee Chen", skill: "Unavailable today", slots: [] },
      ];
      let selected = "alex";
      const $ = (id) => document.getElementById(id);
      function update() {
        const p = staff.find((s) => s.id === selected);
        $("technician").textContent = p.name + " · " + p.skill;
        $("time").replaceChildren(...p.slots.map((t) => new Option(t, t)));
        $("feedback").textContent = "";
      }
      $("people").replaceChildren(
        ...staff.map((p) => {
          const l = document.createElement("label");
          l.className = "person";
          const input = document.createElement("input");
          input.type = "radio";
          input.name = "technician";
          input.checked = p.id === selected;
          input.disabled = p.slots.length === 0;
          input.onchange = () => {
            selected = p.id;
            update();
          };
          const text = document.createElement("span");
          text.innerHTML = `<strong>${p.name}</strong><small>${p.skill}</small>`;
          const availability = document.createElement("span");
          availability.textContent = p.slots.length
            ? p.slots.length + " start times"
            : "Unavailable";
          l.append(input, text, availability);
          return l;
        }),
      );
      $("time").onchange = () => {
        $("feedback").textContent = "";
      };
      $("assign").onclick = () => {
        $("feedback").textContent =
          "Local plan: " +
          staff.find((s) => s.id === selected).name +
          " at " +
          $("time").value +
          ". No visit has been dispatched.";
      };
      $("theme").onclick = () => {
        let dark = $("theme").getAttribute("aria-pressed") !== "true";
        $("theme").setAttribute("aria-pressed", dark);
        document.documentElement.style.colorScheme = dark ? "dark" : "light";
      };
      update();
    </script>
  </body>
</html>
```

Справочник по коду

- Сохраните файл как index.html; современные браузеры поддерживают light-dark(). Это не нативная среда выполнения Anvil2.

### Справочник по интеграции ThemeProvider

Задокументированная структура семантических переопределений в полном примере React. Проверьте совместимость с установленной версией Anvil2.

```
import React from 'react';
import { createRoot } from 'react-dom/client';
import { ThemeProvider } from '@servicetitan/anvil2';

const theme = {
  semantic: {
    BackgroundColorPrimary: {
      value: '#7c3aed',
      extensions: { appearance: { dark: { value: '#a78bfa' } } },
    },
  },
};

function App() {
  return (
    <ThemeProvider theme={theme}>
      <main>
        <h1>Theme integration check</h1>
        <div style={{ background: 'var(--a2-background-color-primary)', padding: 24 }}>
          <span style={{ color: 'var(--a2-foreground-color-on-primary)' }}>
            Primary role with the provider’s matching foreground role
          </span>
        </div>
      </main>
    </ThemeProvider>
  );
}
createRoot(document.getElementById('root')).render(<App />);
```

Справочник по коду

- Этот справочник по провайдеру сверён с официальной документацией; приведённый выше работающий пример HTML независим от него.

## Настройка агента для дизайн-системы ServiceTitan Anvil

### Отправная точка для реализации

Для запущенного примера пакет Anvil2 не требуется. Для отдельного примера ThemeProvider нужна совместимая нативная установка.

```
# Save the complete HTML example as index.html
python3 -m http.server 8080
# Open http://localhost:8080
```

### Запрос для вашего проекта

```
Создайте интерфейс назначения выездных работ, используя семантические роли Anvil2. Сохраните пары цветов текста и фона для светлой и тёмной тем, зависимый от выбранного техника список времени, отключение недоступных сотрудников и сброс проверки при изменении данных из полного примера. Явно выберите исходный HTML или проверенную установку Anvil2 React. Никогда не заявляйте об отправке задания без ответа от серверной части.
```

## Источники и область применения дизайн-системы ServiceTitan Anvil

Руководство по ServiceTitan Anvil2 для OpenDesign. Значения токенов извлечены из общедоступного набора токенов Hammer 3.5.0. Работающий пример использует оригинальный HTML, а не среду выполнения Anvil2 React; все техники и данные о доступности вымышлены.

- Настройка темы Уровни токенов, переопределения и значения оформления.

- Цвета Роли статусов и рекомендации по цветам интерактивных элементов.

- Интервалы Шкала отступов и интеграция утилит.

- Размеры Основы размеров.

- Архитектура библиотеки Границы пакетов и библиотек Anvil2.

## Связанные ресурсы дизайн-системы ServiceTitan Anvil

### Siemens iX

Изучите промышленные события с помощью явно заданной классификации уровней серьёзности, цветовых ролей с учётом темы и API Event List от iX. В этом руководстве создаётся локальный журнал оборудования, в котором проверенная запись не выдаётся за устранённую аварию оборудования.

### Sprout Social Seeds

Изучите фактические публичные токены цвета, интервалов, продуктовой типографики и границ в Seeds на примере полноценной редакторской очереди: с выбором нескольких элементов, пустыми состояниями после фильтрации и отменой действия.

### SumUp Circuit

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


- [Справочник по реализации](https://anvil.servicetitan.com/docs/web/foundations/theming)
- [Источник](https://anvil.servicetitan.com/docs/web/foundations/theming)
- [Источник](https://anvil.servicetitan.com/docs/web/foundations/sizing)
- [Справочник по коду](https://anvil.servicetitan.com/docs/web/foundations/theming)
- [Настройка темы Уровни токенов, переопределения и значения оформления.](https://anvil.servicetitan.com/docs/web/foundations/theming)
- [Цвета Роли статусов и рекомендации по цветам интерактивных элементов.](https://anvil.servicetitan.com/docs/web/foundations/colors)
- [Интервалы Шкала отступов и интеграция утилит.](https://anvil.servicetitan.com/docs/web/foundations/spacing)
- [Размеры Основы размеров.](https://anvil.servicetitan.com/docs/web/foundations/sizing)
- [Архитектура библиотеки Границы пакетов и библиотек Anvil2.](https://anvil.servicetitan.com/docs/library-architecture)
