Компоненты
Каталог компонентов Lemu. Все компоненты настраиваются общим словарём визуальных пропсов — он описан на странице Основные принципы; подключение библиотеки — на странице Установка.
Раскладка
FlexFlex — примитив раскладки: div с display: flex, у которого основные свойства флексбокса вынесены в пропсы. Нужен там, где раскладка одноразовая и заводить под неё CSS-модуль незачем — ряд кнопок в шапке, колонка полей формы, строка с иконкой и текстом.
GridGrid — примитив раскладки: div с display: grid, у которого свойства грида вынесены в пропсы. Нужен там, где раскладка двумерная — карточки в несколько колонок, шапка-сайдбар-контент, сетка полей формы.
GridItemGridItem — ячейка внутри Grid: div, у которого свойства размещения в сетке вынесены в пропсы. Оборачивать в него всех детей сетки не нужно — обычный элемент и так встаёт в следующую свободную ячейку. GridItem нужен только там, где ячейке требуется своё место: растянуться на несколько колонок или строк, встать в именованную область, начаться с конкретной дорожки.
PagePage — каркас страницы приложения на весь вьюпорт (100dvw × 100dvh).
PanelPanel — панель рабочей области: скруглённый блок на фоне приложения, из которых собирается интерфейс (панели сплита документов, тулбоксы).
ScrollAreaScrollArea — область прокрутки с собственным скроллбаром: нативный скрыт, вместо него поверх контента рисуется overlay-ползунок — места он не занимает и появляется, только когда содержимое не помещается. Своей высоты у области нет: она растягивается по родителю, поэтому скролл появится лишь там, где размер чем-то ограничен (зона контента панели, колонка доски, ячейка сплита). Скроллится одна ось за раз — её задаёт orientation. Компонент построен на Ark UI ScrollArea, остальные пропсы уходят на его Root. В части компонентов область уже встроена — Panel.Content и popover Breadcrumbs скроллят содержимое сами, оборачивать его не нужно.
TripletTriplet — раскладка строки из трёх равных колонок: left прижат к левому краю, center стоит по центру, right — к правому. Центр держится ровно посередине строки независимо от того, чем заполнены боковые слоты, — этим Triplet отличается от простого space-between. В каждый слот можно передать строку или произвольный ReactNode, несколько элементов в одном слоте выстраиваются в ряд с общим отступом. Своих отступов, фона и высоты у компонента нет — он тянется на всю ширину родителя, остальное задаёт хост. На Triplet построены шапки и подвалы Panel и Page: если такая раскладка нужна внутри них, отдельно оборачивать содержимое не нужно — достаточно их слотов left / center / right. Колонки равны, пока содержимое помещается в свою треть: слот шире трети раздвигает колонку и сдвигает центр.
Контент
AccordionAccordion — составной компонент сворачиваемых секций на список заголовков, под каждым — содержимое, которое раскрывается по клику.
AvatarAvatar — составной компонент для отображения фотографии или инициалов.
AvatarGroupAvatarGroup — стопка аватаров с наложением: соседние аватары наезжают друг на друга, между ними зазор цветом фона. Кто лежит поверх — задаёт stack.
BadgeBadge — составной компонент для небольшой подписи рядом с содержимым: статус, тип, метка.
BreadcrumbsBreadcrumbs — составной компонент хлебных крошек: клик по крошке открывает popover со смежными пунктами её уровня, переход — выбором пункта.
CardCard — составной компонент карточки: обложка, заголовок, описание, действия и нижняя строка с исполнителем и сроком.
CellCell — составной компонент строки списка: аватар, текстовый блок и действия в один ряд.
DataListDataList — список свойств сущности парами «подпись → значение»: панель деталей задачи, карточка профиля, сводка проекта.
DateDate — дата в интерфейсе: форматирует значение паттерном date-fns с русской локалью либо показывает относительное время («через 3 дня») с тултипом на точную дату. Рендерится тегом time с машинным dateTime. Компонент ничего не знает про смысл даты — цвет и иконку задаёт потребитель через getAppearance.
DescriptionDescription — составной компонент для пояснения рядом с содержимым: подсказка над формой, предупреждение в модалке, сообщение о последствиях действия.
EntityLabelEntityLabel — подпись сущности: аватар слева, имя и вторичная строка справа. Один компонент и для человека, и для организации — различает их только скругление аватара.
PlaceholderPlaceholder — заглушка на месте отсутствующего содержимого: пустой список, ненайденный документ, ещё не открытый редактор. Placeholder.Root занимает всю высоту родителя и центрирует части по ней, поэтому кладётся вместо содержимого целиком — не рядом с ним. Placeholder.Preview — иллюстрация или иконка (ширина картинки ограничена 400px), Placeholder.Content — текст, Placeholder.Actions — ряд кнопок (сами кнопки ставит потребитель). Порядок частей не фиксирован: они идут в том порядке, в котором их написали.
SkeletonSkeleton — плашка-заглушка на время загрузки: серый блок с бегущим шиммером, форму задают width, height и radius.
Контролы
ButtonButton — составной компонент кнопки действия.
ButtonIconButtonIcon — квадратная кнопка с иконкой вместо текста.
PaginationPagination — переключатель страниц длинного списка: стрелка назад, номера страниц и стрелка вперёд.
RippleRipple — эффект волны от точки нажатия. Компонент вешает обработчик pointerdown на свой родительский элемент, поэтому достаточно вставить <Ripple /> внутрь контейнера с position: relative и overflow: hidden — так он и работает внутри Button и ButtonIcon. Волна декоративна (aria-hidden) и кликов не перехватывает. Цвет по умолчанию берётся из CSS-переменной --ripple-color родителя с откатом на currentColor; при включённом «уменьшить движение» анимация сворачивается почти мгновенно.
TabsTabs — составной компонент вкладок в виде «пилюли».
Формы
CalendarCalendar — сетка месяца с выбором даты, собранная на Ark DatePicker в инлайновом режиме: поля ввода и поповера у неё нет, календарь всегда раскрыт. Месяц листается стрелками, а заголовок шапки переключает сетку: дни → месяцы → годы, и обратно вниз по выбранному значению — так до нужной даты в другом году доходят за три клика.
CheckboxCheckbox — составной компонент выбора «да / нет» на Ark Checkbox.
EditableEditable — текст, который правится по двойному клику на месте. До двойного клика это обычный текст: его можно выделить и скопировать, поле ввода появляется только на время правки. Значение — контролируемая модель, но с задержкой: набранное живёт внутри компонента, а наружу уходит одним onValueCommit — по Enter или клику мимо; Esc отменяет правку. Пока поле не редактируют, внешний value побеждает — свежие данные с сервера доезжают до превью сами. Типографику компонент наследует от родителя: он встраивается в чужой текст (заголовок ячейки, абзац описания), а не стоит отдельным полем, как Input и Textarea.
FieldField — составной компонент оболочки формового контрола: лейбл сверху, иконки и кнопка «Очистить» поверх самого контрола, текст ошибки снизу.
FieldsetFieldset — обёртка-группа для нескольких контролов: заголовок (Legend) сверху, под ним необязательная подсказка, затем сами контролы, а снизу текст ошибки на всю группу. В отличие от Field, который одевает один контрол, Fieldset ничего не оборачивает — он раскладывает переданные children и кладёт вокруг них общие подписи. Внутрь передают уже готовые контролы набора (Input, Textarea, Select, Checkbox и т.д.), а не голые элементы. direction задаёт раскладку: column (по умолчанию) — контролы друг под другом на всю ширину, row — в один ряд одинаковой ширины. Непустой error помечает группу как invalid и рисует ErrorText снизу.
FormForm — обёртка над form, которая раскладывает группы полей колонкой и держит между ними единый отступ. Нужна там, где раньше каждая форма заводила свой CSS-модуль с display: flex и gap: величина зазора между группами живёт в ДС, поэтому все формы приложения выглядят одинаково.
InputInput — однострочное текстовое поле. Готовый контрол, собранный на Field: сам одевает нативный input в оболочку с лейблом, иконками, текстом ошибки и кнопкой «Очистить» и пробрасывает пропсы Field (label, error, iconLeft, iconRight). Значение — контролируемая модель: пара value / onValueChange (onValueChange отдаёт уже строку, не событие). type задаёт нативный тип поля (text, email, password, number…). clearable добавляет кнопку очистки, когда значение непустое. Ступень size и форма radius — из общего словаря визуальных пропсов ДС: шкала поля зеркалит шкалу кнопки, radius="circle" — таблетка. В большинстве форм берут именно Input, а не голый Field.
RadioRadio — радио на Ark RadioGroup.Item. Item рендерит <label>, поэтому children — это контент лейбла: клик по любой его части выбирает значение. В отличие от Checkbox, сам по себе Radio не работает — только внутри RadioGroup: группа держит выбранное value (string | null) и onValueChange, а вложенным Radio достаточно передать свой value и лейбл — состояние ведёт группа.
SearchSearch — поле поиска, собранное на Input: слева уже стоит лупа, кнопка «Очистить» включена (iconLeft и clearable снаружи недоступны). Текст поле хранит само, а onValueChange вызывает через 300 мс после последнего ввода (debounce) — колбэк можно вешать сразу на запрос к API или фильтр списка, не дребезжа на каждый символ. value задаёт только начальное значение. Остальные пропсы — от Input и Field: placeholder, type, size, radius, label, error, iconRight.
SelectSelect — выпадающий список с одиночным выбором, собранный на Ark Select и Field. Триггер показывает label выбранного пункта (или placeholder), список открывается порталом и рисует пункты ячейками Cell. Пункт (SelectItem) — это value и label, опционально description и avatar; выбранный отмечается галочкой. Значение — контролируемая модель: пара value / onValueChange (string | null, null — ничего не выбрано). По умолчанию mode=field — самостоятельное поле с рамкой как у Input, с пропсами Field (label, error); mode=inline — голый триггер для встраивания в составной контрол.
TextareaTextarea — многострочное текстовое поле. Готовый контрол, собранный на Field: сам одевает нативный textarea в оболочку с лейблом, текстом ошибки и кнопкой «Очистить» и пробрасывает пропсы Field (label, error, iconLeft, iconRight). Значение — контролируемая модель: пара value / onValueChange (onValueChange отдаёт уже строку, не событие). rows задаёт стартовую высоту в строках текста, дальше поле тянется пользователем за уголок (resize по вертикали). clearable добавляет кнопку очистки, когда значение непустое. Для однострочного ввода используется Input.
Оверлеи
ContextMenuContextMenu — меню по правому клику на области. Область — это children: компонент оборачивает их в div и вешает обработчик на него, так что внутри могут быть ссылки и кнопки. Пункты описываются массивом items, где элемент — либо { separator: true } (разделитель групп), либо пункт: обязательные value и label плюс icon, variant (danger для разрушающих действий), disabled и вложенный items (подменю). Состояния открытия у компонента нет — потребитель получает только onSelect(value) и ветвится по значению выбранного пункта; в IDE пункты обычно зовут команды реестра. Меню порталится в body и монтируется лениво при открытии. Родня: DropdownMenu — то же меню, но открывается кликом по своему триггеру; MenuItemButton — одиночный пункт того же вида вне меню (например, в popover крошек или в сайдбаре).
DialogDialog — окно подтверждения действия: заголовок, текст и две кнопки. Содержимое задаётся пропсами (title, description, cancelText, confirmText) — своего children-контента у окна нет, для произвольного наполнения берётся Panel в модалке хоста. Обе кнопки закрывают окно сами: onCancel срабатывает по отмене, onConfirm — по подтверждению. Кнопка подтверждения по умолчанию danger (типичный сценарий — удаление), для неразрушающих действий вариант меняется confirmVariant. Открывается двумя способами: неконтролируемо — DialogTrigger внутри children (окно живёт рядом со своей кнопкой), либо контролируемо — парой open / onOpenChange, когда открытием распоряжается хост. Окно порталится в body, монтируется лениво при открытии и размонтируется при закрытии, роль — alertdialog (Esc и клик по фону закрывают, фокус заперт внутри). В IDE поверх него построена модалка подтверждения confirmModal — прямо Dialog там не используют.
DropdownMenuDropdownMenu — меню, открывающееся кликом по своему триггеру. Триггер задаёт потребитель через trigger — это любой элемент (кнопка, иконка), на который Ark через asChild вешает onClick и aria-атрибуты. Пункты описываются массивом items — тем же, что у ContextMenu: элемент либо { separator: true } (разделитель групп), либо пункт с обязательными value и label плюс icon, variant (danger для разрушающих действий), disabled и вложенный items (подменю). Состояния открытия у компонента нет — потребитель получает только onSelect(value) и ветвится по значению выбранного пункта; в IDE пункты обычно зовут команды реестра. Меню порталится в body и монтируется лениво при открытии. Родня: ContextMenu — то же меню, но открывается правым кликом по области; MenuItemButton — одиночный пункт того же вида вне меню (например, в popover крошек или в сайдбаре).
DropdownPanelDropdownPanel — выпадающая панель, открывающаяся кликом по своему триггеру. В отличие от DropdownMenu не ограничена списком пунктов: внутрь через children кладётся произвольная вёрстка — карточка профиля, список сущностей, смешанный контент. Пункты-действия внутри собираются из MenuItemButton (@tulls/ui) — тот же вид пункта, что и в меню, но без машины Ark Menu. Триггер задаёт потребитель через trigger — это любой элемент (кнопка, иконка, аватар), на который Ark через asChild вешает onClick и aria-атрибуты. Открытость по умолчанию панель держит сама; когда открытием распоряжается хост — контролируемо парой open / onOpenChange. Положение относительно триггера настраивает positioning (по умолчанию bottom-start). Панель порталится в body, монтируется лениво при открытии и размонтируется при закрытии. Родня: DropdownMenu — та же механика открытия, но контент строго список пунктов; MenuItemButton — одиночный пункт того же вида, из которого набирается меню внутри панели.
FloatingPanelFloatingPanel — плавающее окно: шапка с заголовком и кнопками, под ней область произвольного контента (children). Окно двигается за шапку (draggable), меняет размер за края и углы (resizable), сворачивается до шапки (minimizable), разворачивается на весь экран (maximizable) и закрывается крестиком (closable) — каждую способность можно отключить по отдельности. Открытость всегда контролируемая — пара open / onOpenChange: onOpenChange(false) приходит при закрытии крестиком, Esc или (при overlayClose) кликом мимо окна, а open меняет хост. Режим modal кладёт под окно невидимый слой-блокиратор, отсекающий страницу. Стартовые размер и позиция — defaultSize / defaultPosition, границы ресайза — minSize / maxSize. Несколько окон наводятся друг на друга через stackIndex (z-index) и onBringToFront (сигнал поднять окно при фокусе). Окно порталится в body, монтируется лениво при открытии и размонтируется при закрытии; первое поле контентной области получает автофокус. Собрано на Ark UI FloatingPanel в каркасе Panel из ДС. В IDE поверх него построена оболочка модалок ModalWindow (связывает окно со стором модалок, центрирует и каскадирует) — прямо FloatingPanel там обычно не используют.
MenuMenu — общая основа пунктов меню, а не самостоятельный оверлей. Отсюда два вида артефактов. Словарь пунктов — тип MenuItem (MenuItemOption | MenuSeparator): им заполняется проп items у ContextMenu и DropdownMenu. Пункт (MenuItemOption) — обязательные value и label плюс icon, variant (danger для разрушающих действий), disabled и вложенный items (подменю); { separator: true } — разделитель смысловых групп. Рендерит этот массив внутренний MenuItems — общий список, который оба меню кладут в свой Content (потому вид пунктов у них совпадает). MenuItemButton — одиночный пункт того же вида, но вне машины Ark Menu: обычная <button> (или, через asChild, ссылка), из которой набирают меню там, где готового оверлея нет — контент DropdownPanel, popover крошек, сайдбар. Вид пункта у обоих один — общий menu.module.css (внешность ghost-кнопки, слот иконки для выравнивания, подсветка по ховеру и клавиатуре). Родня: ContextMenu — меню по правому клику, DropdownMenu — меню по клику на триггер (оба потребляют словарь items); DropdownPanel — панель произвольного контента, куда MenuItemButton обычно и кладут.
QuickSearchQuickSearch — модалка быстрого поиска, аналог Search Everywhere в IDE. В шапке — ряд табов типов документов и строка поиска, ниже — область результатов. Компонент презентационный: он рисует каркас, но сам ничего не ищет — и список результатов (children), и набор табов (tabs) приходят снаружи. Внутрь children кладётся любая вёрстка: секции по типам, строки-Cell, заглушка «ничего не найдено», индикатор загрузки. Табы фильтруют выдачу по типу — активный задаёт tab, переключение ловит onTabChange; в tabs приходят готовые Tabs.Item, а Tabs.Root вокруг них рисует сам QuickSearch. Строка поиска отдаёт ввод с задержкой (дебаунс) через onSearchChange; search — лишь начальное значение, дальше поле хранит текст само, searchPlaceholder задаёт подсказку. Открытость всегда контролируемая — пара open / onOpenChange: onOpenChange(false) приходит при закрытии крестиком, по Esc или клику по затемнению вокруг окна, а open меняет хост. Окно прижато к верху экрана, порталится в body, монтируется лениво при открытии и размонтируется при закрытии; при открытии автофокус уходит в строку поиска, список результатов скроллится внутри. Собрано на Ark UI Dialog в каркасе Panel из ДС (Tabs и Search — оттуда же). В IDE смонтирован один раз в корневом layout и открывается двойным нажатием Shift.
ToastToast — всплывающее уведомление в правом нижнем углу экрана. Состоит из двух частей. Регион Toaster монтируется один раз в корне приложения (в IDE — рядом с modal-root) и рисует стопку уведомлений. Сами уведомления показывают вызовом функции toast(options) — она обращается к синглтону, созданному вне React, поэтому звать её можно откуда угодно: из обработчика, из колбэка мутации, из утилиты без доступа к хукам. Своих пропсов у Toaster нет — всё содержимое конкретного тоста задаётся полями options. Поля ToastOptions: title — заголовок (одна строка, обрезается многоточием); description? — вторичный текст под ним; variant? — смысловой вариант из словаря ДС (primary по умолчанию; успех операции — success, ошибка — danger), задаёт цвет полоски-индикатора слева; duration? — время до автозакрытия в мс (по умолчанию 5000, наведение мыши ставит таймер на паузу); content? — произвольный узел под описанием (картинка, кнопки действий и т.п.). Уведомления складываются в стопку, автоматически исчезают по таймеру или по крестику, порталятся в body и позиционируются фиксированно у нижнего правого края. Собрано на Ark UI Toast.
TooltipTooltip — всплывающая подсказка, появляющаяся при наведении на триггер и поясняющая его назначение. Оборачивает любой элемент через children — Ark по asChild вешает на него события и aria-атрибуты, поэтому триггер обязан пробрасывать пропсы и ref (кнопка, иконка, ссылка ДС это умеют). Текст или произвольная вёрстка подсказки задаётся пропсом content, сторона появления — placement (по умолчанию top). Подсказка открывается с задержкой (400 мс на наведение, 100 мс на увод), порталится в body, монтируется лениво при первом показе и размонтируется после закрытия. Главный потребитель — ButtonIcon: иконочная кнопка без подписи оборачивает себя в Tooltip и показывает свой label как подсказку, так что для неё отдельный Tooltip не нужен. Собрано на Ark UI Tooltip.