npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smounters/ui

v2.22.1

Published

HeroUI-based design layer: app shell with a resizable sidebar, a data table with per-user column settings, form drawer

Readme

@smounters/ui

Дизайн-слой на HeroUI v3 + Tailwind v4: каркас приложения с ресайзящимся сайдбаром, таблица с пользовательскими настройками колонок, выезжающая форма.

npm i @smounters/ui @heroui/react @heroui/styles react-router-dom

Что внутри

| | | |---|---| | AppLayout / Sidebar / Navbar | каркас: сайдбар (ширина тянется мышью, сворачивается), топбар с заголовком страницы, контент через <Outlet/> | | DataTable | TanStack Table + перетаскивание колонок (dnd-kit) + меню видимости + ресайз; порядок, ширины, видимость и сортировка сохраняются per-user; по желанию — выделение строк под массовые действия | | FormDrawer / Field | правая панель под создание/редактирование, ширина тянется за левый край | | Paginator, StatusPill, StatCard, ThemeSwitcher, LanguageSwitcher | мелкие общие компоненты | | useResizable, useServerTable, useDebounce | хуки |

Подключение

import "@smounters/ui/styles.css";
import { UiProvider, AppLayout } from "@smounters/ui";

<UiProvider labels={labels} useSetting={useSetting}>
  <AppLayout sidebarItems={items} appName="Console" />
</UiProvider>;

Пакет не знает ни про ваш RPC, ни про ваши переводы — обе связки приходят через UiProvider:

  • useSetting — хук (section, key, fallback) => { value, isLoaded, setValue }. Через него таблица и сайдбар хранят состояние. В наших продуктах он ходит в UserSettingsService по ConnectRPC, но подойдёт что угодно, включая localStorage. Не передан — состояние живёт только в памяти вкладки. Это хук: реализация должна быть стабильной по идентичности и не зваться условно.
  • labels — подписи кнопок и таблицы. По умолчанию английские (DEFAULT_LABELS); переопределяются частично, значения с подстановкой — функциями:
const labels = {
  actions: { save: t("actions.save") },
  table: { range: ({ from, to, total }) => t("table.range", { from, to, total }) },
};

Токены и переопределение

@smounters/ui/styles.css подключает Tailwind, стили HeroUI, палитру продуктов (theme/palette.css) и структурные правки (theme/base.css: рамки полей, единый масштаб радиусов, focus-ring, стрелка нативного <select>). Светлая и тёмная темы — из коробки, переключатель <ThemeSwitcher/> (он же ставит data-theme на <html>).

Базовые токены (задаются на :root, [data-theme="light"] и отдельно на [data-theme="dark"]):

| токен | что красит | |---|---| | --accent / --accent-foreground | акцент: активный пункт меню, primary-кнопки, ссылки | | --background / --foreground | фон страницы и основной текст | | --success / --warning / --danger | статусы (StatusPill, кнопки удаления, ошибки) | | --radius-small / --radius-medium / --radius-large | контролы и поверхности | | --field-border / --field-border-width | рамка инпутов |

Остальное (--surface, --surface-secondary, --border, --muted, --focus, hover- и soft-варианты) HeroUI выводит из базовых через color-mix() — их трогать обычно не нужно.

Переопределить существующий

Приложение импортирует пакет, а СВОИ токены объявляет после — по каскаду выигрывают они. Обе темы задаются отдельно: одного :root мало, иначе тёмная останется на палитре пакета.

@import "@smounters/ui/styles.css";

:root,
[data-theme="light"] {
  --accent: oklch(0.55 0.13 195);
}
[data-theme="dark"] {
  --accent: oklch(0.72 0.13 195);
}

Добавить новый

Своё понятие (у торгового терминала это, например, «лонг» и «шорт») объявляется в два слоя: переменная на тему — чтобы значение зависело от светлой/тёмной, и @theme — чтобы Tailwind СГЕНЕРИРОВАЛ утилиты (text-long, bg-long/10, border-long). Без второго слоя переменная есть, а классов нет.

:root, [data-theme="light"] { --long: oklch(0.52 0.15 150); }
[data-theme="dark"]         { --long: oklch(0.78 0.15 150); }

/* мягкие варианты — СВОИМИ токенами, а не через `/10` (почему — ниже) */
:root, [data-theme="light"], [data-theme="dark"] {
  --long-soft: color-mix(in oklch, var(--long) 12%, transparent);
  --long-border: color-mix(in oklch, var(--long) 40%, transparent);
}

@theme inline {
  --color-long: var(--long);
  --color-long-soft: var(--long-soft);
  --color-long-border: var(--long-border);
}

inline обязателен: без него Tailwind подставит значение на момент сборки, и утилита перестанет реагировать на смену темы.

Грабли, о которых стоит знать заранее: у токена-ссылки (var(...)) не работает модификатор прозрачности. Tailwind не знает цвет на сборке и молча выбрасывает модификатор — bg-long/10 собирается в background-color: var(--long), то есть в сплошную заливку вместо подложки. Ошибки при сборке не будет, увидите только глазами. Поэтому мягкий фон и рамку задавайте отдельными токенами через color-mix(), как выше.

Своя палитра целиком

Продукту под чужим брендом импортировать нашу палитру незачем — есть вход без неё:

@import "@smounters/ui/styles-unthemed.css";
/* дальше свои --accent/--background/--foreground/--success/--warning/--danger на обе темы */

Отдельно доступны и части: @smounters/ui/theme/palette.css, @smounters/ui/theme/base.css.

Настройки таблицы

DataTable требует code — стабильный идентификатор таблицы ("trades.list"). Под ним в секции table-columns лежит { order, hidden, sorting, sizes }. Код таблицы менять нельзя: он и есть ключ, по которому у пользователя найдутся его колонки.

Выделение строк

Для массовых действий (снять с публикации, назначить, удалить пачкой) включается отдельно:

<DataTable
  code="orders.list"
  getRowId={(o) => o.id}   // обязателен: id выбранных строк берутся отсюда
  enableSelection
  onSelectionChange={setSelectedIds}
  selectionResetSignal={resetTick}  // необязательно: увеличить, чтобы снять выделение снаружи
  …
/>

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

Подписи галочек — labels.table.selectAll и labels.table.selectRow.