Управление услугами · Новый ресурс

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

Раскройте возможности Anvil2 на конкретном примере: модель токенов из трёх уровней, проверенные значения ролей для светлой и тёмной тем и полный сценарий назначения услуги с синхронизированными состояниями специалиста, времени и проверки.

Руководство OpenDesign7 глав38 справочных записейАвторское руководство по приложению
01

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

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

Подберите специалиста для выездного обслуживанияОткрыть полный пример
Самостоятельный пример HTML со значениями ролей токенов Hammer 3.5.0. · Справочник по реализации
02

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

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

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

Источник
Название / правилоЗначение / конфигурацияПрименение
BackgroundColorDefaultlight #ffffff / dark #1a1a1aОсновная поверхность приложения.
BackgroundColorDefaultHoverlight #f9f9f9 / dark #292929Фон при наведении.
BackgroundColorPrimarylight #0265dc / dark #70b1ffЗаливка основного действия.
BackgroundColorPrimaryHoverlight #1d4ca3 / dark #9ecaffНативная роль при наведении для основного действия.
BackgroundColorPrimaryFaintlight #fafcff / dark #000b19Сдержанная поверхность выбранного элемента.
ForegroundColorSubduedlight #707070 / dark #c4c4c4Вспомогательный текст.
ForegroundColorOnPrimarylight #ffffff / dark #1a1a1aТекст в паре с основной заливкой.
ForegroundColorPrimarylight #0265dc / dark #70b1ffИнтерактивный передний план.
BorderColorSubduedlight #e6e6e6 / dark #404040Границы с низким визуальным акцентом.
BorderColorPrimarylight #0265dc / dark #9ecaffИнтерактивная граница.
StatusColorInfolight #0265dc / dark #70b1ffИнформационное состояние.
StatusColorDangerlight #d62100 / dark #ff5b3dТребует немедленного внимания.
StatusColorSuccesslight #077e50 / dark #0bd085Локальная проверка завершена.
StatusColorWarninglight #ffbe00 / dark #ffe278Предупреждение, не требующее немедленных действий.

Примитивная геометрия и адаптивные значения

Источник
Название / правилоЗначение / конфигурацияПрименение
BreakpointSm640pxОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
BreakpointMd768pxОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
BreakpointLg1024pxОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
BreakpointXl1280pxОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
BreakpointXxl1536pxОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
DurationDefault200msОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
DurationInstant0msОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
DurationFast100msОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
DurationSlow300msОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius00remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius10.1875remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius20.375remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius30.5625remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius40.75remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius50.9375remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius61.125remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius71.3125remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.
Radius81.5remОпубликованный примитив; фактические роли в приложении задаются семантическими алиасами и алиасами компонентов.

Контракт переопределения ThemeProvider

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

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

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

DESIGN-servicetitan-anvil.md

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

Скачать руководство

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

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

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

Из этого руководства OpenDesign

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Нативная интеграция дизайн-системы 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 с оформлением на основе токенов и зависимым состоянием формы.

html
<!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.

jsx
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 независим от него.
04

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

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

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

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

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

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

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

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