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

@tinrun/ui

v1.0.0

Published

Shared interface of tin apps: theme tokens, base styles and Vue 3 components — one look for the platform and its apps.

Readme

@tinrun/ui

Общий интерфейс приложений tin: цвета, шрифты, отступы, кнопки, поля, окна, раскладка на телефоне — один вид и одно поведение у всех сервисов платформы и у самого tin. Пользовательские приложения могут взять его по желанию — и выглядеть «как tin» без усилий.

Витрина со всеми компонентами во всех состояниях, в обеих темах и на телефоне — tin.bar/ui (открывается без входа) или npm run dev в этом репозитории.

Два слоя:

  1. Токены и базовые стили — CSS без фреймворка, для любого стека (React, Svelte, чистый HTML).
  2. Компоненты Vue 3 — @tinrun/ui/vue.

Подключение

npm i @tinrun/ui

Vue 3 — одна строка

import { TinButton, TinAppLayout, applyTinTheme } from '@tinrun/ui/vue'

Импорт @tinrun/ui/vue сам подключает стили: токены, базовые стили и стили компонентов (сборщик приложения — Vite и т.п. — обрабатывает CSS из пакета). Больше ничего не нужно.

Любой стек — только CSS

import '@tinrun/ui/tokens.css'   // переменные --tin-* обеих тем
import '@tinrun/ui/base.css'     // шрифт, фон, ссылки, фокус, выделение (необязательно)
<!-- без сборщика -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tinrun/ui@1/dist/tokens.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tinrun/ui@1/dist/base.css" />
<button style="background: var(--tin-color-accent); color: var(--tin-color-on-accent)">Готово</button>

Темы

Тема выбирается атрибутом data-theme="light|dark" на <html>; без него — как в системе. Внутри tin тема приходит от tin — свяжите их одной строкой:

import { connect } from '@tinrun/client'
import { applyTinTheme } from '@tinrun/ui'      // или из '@tinrun/ui/vue'

const tin = await connect({ spa: true })
applyTinTheme(tin)   // data-theme = tin.theme, и дальше — вслед за themeChanged. Возвращает «перестать следить»
tin.ready()

applyTinTheme принимает любой объект с theme и on('themeChanged', …) — зависимости от @tinrun/client нет. setTinTheme('dark' | 'light' | null, root?) — поставить тему руками (null — системная). data-theme можно поставить и на любой элемент — эта часть страницы будет в другой теме (так витрина показывает обе темы рядом).

Токены

Все значения — CSS-переменные --tin-* (из tokens.css) и те же значения в JS (import { tokens } from '@tinrun/ui').

| Группа | Переменные | |---|---| | Цвета (свои в каждой теме) | --tin-color-bg, surface (тихие области: код, боковое меню), surface-raised (меню, окна, уведомления), hover, text, text-muted, border (разделители), border-strong (контуры полей, 3:1), accent, on-accent, focus, danger, on-danger, danger-soft, success, success-soft, warning, warning-soft, info, info-soft, overlay, selection, skeleton, chart | | Тени (свои в каждой теме) | --tin-shadow-sm, --tin-shadow-md | | Отступы | --tin-space-1 … --tin-space-8: 4, 8, 12, 16, 24, 32, 48, 64 px | | Радиусы | --tin-radius-sm 4px, md 6px (кнопки, поля), lg 10px (карточки, окна), full | | Шрифт | --tin-font-sans, --tin-font-mono, --tin-font-size-xs|sm|md|lg|xl, --tin-font-weight-regular|medium|bold, --tin-font-line-height, --tin-font-line-height-tight | | Размеры | --tin-size-control — высота кнопок и полей: 36px, 44px на телефоне и сенсорных экранах; --tin-size-touch 44px; --tin-size-sidebar 240px; --tin-size-content 960px; --tin-size-icon 20px | | Слои | --tin-z-dropdown, --tin-z-sticky, --tin-z-dialog, --tin-z-toast | | Движение | --tin-motion-fast 120ms, --tin-motion-normal 200ms (0ms при prefers-reduced-motion), --tin-motion-easing |

Широкий экран — от 768px (@media (min-width: 768px); в медиазапросах переменные не работают). Контраст всех пар текст/фон — WCAG AA в обеих темах, контуры и фокус — 3:1 (проверяется тестами).

Компоненты (@tinrun/ui/vue)

Общее для всех: только токены (обе темы), от 360 px ширины, касание ≥ 44 px на телефоне, управление с клавиатуры, видимый фокус, aria. Тексты по умолчанию — русские, все заменяются свойствами (closeText, copyText, …). Компоненты не ходят в сеть. Атрибуты и слушатели, не описанные в свойствах, уходят на основной элемент (<button>, <input>, …).

Действия

TinButton — кнопка вместо голого <button>. variant: primary (главное действие экрана) | secondary (по умолчанию) | danger | ghost; loading; disabled; type (button по умолчанию); block (на всю ширину); icon (имя TinIcon); href — ссылка в виде кнопки.

<TinButton variant="primary" :loading="saving" @click="save">Сохранить</TinButton>

TinIconButton — квадратная кнопка с иконкой. icon, label (обязателен: aria-label и подсказка), variant (ghost по умолчанию), loading, disabled.

<TinIconButton icon="copy" label="Скопировать" @click="copy" />

TinMenu — выпадающее меню. items: { id, label, icon?, danger?, disabled? }[], label, align (start|end), icon кнопки; событие select(id). Своя кнопка — слот trigger (v-bind="props"). Клавиатура: Enter/Space/↓ открывает на первом пункте, ↑ — на последнем, ↑ ↓ Home End, буква — к пункту, Esc закрывает и возвращает фокус, Tab закрывает.

<TinMenu :items="[{ id: 'delete', label: 'Удалить', icon: 'trash', danger: true }]" label="Действия" @select="onAction" />

TinIcon — иконка открытого набора Lucide по имени. name, size (CSS-длина), label (если иконка значит что-то сама по себе). Встроенные имена: activity, arrow-down|left|right|up, bell, bot, box, calendar, chart, check, chevron-down|left|right|up, circle-alert, circle-check, circle-help, circle-x, clock, code, copy, cpu, credit-card, database, download, external-link, eye, eye-off, file, file-text, filter, folder, globe, grid, history, home, image, inbox, info, key, layers, link, list, lock, log-in, log-out, menu, message, minus, moon, more-horizontal, more-vertical, palette, paperclip, pause, pencil, play, plug, plus, refresh, rocket, rotate-ccw, search, send, server, settings, shield, sparkles, square, star, sun, terminal, trash, triangle-alert, upload, user, wallet, wrench, x. Другие иконки Lucide:

import { Bike } from 'lucide'
registerTinIcons({ bike: Bike })   // <TinIcon name="bike" />

Ввод

TinField — подпись, подсказка и ошибка поля, связанные для экранных дикторов. label, hint, error (показывается вместо подсказки и делает поле aria-invalid), required. Для своего поля — слот отдаёт id и describedBy.

<TinField label="Название" hint="Видно только вам" :error="nameError" required>
  <TinInput v-model="name" />
</TinField>

TinInput — поле в одну строку. v-model, type (text, search, email, url, tel, password, number), invalid, icon (слева).

TinTextarea — многострочное поле, растёт по высоте. v-model, rows (3), maxRows (12), invalid; метод focus().

TinSelect — выбор из списка (нативный <select>: системный выбор на телефоне). v-model, options: { value, label, disabled? }[], placeholder, invalid.

TinCheckbox — флажок с подписью (слот). v-model, disabled.

TinSwitch — переключатель для настройки, которая действует сразу (role="switch"). v-model, disabled, подпись — слот.

Окна

TinDialog — модальное окно. v-model:open, title, description, size (sm|md|lg), dismissible (Esc, фон и ✕ закрывают; false — только кнопки окна), closeText; слоты: содержимое и footer; событие close. Фокус — на элемент с autofocus, иначе на первое поле; Tab ходит по кругу внутри; страница под окном не прокручивается; после закрытия фокус возвращается.

TinConfirm / confirm() — подтверждение одной строкой кода:

if (await confirm({ title: 'Удалить проект?', text: 'Его нельзя будет вернуть.', confirmText: 'Удалить', danger: true })) {
  await remove()
}
if (await confirm('Выйти без сохранения?')) close()

confirm() сама показывает окно и убирает его. В шаблоне: <TinConfirm v-model:open="asking" title="…" danger @confirm="…" @cancel="…" />. Свойства: title, text, confirmText, cancelText, danger (красная кнопка, фокус на «Отмена»).

TinToast / toast() — короткие уведомления внизу экрана.

toast('Скопировано')
toast({ message: 'Не удалось сохранить', tone: 'danger', action: { label: 'Повторить', onClick: save } })

tone: info | success | warning | danger; timeout (мс, 5000; danger — 8000; 0 — пока не закроют); возвращает id для dismissToast(id). Место для уведомлений <TinToast /> необязательно — toast() создаст его сам. Уведомления читаются экранными дикторами (ошибки — сразу), таймер стоит, пока на уведомлении указатель или фокус. Пока открыт модальный TinDialog, уведомления видны под его фоном.

Раскладка

TinAppLayout — рамка экрана приложения: верхняя строка (название и действия), меню разделов и содержимое. Широкий экран (≥ 768 px) — меню слева; телефон — вкладки снизу (mobile-nav="bottom", по умолчанию) или сверху ("top"). items: { id, label, icon?, href?, badge? }[], active, title, navLabel; событие navigate(id, event) (для пунктов с href — event.preventDefault(), чтобы перейти роутером приложения). Слоты: содержимое, brand (вместо названия), actions (справа в строке), nav-footer (под меню, только на широком экране).

<TinAppLayout title="Студия" :items="sections" :active="route.name" @navigate="(id) => router.push({ name: id })">
  <template #actions><TinIconButton icon="settings" label="Настройки" /></template>
  <RouterView />
</TinAppLayout>

TinTabs — вкладки. v-model (id вкладки), tabs: { id, label, icon?, badge?, disabled? }[], label; содержимое выбранной — в слоте ({ active }). Клавиатура: ← → Home End (выбирают сразу), Tab — в панель.

TinCard — блок с рамкой. title, as (section|article|div|li), padding (none|sm|md); слоты: содержимое, actions, footer.

TinList — список строк с разделителями. items, itemKey (поле или функция), selectable (строки — кнопки, событие select(item)), label; слоты item ({ item, index }) и empty.

Состояния

TinSpinner — загрузка. size, label (тогда это role="status" для дикторов).

TinSkeleton — заготовка загружающегося содержимого. width, height, lines, circle.

TinEmpty — «пусто». icon, title, text; кнопки — в слоте.

TinError — ошибка с «Повторить». title («Что-то пошло не так»), text, retryText, retrying; событие retry (без слушателя кнопки нет); role="alert".

TinBadge — метка. tone: neutral | accent | success | warning | danger | info.

TinStatus — состояние точкой и словом. status: running (Работает), starting, waking, stopping, stopped, sleeping, crashed (Упал), error, failed, building, deploying, live, pending, paid, expired, cancelled, blocked; незнакомый показывается как есть. label, tone — заменить слово и цвет.

Данные

TinCopyField — значение с «Скопировать». value, label, secret (скрыто до «Показать»; копируется настоящее значение), тексты copyText, copiedText, failedText, showText, hideText; событие copy.

TinCode — моноширинный блок с копированием. code, language (подпись), copyable (true), wrap. Код показывается только как текст.

TinMeter — «использовано / лимит». label, value, max, format, warnAt (0.8 — с этой доли жёлтый; на лимите — красный), ofText. role="meter".

TinBarChart — столбцы на SVG без библиотек. data: { label, value }[], label, height (160), format, hint, emptyText. Подсказка со значением — при наведении и с клавиатуры: Tab на график, ← → Home End, Esc; значение объявляется дикторам, данные для них — ещё и таблицей.

TinTime — «5 мин. назад» (обновляется сама) или дата, в поясе пользователя; полная дата — в подсказке. value (Date, ISO-строка, мс), mode (relative|absolute), timeZone, locale.

TinNumber — «1 234 567», compact — «1,2 млн» (точное — в подсказке). maximumFractionDigits, locale. Без Vue — formatNumber, formatRelativeTime, formatDateTime из @tinrun/ui.

Недоверенный текст

TinMarkdown — markdown ответов ИИ и любых данных пользователей. source, openLink (в приложении tin — (url) => tin.openLink(url); без него ссылка откроется в новой вкладке).

<TinMarkdown :source="answer" :open-link="(url) => tin.openLink(url)" />

Как защищено (renderMarkdown из @tinrun/ui — то же без Vue):

  • HTML внутри markdown показывается текстом, а итог проходит через DOMPurify с коротким списком тегов: нет <script>, обработчиков (onerror, …), style, class, iframe, форм, SVG;
  • ссылки — только абсолютные http(s) (остальные теряют ссылку, текст остаётся), rel="noopener noreferrer"; клик — через openLink;
  • картинки с внешних адресов не загружаются (вместо них — подпись): внешняя картинка сообщила бы постороннему серверу, кто читает текст. Остаются только встроенные data:image/png|jpeg|gif|webp.

Правила для своих экранов

  • Цвета и размеры — только var(--tin-…); обе темы; вёрстка от 360 px.
  • Не делайте свой вход и не показывайте ключи целиком: TinCopyField secret.
  • Данные пользователей и ответы ИИ — только текстом или через TinMarkdown, никогда v-html с сырым текстом.
  • Внешние ссылки — через tin.openLink.

Витрина — приложение tin.bar/ui

showcase/ — сайт на Vite: все компоненты во всех состояниях, переключатель темы, «обе темы рядом», «ширина телефона 390 px». Внутри tin она подключает @tinrun/client с адреса tin (VITE_TIN_URL, по умолчанию https://tin.bar), вызывает tin.ready() и следует теме tin (applyTinTheme); вне tin работает сама.

npm run dev                                 # http://localhost:5013 (PORT=… — другой порт)
VITE_TIN_URL=https://tin.bar npm run build:showcase   # → showcase/dist/, статический сайт

Регистрация как нашего приложения с гостевым доступом (открывается по tin.bar/ui без входа) — в tinAPI:

npm run apps:register -- --id ui --title "tin UI" --url https://ui.tin.bar/ --guest-access

Хостинг — любой статический (showcase/dist/), с заголовком Content-Security-Policy: frame-ancestors https://tin.bar (стандарт приложения, п. 7). Локально: tinAPI с APPS_ALLOW_LOCALHOST=true, --url http://localhost:5013/, tinWeb с VITE_TIN_URL витрины на адрес этого tinWeb.

Разработка

npm ci
npm run dev          # витрина
npm run lint && npm run type-check && npm test && npm run build

Правила изменений — CLAUDE.md; история — CHANGELOG.md.

Версии и публикация

Как у @tinrun/client / @tinrun/server: 1.x — только совместимо (новые компоненты, свойства, токены); переименование и удаление — 2.0, 1.x поддерживается 12 месяцев. Публикация — npm publish --provenance из CI по тегу v<версия> (.github/workflows/publish.yml, секрет NPM_TOKEN).

Лицензия

MIT. Иконки — Lucide (ISC).