Дизайн корпоративных продуктов · Новый ресурс

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

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

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

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

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

Планировщик периода поездкиОткрыть полный пример
Автономная интерпретация HTML с нативными полями даты и проверенными значениями темы Duet по умолчанию; это не среда выполнения календаря Duet. · Справочник по реализации
02

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

Снимок общедоступной документации Duet: компоненты основной системы, токены темы LocalTapiola по умолчанию и описанная интеграция версии 10.7.0/CDN. Это не отдельный пакет @duetds/date-picker версии 1.x. Некоторые токены скругления по-прежнему отмечены статусом In Review.

Роли тем по умолчанию и Turva

Источник
Название / правилоЗначение / конфигурацияПрименение
--color-primary#0077b3 Действие LocalTapiola.
--color-primary-dark#004b81 Более тёмное состояние взаимодействия.
--color-primary-darker#00619b Роль ссылки.
--color-primary-light#c6e1ee Оттенок для диапазона.
--color-secondary#082a4d Основной текст.
--color-primary-turva#e61740 Действие Turva, отдельная тема.
--color-danger#d61c52 Проверка.
--radius-default4pxПоля ввода и контейнеры.
--radius-pill20remКнопки.
--radius-medium8px; In ReviewНе заявляйте о готовности к промышленному использованию.

Токены отступов

Источник
Название / правилоЗначение / конфигурацияПрименение
--space-xxx-small2pxМинимальный.
--space-xx-small4pxМинимальный.
--space-x-small8pxКомпактный.
--space-small12pxМаленький.
--space-medium16pxПо умолчанию.
--space-large20pxМежду компонентами.
--space-x-large28pxУвеличенный интервал.
--space-xx-large36pxГоризонтальные внутренние отступы кнопки.
--space-xxx-large48pxРаздел.
--size-layout-max-width1110pxЗаданный в документации максимальный размер макета.

Шкала типографики

Источник
Название / правилоЗначение / конфигурацияПрименение
$font-size-medium16px / 1remОсновной текст и H5.
$font-size-large20px / 1.25remH4.
$font-size-x-large24px / 1.5remH3.
$font-size-xx-large36px / 2.25remH2.
$font-size-xxx-large48px / 3remH1.
$font-size-xxxx-large72px / 4.5remH0.

Контракт Date Picker

Источник
Название / правилоЗначение / конфигурацияПрименение
value / min / maxYYYY-MM-DD stringsЗначения хранятся в ISO, а не в виде текста для отображения.
duetChangedetail.value and detail.valueAsDateСобытие выбора даты.
validitybadInput / valueMissing / rangeOverflow / rangeUnderflow / customErrorПроверьте значение, прежде чем принять его.
setFocus()Promise<void>Установите фокус на поле, а не вызывайте focus() у родительского элемента.
show() / hide()Promise<void>Календарь и управление фокусом.
languageDeprecatedИспользуйте атрибут html lang.
disallow-weekendsBooleanИсключение на уровне компонента.

Свойства действия

Источник
Название / правилоЗначение / конфигурацияПрименение
variation="primary"Main actionОдно основное действие на представление.
submitBoolean, default falseЯвное включение отправки формы.
urlAnchor destinationСемантика навигации.
expand100% containerШирокое действие.
themedefault / turva / inherited emptyВыбор бренда.
margin="none"Remove automatic marginПриложение управляет отступами.
03

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

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

DESIGN-duet.md

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

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

Дизайн-система Duet Основная система Duet и picker с открытым исходным кодом — разные продукты

Сохраняйте контракт пакета вместе с примером

Duet предоставляет LocalTapiola и Turva тематические компоненты и базовые стили. Отдельно опубликованный проект @duetds/date-picker — более узкий дистрибутив с открытым исходным кодом. Совпадение имени тега не означает, что его свойства, настройка локализации или переменные CSS взаимозаменяемы с текущей основной дизайн-системой.

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

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

Прочитать вспомогательную спецификацию

Дизайн-система Duet Фирменный синий и сигнальный красный выполняют разные задачи

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

В теме LocalTapiola по умолчанию синий цвет действия сочетается с тёмно-синим основным текстом. В Turva используются собственный красный цвет действия и отдельный стиль вторичного текста. Замена только цвета действия не учитывает остальные связи между элементами темы. В текущей таблице старые оттенки Turva также отмечены как устаревшие; копирование всех исторических цветов палитры закрепило бы устаревшие рекомендации.

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

Прочитать вспомогательную спецификацию

Дизайн-система Duet Иерархия строится вокруг основного текста размером 16px

Для масштаба не требуется огромный заголовок повсюду

В крупной части шкалы размеров Duet используются 16, 20, 24, 36, 48 и 72px. Во встроенной панели планирования задан размер 24px, чтобы кнопка действия и подписи дат оставались заметными. Маркетинговый размер 72px не является значением по умолчанию для компактной формы.

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

Прочитать вспомогательную спецификацию

Дизайн-система Duet Используйте фактические интервалы 20px и 28px

Шкала Duet не сводится к универсальным интервалам в восемь пикселей

Помимо привычных степеней двойки, шкала включает 12, 20, 28 и 36px. Эти промежуточные интервалы важны для точного воспроизведения ритма формы в системе. В широком макете используйте интервал 20px между двумя полями; при расположении столбцом сохраняйте каждую подпись рядом с соответствующим полем. Точку перехода между макетами приложение выбирает самостоятельно.

В элементах управления по умолчанию используется скругление углов 4px, а для кнопок — форма капсулы. Некоторые новые значения радиуса контейнеров в справочнике токенов явно помечены статусом In Review; в этом руководстве этот статус сохранён, а не заменён утверждением, будто одобрены все перечисленные токены.

Прочитать вспомогательную спецификацию

Дизайн-система Duet Храните значения в формате ISO и отдельно указывайте видимый формат

Не обрабатывайте локализованные строки отображения как данные

Значения и границы компонента задаются в формате YYYY-MM-DD. Событие выбранной даты содержит строку и объект Date. Для сравнения и хранения используйте строку; локализованное отображение в поле относится к представлению данных. Текущая точка входа для локализации — язык документа, а устаревшее свойство language объявлено устаревшим.

В примере разрешены даты только в июне 2027 года. Этот фиксированный интервал делает тестирование предсказуемым и явно обозначен как ограничение примера. В рабочей форме допустимый интервал следует определять правилами продукта и пояснять рядом с полем.

Прочитать вспомогательную спецификацию

Дизайн-система Duet Допустимая дата не обязательно означает допустимый диапазон

Проверяйте взаимосвязь дат после проверки каждого поля

Объект validity компонента описывает ошибки отдельных полей. Дата возвращения, которая предшествует дате отъезда, всё ещё может быть допустимой календарной датой, поэтому приложение должно отдельно проверить их взаимное расположение. В нативном примере назначается понятная ошибка и вызывается setFocus() — документированный метод, который переводит фокус на внутреннее поле ввода.

Количество дней рассчитывается включительно: если отъезд и возвращение приходятся на один день, результатом будет один календарный день. Преобразование дат ISO в полночь по UTC не позволяет переходу на летнее время превратить календарный интервал в дробное значение длительности. Это оригинальная логика приложения, а не правило ценообразования Duet.

  1. Отклоняйте отсутствующие, неправильно сформированные значения и даты за пределами допустимого интервала.
  2. Отклоняйте дату возвращения, предшествующую дате отъезда.
  3. Переводите фокус на поле, которое нужно исправить.
  4. Рассчитывайте диапазон включительно, только если обе проверки пройдены.

Прочитать вспомогательную спецификацию

Дизайн-система Duet Основное действие не подразумевает отправку формы

В Duet для отправки формы предусмотрено отдельное свойство submit

Кнопка основной системы включает отправку формы с помощью submit; по умолчанию это свойство равно false. В предоставленной композиции действие click подготавливает локальный результат, поэтому это свойство не требуется. Для перехода используется url, который задаёт семантику ссылки.

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

  • Используйте одно основное действие подготовки.
  • Укажите, что результат создаётся локально.
  • Не используйте формулировку об успехе, чтобы создать впечатление, будто полис оформлен.

Прочитать вспомогательную спецификацию

Дизайн-система Duet Зафиксируйте ресурсы основной системы как совместимый набор

Пример — документированный снимок, а не обещание использовать последнюю версию

На проверенной странице с примерами использования указаны компоненты версии 10.7.0 и CSS версии 5.1.4; в снимках поискового индекса уже упоминаются более поздние выпуски. В примере намеренно сохранена пара, непосредственно проверенная на странице. Перед внедрением в production выберите один выпуск и проверьте совместимость с CSS, шрифтами и поведением компонентов.

Поля даты — пользовательские элементы. Дождитесь их определения, прежде чем вызывать методы; назначайте обработчики для duetChange и читайте документированные данные события, не предполагая, что оно будет нативным событием change. Не используйте на одной странице модуль CDN основной системы вместе с автономным средством выбора дат: оба определяют один и тот же тег.

Прочитать вспомогательную спецификацию

Дизайн-система Duet Примеры компонентов дизайн-системы

Полноценный автономный интерактивный пример

Сохраните весь документ как index.html и откройте его в браузере. Для автономной работы HTML используются нативные поля даты и проверенные значения темы Duet по умолчанию; это не среда выполнения календаря Duet.

html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Duet travel period interpretation</title><style>*{box-sizing:border-box}body{margin:0;background:#f0f7fa;color:#082a4d;font:16px/1.5 system-ui,sans-serif}main{max-width:1000px;margin:auto;padding:36px 28px}.eyebrow{font-size:12px;letter-spacing:2px;text-transform:uppercase;margin-bottom:12px}h1{font-size:36px;line-height:1.2;font-weight:600;margin:0 0 20px}p{margin:0 0 20px}.hero{display:grid;grid-template-columns:1fr 300px;gap:28px;align-items:center;margin-bottom:28px}.art{height:170px;position:relative;overflow:hidden;border-radius:50%;background:#c6e1ee}.sun{position:absolute;top:25px;right:60px;width:36px;height:36px;border-radius:50%;background:white}.hill{position:absolute;background:#0077b3;width:210px;height:170px;bottom:-95px;left:-10px;transform:rotate(25deg);border-radius:24px}.hill.two{left:135px;background:#00619b;bottom:-110px;transform:rotate(-25deg)}.road{position:absolute;width:150px;height:200px;border:12px solid white;border-radius:50%;top:65px;left:95px;transform:rotate(25deg)}.card{background:white;border-radius:4px;padding:28px;box-shadow:0 2px 8px #082a4d0d;display:grid;grid-template-columns:1fr 230px;gap:28px}h2{font-size:24px;line-height:1.3;margin:0 0 20px}.fields{display:grid;grid-template-columns:1fr 1fr;gap:20px}label{display:block;font-weight:600;margin-bottom:8px}input{width:100%;max-width:100%;min-width:0;border:1px solid #909599;border-radius:4px;font:inherit;color:inherit;padding:12px;background:white}input[aria-invalid=true]{border-color:#d61c52}button{background:#0077b3;color:white;border:0;border-radius:20rem;padding:12px 36px;font:600 16px/1.5 system-ui;cursor:pointer}button:hover{background:#004b81}button:focus-visible,input:focus-visible{outline:3px solid #082a4d;outline-offset:3px}.error{font-size:14px;color:#d61c52;min-height:24px;margin:8px 0 20px}.calendar{background:#f0f7fa;border-radius:4px;padding:20px}.calendar strong{display:block;margin-bottom:12px}.grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;font-size:12px;text-align:center}.grid span{padding:3px 0}.grid .in-range{background:#c6e1ee}.grid .endpoint{background:#0077b3;color:white;border-radius:50%}.caption{font-size:12px;margin:12px 0 0}.result{margin-top:20px;background:#e4f1f7;padding:20px;border-radius:4px}.result:empty{display:none}.boundary{font-size:12px;margin-top:28px}.hint{font-size:14px}@media(max-width:650px){main{padding:24px 16px}.hero{grid-template-columns:1fr}.art{display:none}h1{font-size:28px}.card{grid-template-columns:1fr;padding:20px}.fields{grid-template-columns:1fr;gap:16px}.calendar{max-width:300px;width:100%}button{width:100%;padding:12px 20px}}
</style><main><div class="hero"><div><div class="eyebrow">Travel period planner</div><h1>Make time for a change of scenery.</h1><p>Choose a departure and return date to explore an inclusive calendar-day count.</p></div><div class="art" aria-hidden="true"><div class="sun"></div><div class="hill"></div><div class="hill two"></div><div class="road"></div></div></div><div class="card"><section><h2>When will you travel?</h2><p class="hint" id="hint">Use dates from 1–30 June 2027. This fixed month is for demonstration.</p><form novalidate><div class="fields"><div><label for="start">Departure</label><input id="start" type="date" min="2027-06-01" max="2027-06-30" value="2027-06-10" required aria-describedby="hint error"></div><div><label for="end">Return</label><input id="end" type="date" min="2027-06-01" max="2027-06-30" value="2027-06-17" required aria-describedby="hint error"></div></div><div class="error" id="error" role="alert"></div><button type="submit">Prepare date range</button></form></section><aside class="calendar"><strong>June 2027</strong><div class="grid" id="calendar" aria-hidden="true"></div><p class="caption" id="range-caption">10–17 June selected</p></aside></div><div class="result" id="result" role="status"></div><p class="boundary">Independent HTML interpretation of Duet tokens with native date fields. The calendar image is a non-interactive range overview. No insurance quote, booking or policy is created.</p></main><script>const start=document.querySelector('#start'),end=document.querySelector('#end'),result=document.querySelector('#result'),error=document.querySelector('#error');function valid(){return start.validity.valid&&end.validity.valid&&start.value&&end.value&&end.value>=start.value}function draw(){let days=['M','T','W','T','F','S','S'].map(d=>'<span>'+d+'</span>').join('')+'<span></span>';for(let i=1;i<=30;i++){let iso='2027-06-'+String(i).padStart(2,'0'),range=valid()&&iso>=start.value&&iso<=end.value,edge=range&&(iso===start.value||iso===end.value);days+='<span class="'+(edge?'endpoint':range?'in-range':'')+'">'+i+'</span>'}document.querySelector('#calendar').innerHTML=days;document.querySelector('#range-caption').textContent=valid()?start.value+' to '+end.value:'Choose a valid range to highlight it.'}function changed(){result.textContent='';error.textContent='';start.removeAttribute('aria-invalid');end.removeAttribute('aria-invalid');draw()}[start,end].forEach(el=>el.oninput=changed);document.querySelector('form').onsubmit=e=>{e.preventDefault();let bad;if(!start.value||!start.validity.valid){error.textContent='Choose a departure date in June 2027.';bad=start}else if(!end.value||!end.validity.valid){error.textContent='Choose a return date in June 2027.';bad=end}else if(end.value<start.value){error.textContent='Return must be on or after departure.';bad=end}if(bad){bad.setAttribute('aria-invalid','true');bad.focus();result.textContent='';return}const days=(Date.parse(end.value+'T00:00:00Z')-Date.parse(start.value+'T00:00:00Z'))/86400000+1;result.textContent=days+' calendar '+(days===1?'day':'days')+', including departure and return. Local draft: '+start.value+' to '+end.value+'. No policy created.'};draw();</script></html>

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

  • В этом документе используются нативные элементы управления датой браузера. В отдельном примере Duet применяется средство выбора дат основной системы; не путайте его с автономным пакетом выбора дат с открытым исходным кодом.

Проверьте пару средств выбора дат основной системы

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

html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Date range draft</title>
<link rel="stylesheet" href="https://cdn.duetds.com/api/css/5.1.4/lib/duet.min.css">
<script type="module" src="https://cdn.duetds.com/api/components/10.7.0/lib/duet/duet.esm.js"></script>
<style>main{max-width:680px;margin:auto;padding:28px}.range{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:576px){.range{grid-template-columns:1fr}}</style>
<main><h1>Choose a travel period</h1><p>Demonstration only. No insurance is quoted or purchased.</p><div class="range">
<duet-date-picker id="start" label="Departure" required expand value="2027-06-10" min="2027-06-01" max="2027-06-30"></duet-date-picker>
<duet-date-picker id="end" label="Return" required expand value="2027-06-17" min="2027-06-01" max="2027-06-30"></duet-date-picker></div>
<duet-button id="prepare" variation="primary">Prepare date range</duet-button>
<p id="result" role="status"></p></main><script type="module">
await customElements.whenDefined('duet-date-picker');
const start=document.querySelector('#start'),end=document.querySelector('#end'),result=document.querySelector('#result');
function error(picker){const v=picker.validity;if(!picker.value)return 'Choose a date.';if(v&&!v.valid)return 'Choose a valid date in June 2027.';return '';}
function validate(){start.error=error(start);end.error=error(end);if(!start.error&&!end.error&&end.value<start.value)end.error='Return must be on or after departure.';return !start.error&&!end.error;}
[start,end].forEach(p=>p.addEventListener('duetChange',()=>{result.textContent='';validate()}));
document.querySelector('#prepare').addEventListener('click',async()=>{if(!validate()){await(start.error?start:end).setFocus();return;}const a=Date.parse(start.value+'T00:00:00Z'),b=Date.parse(end.value+'T00:00:00Z');result.textContent=`Local range: ${start.value} to ${end.value}, ${(b-a)/86400000+1} calendar days inclusive. No policy created.`;});
</script></html>

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

  • Здесь используется @duetds/components через CDN основной системы, а не @duetds/date-picker 1.x.
  • Диапазон с одинаковыми начальной и конечной датами составляет один календарный день включительно.
  • При подсчёте используется арифметика UTC, чтобы избежать смещения из-за перехода на летнее время.
04

Дизайн-система Duet Настройка агента

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

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

html
<!doctype html><html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Date range draft</title>
<link rel="stylesheet" href="https://cdn.duetds.com/api/css/5.1.4/lib/duet.min.css">
<script type="module" src="https://cdn.duetds.com/api/components/10.7.0/lib/duet/duet.esm.js"></script>
<style>main{max-width:680px;margin:auto;padding:28px}.range{display:grid;grid-template-columns:1fr 1fr;gap:20px}@media(max-width:576px){.range{grid-template-columns:1fr}}</style>
<main><h1>Choose a travel period</h1><p>Demonstration only. No insurance is quoted or purchased.</p><div class="range">
<duet-date-picker id="start" label="Departure" required expand value="2027-06-10" min="2027-06-01" max="2027-06-30"></duet-date-picker>
<duet-date-picker id="end" label="Return" required expand value="2027-06-17" min="2027-06-01" max="2027-06-30"></duet-date-picker></div>
<duet-button id="prepare" variation="primary">Prepare date range</duet-button>
<p id="result" role="status"></p></main><script type="module">
await customElements.whenDefined('duet-date-picker');
const start=document.querySelector('#start'),end=document.querySelector('#end'),result=document.querySelector('#result');
function error(picker){const v=picker.validity;if(!picker.value)return 'Choose a date.';if(v&&!v.valid)return 'Choose a valid date in June 2027.';return '';}
function validate(){start.error=error(start);end.error=error(end);if(!start.error&&!end.error&&end.value<start.value)end.error='Return must be on or after departure.';return !start.error&&!end.error;}
[start,end].forEach(p=>p.addEventListener('duetChange',()=>{result.textContent='';validate()}));
document.querySelector('#prepare').addEventListener('click',async()=>{if(!validate()){await(start.error?start:end).setFocus();return;}const a=Date.parse(start.value+'T00:00:00Z'),b=Date.parse(end.value+'T00:00:00Z');result.textContent=`Local range: ${start.value} to ${end.value}, ${(b-a)/86400000+1} calendar days inclusive. No policy created.`;});
</script></html>

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

Промпт для AI-агента
Создайте диапазон дат Duet основной системы с помощью двух полей выбора даты с метками, значений ISO и проверок корректности. Отдельно проверьте порядок дат, при ошибках вызывайте setFocus и используйте явно обозначенное основное действие для локальной подготовки. Не подключайте к той же странице автономный пакет выбора дат.
05

Дизайн-система Duet Источники и область применения

Независимая интерпретация OpenDesign. Самостоятельный планировщик; полис, бронирование или предложение не создаются. У основной системы Duet и её отдельного средства выбора дат с открытым исходным кодом разные контракты.