Перейти к содержимому
EN
Масштаб
Плотность
Тема
Акцент

Агентный слой

Поле ввода

Поле, в котором пишут реплику: одна рамка держит текст и контролы, которые его отправляют.

Компонент
контекст 90%
Разметка
<div class="inst-stack inst-stack--tight">
  <div class="inst-composer">
    <textarea class="inst-textarea" rows="1" placeholder="Опишите задачу или задайте вопрос" aria-label="Опишите задачу или задайте вопрос"></textarea>
    <div class="inst-composer-bar inst-composer-bar--edge">
      <button class="inst-btn inst-btn--sm inst-btn--icon inst-btn--ghost" type="button" aria-label="Отправить"><svg class="inst-icon" aria-hidden="true"><use href="#i-enter"/></svg></button>
    </div>
  </div>
  <div class="inst-composer-bar">
    <button class="inst-btn inst-btn--xs inst-btn--ghost" type="button" aria-haspopup="menu">Авто</button>
    <button class="inst-btn inst-btn--xs inst-btn--icon inst-btn--ghost" type="button" aria-label="Добавить контекст"><svg class="inst-icon" aria-hidden="true"><use href="#i-plus"/></svg></button>
    <span class="inst-cluster-spacer"></span>
    <button class="inst-btn inst-btn--xs inst-btn--ghost" type="button" aria-haspopup="menu">Opus 5</button>
    <span class="inst-badge" data-tone="warn">контекст 90%</span>
  </div>
</div>

Контракт

Что Обязательно Почему
aria-label или <label> у поля да Плейсхолдер — не имя: он исчезает на первом же символе
Контролы сообщения внутри рамки да Вложение и отправка принадлежат полю. Снаружи они второй объект, и кольцо фокуса подсвечивает половину контрола. То, что переживает сообщение, — режим, модель, контекст — стоит под рамкой отдельной полосой, и быть вторым объектом там как раз и требуется
<button type="button"> для отправки да Внутри формы кнопка без типа её отправляет
Отправка по Enter нет Кит оформляет; отправляет ли Enter или переносит строку — решение приложения, и обоснованы оба

Граница живёт на рамке, а поле отдаёт свою наружу — тот же приём, что у группы поля. Текстовое поле рядом с панелью инструментов даёт две границы с зазором; группа поля растягивает кнопку отправки на всю высоту; панель вокруг обоих рисует рамку внутри рамки.

Доступность

Имя На поле, а не на рамке: рамка это <div> и фокуса не берёт
Фокус Кольцо охватывает рамку целиком, поэтому подсвечен тот объект, в котором находится клавиатура
Контраст Граница — --border-control, 3.46:1: линия несущая, потому что здесь граница и есть контрол

Варианты

Строка может занимать всю ширину внутри рамки, а не хвостовой слот. Это форма для композера, у которого контролов больше, чем отправка: вложение, модель, счётчик остатка. Цена — пустое поле теряет свою одну строку, и это размен.

Пример
opus 12 480 / 200 000
Разметка
<div class="inst-composer">
  <textarea class="inst-textarea" rows="3" placeholder="Опишите задачу" aria-label="Опишите задачу"></textarea>
  <div class="inst-composer-bar">
    <button class="inst-btn inst-btn--xs inst-btn--ghost" type="button">Вложить</button>
    <span class="inst-badge">opus</span>
    <span class="inst-cluster-spacer"></span>
    12 480 / 200 000
    <button class="inst-btn inst-btn--sm inst-btn--primary" type="button">Отправить</button>
  </div>
</div>

Размеры

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

Поэтому нижняя граница снята, а высоту задаёт содержимое — через field-sizing: content. Потолок — восемь контролов, дальше поле прокручивается, а не съедает ленту над собой. Там, где field-sizing не реализован, поле принимает высоту своего rows и прокручивается: поле поменьше, а не сломанное, и альтернатива — скрипт, которого у кита нет.

inst-composer-bar--edge ставит строку в хвостовой слот, а не на отдельную строку, — именно это и делает одну строку возможной: отправка это один контрол, и своей строки он не заслуживает. Слот прижат к низу поля: на одной строке это читается как по центру, на десяти — остаётся у последней.

Пример
Разметка
<div class="inst-composer">
  <textarea class="inst-textarea" rows="1" aria-label="Выросшее поле">Поле растёт вместе с тем, что в него набирают, а отправка остаётся у последней строки, а не висит посреди пустой рамки.</textarea>
  <div class="inst-composer-bar inst-composer-bar--edge">
    <button class="inst-btn inst-btn--sm inst-btn--icon inst-btn--ghost" type="button" aria-label="Отправить"><svg class="inst-icon" aria-hidden="true"><use href="#i-enter"/></svg></button>
  </div>
</div>

API

Классы
ИмяЧто делает
класс
inst-composerРамка. Несёт границу, фон и кольцо фокуса
inst-composer-barКонтролы. Внутри рамки они принадлежат сообщению, под ней — сессии. Всё, что не контрол, читается метаданными, а глифы берут мелкую иконку
модификатор
inst-composer-bar--edgeСтрока в хвостовом слоте рядом с полем, а не на отдельной строке. Прижата к низу, поэтому пустой композер высотой в одну строку
Токены
ИмяЗначениеОткуда
токен
--control-h-mdПотолок роста — восемь таких32pxбаза +14: плотность · масштаб
--border-controllight-dark(oklch(0 0 0 / 0.46), oklch(1 0 0 / 0.38))одно на все ячейки
--accent-borderlight-dark(var(--a-4), var(--a-3))одно на все ячейки
--surface-fieldvar(--surface-raised)одно на все ячейки
--focus-ringlight-dark(var(--a-4), var(--a-3))одно на все ячейки
--radius-lg12pxбаза +4: масштаб
--control-pad-smcalc(var(--control-h-sm) * var(--control-ratio-sm))одно на все ячейки
--space-36pxодно на все ячейки
--text-xs0.78125remбаза +4: масштаб
--text-mutedlight-dark(var(--n-8), var(--n-6))одно на все ячейки

Почему кит устроен именно так — конституция · Открытый код под MIT, github.com/keshon/instrument