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

@inova-lab-ws/ui

v0.14.0

Published

INOVA Lab UI: os componentes, tokens e logos da INOVA Lab Library em React. Fonte única para AmbientAI, Gate e todo produto novo do INOVA Lab.

Readme

INOVA UI

Componentes, tokens e logos do INOVA Lab · Leroy Merlin Brasil

Um desenho no Figma, um pacote em código, a mesma interface em todo produto.

inova-ui é o repositório. @inova-lab-ws/ui é o pacote que os apps instalam.

figma repo package

[!IMPORTANT] 🔒 Fonte única de interface do INOVA Lab. AmbientAI, Gate e todo produto novo instalam este pacote. Nenhum app cria componente de interface próprio nem copia o código daqui. O que faltar é pedido por issue. Dono e único aprovador: @gabriel-moma-lmbr.

Como funciona · Instalar · Componentes · Tokens · Pedir algo novo · Desenvolver

🔆 O caminho de uma mudança

1. Desenhar     → o componente ou token nasce na INOVA Lab Library (Figma) e é publicado
2. Exportar     → as variáveis viram tokens/tokens.json; o tema é gerado (npm run tokens)
3. Implementar  → o componente entra em src/components, com Code Connect para o Figma
4. Lançar       → uma release no GitHub publica a nova versão do pacote
5. Atualizar    → cada app sobe a versão da dependência; nada é copiado

O Figma desenha, o pacote implementa, a INOVA UI Skill orienta o uso. Valor que não passou pela biblioteca não entra aqui; componente que não está aqui não existe no app.

🎯 Como funciona

flowchart LR
    Figma[INOVA Lab Library<br/>Figma] -->|variáveis| Tokens[tokens.json]
    Tokens -->|build-tokens| Tema[theme.css]
    Figma -->|Code Connect| Comp[Componentes React]
    Tema --> Pacote["@inova-lab-ws/ui"]
    Comp --> Pacote
    Pacote --> AmbientAI
    Pacote --> Gate
    Pacote --> Novo[Produto novo]
    Skill[INOVA UI Skill] -.orienta.-> AmbientAI
    Skill -.orienta.-> Gate
    Skill -.orienta.-> Novo

📦 Instalar num projeto

1. Dependência. Público no npm, sem token nem .npmrc. O projeto precisa de React 19, Tailwind 4 (obrigatório) e lucide-react 1.x (o pacote usa a cópia do app).

npm install @inova-lab-ws/ui lucide-react

2. Tema. No CSS global, o Tailwind primeiro e o tema depois, e nenhum outro tema. O tema já aponta para os componentes (@source), então o app não precisa acertar o caminho de node_modules:

@import "tailwindcss";
@import "@inova-lab-ws/ui/theme.css";

Sem Tailwind no app, use @import "@inova-lab-ws/ui/theme-standalone.css";, que já traz o Tailwind.

[!WARNING] O tema muda o tamanho do texto do app inteiro (#59). A biblioteca usa a escala 4/8 nos nomes padrão do Tailwind, então, no momento em que o app importa o tema, toda tela que ainda usa estas classes muda de tamanho:

| Classe | Tailwind | Com o tema | | :--- | ---: | ---: | | text-lg | 18px | 20px | | text-xl | 20px | 24px | | text-2xl | 24px | 32px | | text-3xl | 30px | 40px | | text-4xl | 36px | 48px |

text-xs, text-sm e text-base não mudam. Antes de importar, liste os usos no app: grep -rnE "text-(lg|xl|[234]xl)\b" app components src.

2b. Na raiz do app (uma vez): KeyboardInsetProvider, para a folha de baixo ficar acima do teclado no celular, e ToastProvider, para chamar useToast().show({ kind, message }) de qualquer tela.

<KeyboardInsetProvider>
  <ToastProvider>{children}</ToastProvider>
</KeyboardInsetProvider>

3. Fontes. Com next/font, declare as variáveis --font-inter, --font-instrument-serif e --font-roboto-mono; o tema lê essas variáveis antes do nome da família.

4. Usar.

import { Button, Alert, Toast, ToastViewport } from "@inova-lab-ws/ui";

<Button variant="primary">Salvar pedido</Button>
<Alert tone="warning" title="Dados de exemplo">Ainda não há eventos reais neste servidor.</Alert>

// Botão que leva a outra página
<Button asChild><Link href="/novo">Adicionar</Link></Button>

Página montada no servidor: os componentes são código de navegador. Para dar o visual do pacote a um elemento numa página do servidor, use import { buttonVariants, cn } from "@inova-lab-ws/ui/variants";.

Projeto sem Tailwind: instale tailwindcss e @tailwindcss/postcss, crie postcss.config.mjs, importe o tema e isole o CSS antigo numa camada (@import "./legacy.css" layer(components);). O passo a passo está na skill INOVA UI.

🧩 Componentes

Todos os componentes da INOVA Lab Library estão no pacote, cada um com Code Connect em figma/.

Indicadores

| Componente | No Figma | | :--- | :--- | | Badge | badge | | Spinner | spinner | | Tooltip · TooltipProvider | tooltip | | Avatar (pessoa: inicial, ícone ou foto; variant="app": logo de serviço) | avatar | | BotAvatar | bot-avatar | | Divider | divider | | Alert | alert | | Toast · ToastViewport | toast | | Chip | chip | | ConnectorCard | connector-card |

Navegação

| Componente | No Figma | | :--- | :--- | | NavigationTabBar | navigation-tab-bar | | Menu | menu | | PillTab · PillTabs | pill-tab |

Estrutura

| Componente | No Figma | | :--- | :--- | | Header | header | | TopArea | top-area | | Card | card | | PageHeader | page-header | | Accordion · AccordionItem | accordion |

Logos

| Componente | No Figma | | :--- | :--- | | LogoAmbientAI | logo / ambientai | | LogoGate | logo / gate | | LogoFormaLab | logo / forma-lab | | LogoInovaUI | logo / inova-ui | | LogoPlaceholder | logo / placeholder |

Formulário

| Componente | No Figma | | :--- | :--- | | Button (destructive-ghost para remoção secundária; action + size="compact" para a pílula verde do Falar) | button | | Input | input | | Textarea | textarea | | Checkbox | checkbox | | Radio | radio | | RoleRadio | role-radio | | Toggle | toggle | | MultiSelect | multi-select | | Select | input (com chevron) | | DateRangePicker · DateRangeDay | date-range-picker | | LoginForm | login-form | | CopyField | copy-field | | Code · codeClassName | code |

Overlay

| Componente | No Figma | | :--- | :--- | | Overlay (dialog, drawer, bottom sheet) | overlay | | ActionMenu · ActionMenuTrigger · ActionMenuContent · ActionMenuItem · ActionMenuSeparator | action-menu | | ProfileMenu | profile-menu |

Dados e gráficos

| Componente | No Figma | | :--- | :--- | | MetricTile · MetricTileGroup | metric-tile | | TrendBadge | metric-trend | | MetricChartCard | metric-chart-card | | AreaChart | area-chart | | ChartLineKey · ComparisonTooltip · ChartExtremeMarker | primitivas de gráficos | | ProgressReadout | progress-readout | | BarChart | bar-chart | | HorizontalBarChart | horizontal-bar-chart | | StackedBarChart | stacked-bar-chart | | DistributionChart | distribution-chart | | Table · TableRow · TableCell | table | | ActivityLog · ActivityLogRow | activity-log |

KPIs e gráficos: MetricTile context="embedded" compartilha conteúdo e ajuda com o cartão independente; onActivate seleciona sem alterar o evento DOM legado onSelect. trend={{direction: "up", evaluation: "unfavorable", value: "12%"}} representa custo crescente. A avaliação nunca é deduzida da direção. A tendência ocupa o canto inferior direito do cartão.

MetricChartCard recebe uma tupla de 4 ou 6 metrics com id, label e value. Use activeMetric/onActiveMetricChange para controle externo ou defaultActiveMetric para estado interno; selectable: false exibe uma métrica informativa. O filho pode ser uma função (id) => <AreaChart ... /> para trocar a série sem incorporar regras do produto à biblioteca.

AreaChart recebe data={[{label: "16/out", value: 0, previousValue: 10, date: "16/10", previousDate: "09/10"}]} e label acessível. comparison ativa o período anterior; showExtrema mostra máximo/mínimo. null interrompe linha e preenchimento, enquanto zero permanece medido. parts em cada ponto adiciona linhas ao tooltip; formatValue formata valores. Estados: ready, loading, empty, unavailable. A biblioteca não calcula períodos nem indicadores.

MultiSelectOption.icon aceita um ícone ou logo opcional (ReactNode), decorativo, ao lado do rótulo na lista e no gatilho com uma opção escolhida. Sem ícone, não há espaço reservado. O Select continua nativo.

PillTabs usa um trilho segmentado; PillTab selecionada tem superfície branca e sombra. As APIs navigation e fullWidth permanecem.

BarChart e HorizontalBarChart aceitam selectedLabel e onSelectionChange: clique, Enter ou Espaço selecionam; selecionar novamente limpa (null). Setas, Home e End percorrem as barras.

Previsão e mídia (0.14.0). AreaChart aceita pontos {label, value, projected, low, high}. projectedLabel e rangeLabel personalizam a legenda e a dica; previsão neutra tracejada e faixa provável são independentes da comparação com o período anterior. Limites ausentes ou inválidos deixam lacunas, sem virar zero. ForecastCard recebe period, value, range, target, targetStatus e 2 grupos opcionais em breakdown; valores, avaliação da meta e formatValue pertencem ao consumidor. ForecastCardGroup organiza os cartões em colunas no desktop e rolagem lateral no celular. Badge color="category1" até category5 usa os tons categóricos dos gráficos, claros por padrão; cores antigas e disabled continuam com o mesmo significado.

Thumbnail appearance="frameless" size="compact" aspectRatio="4:3" usa o mesmo padding global de 4px do cartão padrão do chat, mantendo o conteúdo com 8px de respiro. A aparência frameless preserva o fundo transparente sobre a página bege, o hover neutro e o foco acessível, sem borda ou hover verde. Compact ocupa 42vw, até 180px; ThumbnailRail fornece rolagem com snap e gap de 12px. fallbackSrc é tentado uma vez se a imagem principal falhar; trocar src reinicia a tentativa. ThumbnailSkeleton compartilha aparência, tamanho e proporção com o cartão. AuthorLine name="Ana" store="Loja" fornece avatar e texto truncado; SeeAll fornece o link da ação de seção.

<ThumbnailRail>
  <Thumbnail asChild appearance="frameless" size="compact" aspectRatio="4:3"
    src={photo} fallbackSrc={backupPhoto} caption="Ambiente" overlayAction={<Button onClick={save}>Salvar</Button>}>
    <Link href={detailUrl}>Abrir ambiente</Link>
  </Thumbnail>
</ThumbnailRail>
<PageHeader size="section" title="Ambientes" action={<SeeAll asChild><Link href="/all">Ver todos</Link></SeeAll>} />

O link principal do Thumbnail e as ações são irmãos; não coloque um link envolvendo o cartão inteiro. FilterBar onClearAll={clearFilters} acrescenta “Limpar tudo” ao rodapé móvel sem fechar o painel; showClearAll={false} esconde a ação e clearAllLabel altera seu texto.

DetailLayer é a composição modal de imagem e painel: open, onOpenChange, title, image={{src,alt}} e os filhos do painel. No desktop, a imagem fica contida à esquerda e o painel de 400px rola por dentro; no celular, a camada inteira rola. panelWidth altera a largura desktop. onImageClick oferece a ação de zoom; o consumidor pode abrir ImageViewer, que devolve o foco ao controle que o abriu quando o zoom fecha. initialFocusRef e returnFocusRef permitem escolher o foco inicial e de retorno. Escape, clique no fundo e fechar são gerenciados pela camada. DetailLayout continua sendo a composição lateral no fluxo, sem modal.

Composições e filtros (0.13.0). MetricTile icon={<Icon />} substitui o marcador em layout="variant"; MetricChartCard aceita 2, 4 ou 6 KPIs e bodyPadding="none". O fundo do KPI selecionado acompanha os cantos externos do cartão; foco e ajuda continuam visíveis. Table frameless rowInset permite integrar a tabela sem uma segunda moldura. TableRow interactive onClick={...} também funciona com Enter/Espaço; ações e links dentro da linha continuam independentes.

PageHeader size="section" usa h2 com o mesmo slot action. Thumbnail oferece badge, overlayAction, author e footer; o controle principal e a ação ficam separados. DetailLayout sidebar={...} mantém o detalhe no fluxo, sticky no desktop e abaixo no celular. Breadcrumb recebe items={[{label,href}]}; BreadcrumbBack oferece o retorno dentro de um painel, com no máximo 8px até seus filhos.

PageGrid loadState="idle" onLoadMore={loadNextPage} sinaliza o fim visível sem carregar dados por conta própria. Informe loadKey a cada página consumida, inclusive quando ela retornar zero itens, para rearmar o observador. loadState="loading"|"error"|"end" apresenta progresso, nova tentativa e fim. O consumidor controla a lista, o cursor e os erros.

toast.show({message: "Publicado", action: {label: "Ver", onClick: openDetail}}) adiciona uma ação independente. ProductListRow copySku="123456" oferece cópia compacta e anúncio de sucesso/erro; callbacks onCopySuccess/onCopyError ficam disponíveis.

MultiSelect e DateRangePicker aceitam presentation="popover"|"sheet"|"responsive"; sem a prop, mantêm popover. Responsive usa painel abaixo de 768px. MultiSelectPanel e DateRangePanel são conteúdo controlado sem gatilho/portal, para compor dentro de outro painel. FilterBar recebe primary e filters com id, label, summary, active, field (desktop) e panel (conteúdo do painel móvel); cada painel pode usar esses seletores sem abrir outra modal.

Combobox recebe options em ordem definida pelo consumidor, value/onValueChange, ícone/avatar opcionais e allowCreate/onCreate para criação. Retorne de onCreate um valor ou uma opção, inclusive por Promise. A biblioteca descarta resultados de consultas canceladas; a persistência e a ordenação por uso são do produto. originalValue permite voltar à escolha original; onCancel trata cancelamento. A lista nova usa apresentação responsiva por padrão.

<MenuProvider>
  <Header />
  <Menu presentation="responsive" navigation={navigation} logo={logo} />
  {children}
</MenuProvider>

O Header abre o estado compartilhado automaticamente; onMenuClick explícito continua tendo prioridade. Menu open/onOpenChange permite controle externo. O menu responsive é lateral a partir de 768px e tela cheia abaixo disso, fecha ao navegar ou mudar de breakpoint e devolve foco ao gatilho. Os modos anteriores continuam funcionando sem provider.

Avatar variant="logo" src={url} name="Marca" usa borda, superfície clara, object-contain e respiro de 1/6 do tamanho. Recorte o viewBox dos SVGs ao desenho: contain não remove margens vazias gravadas no arquivo; fontes com viewBoxes diferentes continuarão parecendo de tamanhos diferentes.

Conversa

| Componente | No Figma | | :--- | :--- | | UserMessage | user-message | | AssistantMessage | assistant-message | | Composer | composer | | InputCard | input-card | | ListeningBanner | listening-banner | | Waveform | waveform | | MentionResult | mention-result | | SuggestionList | suggestion-list | | FeedbackPrompt | feedback-prompt | | ActionCard | action-card | | MetaRow | meta-row | | GenerationBoard | generation-board | | HistoryThumbnail | history-thumbnail | | Thumbnail | thumbnail | | ProductListRow | product-list-row | | ProductHero (topo da folha de detalhe do produto) | product-hero |

🎨 Tokens

Escala 4/8. Tudo é múltiplo de 4, no ritmo de 8.

| Tipo | Valores | | :--- | :--- | | Raio | 0 · 4 · 8 · 12 · 16 · 24 · pill | | Espaçamento | 4 · 8 · 12 · 16 · 20 · 24 · 32 · 40 · 48 · 64 | | Fonte | 12 · 14 · 16 · 20 · 24 · 32 · 40 · 48 | | Exceção | chart-axis 10px, só em eixo e rótulo de gráfico |

tokens/tokens.json é exportado da biblioteca e src/styles/theme.css é gerado a partir dele. Não edite o CSS à mão.

🙋 Pedir algo novo

  1. Abra uma issue com a tela, o caso de uso e por que nenhum componente atual serve.
  2. O dono desenha primeiro na biblioteca, publica e só então implementa aqui.
  3. Sai uma nova versão, e o app atualiza a dependência.

[!WARNING] Variante local é divergência. className serve para posicionar (margem, largura, alinhamento), nunca para mudar cor, tamanho ou borda. Valor solto (text-[15px], rounded-[10px], hex) também não entra no app.

🛠️ Desenvolver

npm install
npm run tokens            # regenera src/styles/theme.css a partir de tokens/tokens.json
npm run check             # typecheck + build
npx figma connect parse   # valida o Code Connect

| Caminho | O que é | | :--- | :--- | | tokens/tokens.json | os tokens exportados da INOVA Lab Library | | scripts/build-tokens.mjs | gera o tema (variáveis CSS + mapeamento do Tailwind 4) | | src/components/ | os componentes React | | figma/ | o Code Connect de cada componente | | assets/ | o logo do INOVA UI |

Publicar o pacote: criar uma release no GitHub com a versão do package.json; o workflow publish.yml publica no npm público com proveniência (trusted publishing, sem token). Publicar o Code Connect: FIGMA_ACCESS_TOKEN=… npx figma connect publish (exige plano Organization ou Enterprise no Figma).

🧭 INOVA UI Skill

A skill (skills/inova-ui-skill) é o guia de uso: como instalar, qual componente usar em cada caso, os padrões de tela (área segura, overlays, estados, texto em pt-BR) e o que é proibido. Ela orienta; o pacote decide o visual.


INOVA Lab · Leroy Merlin Brasil