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

@hibrit/moldura

v0.1.0

Published

Escola Moldura e Painel da HIBRIT: moldura do app, folhas, campos pt-BR e componentes do DS, sem cor de produto (tudo por variáveis CSS).

Readme

@hibrit/moldura

Escola Moldura e Painel da HIBRIT (decisão Crystal 1d4fa02e): a moldura do app, folhas e popovers, campos pt-BR e os componentes do DS, extraídos do Obrita (hbt-obra) para serem usados também pelo Spectra9 e pelo STK LITE. Decisão de pacote compartilhado: Crystal 08d00709 (nada de cópia entre repos). Design System de referência: https://claude.ai/artifact/DZoGWtD79HhdLB8egzmX6v

O pacote não traz cor de produto nenhuma. Tudo sai de variáveis CSS que cada produto define no próprio tokens.css.

Instalar

npm install @hibrit/moldura

Peer dependencies: react e react-dom 19+, next 15+, lucide-react 1+. Publicado no npm público (escopo @hibrit), então o build na Vercel não precisa de token.

CSS: a ordem importa

No CSS global do app (ex.: app/globals.css), nesta ordem:

@import "@hibrit/moldura/tokens.base.css"; /* estrutura neutra: raios, toque, sombras, botão do Google */
@import "./tokens.css";                     /* as cores do SEU produto (contrato abaixo) */
@import "@hibrit/moldura/moldura.css";      /* classes da escola */
@import "./ui.css";                         /* classes do seu produto */
@import "@hibrit/moldura/base.css";         /* reset, utilitários de tipografia e anel de foco: POR ÚLTIMO */

base.css vem por último de propósito: os utilitários (.lab, .sm, .muted...) e o :focus-visible precisam vencer as classes de componente de mesma especificidade (é o que mantém o anel de foco do teclado na lista de escolha, por exemplo). Inverter a ordem muda o visual.

Os componentes são client components ("use client" preservado no dist): podem ser importados direto de server components do App Router.

Contrato de tokens

O produto define em :root (claro) e redefine no escuro (@media (prefers-color-scheme: dark) com :root:not([data-theme="light"]), e :root[data-theme="dark"] para a escolha manual):

| token | papel | |---|---| | --shell | moldura (fundo do app, topo, menu lateral, barra inferior) | | --pane | painel do conteúdo, folhas, popovers | | --container | cartões e campos dentro do painel | | --field-shell | trilhos, hover, superfícies neutras mais escuras | | --ink / --ink-muted / --ink-disabled | texto principal / secundário / desabilitado e placeholder | | --icon | ícones e botões de ícone | | --accent / --on-accent | a UMA cor do tenant (ação primária, foco, logo) e o texto sobre ela | | --accent-tint / --on-accent-tint | pílula tingida do item ativo e o texto sobre ela | | --on-inverse-accent | ação do toast ("Desfazer") sobre --inverse | | --error / --error-container / --on-error-container | erro de campo e estado de dinheiro/prazo | | --warning-container / --on-warning-container | atenção | | --success-container / --on-success-container | sucesso | | --inverse / --on-inverse | toast | | --scrim | fundo atrás de folhas e diálogos | | --font-sans | família tipográfica (o pacote só tem system-ui de reserva) |

Raios (--radius-sm 10, --radius-md 14, --radius-lg 20, --radius-xl 28, --radius-pill), alvo de toque (--touch 44px), sombras (--shadow-float, --shadow-dialog, --shadow-toast) e as cores do botão do Google (--google-bg, --google-ink) já vêm do tokens.base.css; o produto pode sobrescrever.

Exemplo: tokens do Obrita (cimento claro + Tijolo)

:root {
  color-scheme: light;
  --shell: #eceae6;
  --pane: #fdfcfa;
  --container: #f4f2ee;
  --field-shell: #e3e0da;
  --ink: #26231f;
  --ink-muted: #6a655d;
  --icon: #4a463f;
  --ink-disabled: #9a948b;
  --accent: #ae5631;
  --on-accent: #ffffff;
  --accent-tint: color-mix(in srgb, var(--accent) 10%, white);
  --on-accent-tint: color-mix(in srgb, var(--accent) 45%, black);
  --on-inverse-accent: color-mix(in srgb, var(--accent) 30%, white);
  --error: #b3261e;
  --error-container: #fce8e6;
  --on-error-container: #8c1d18;
  --warning-container: #fff1d6;
  --on-warning-container: #5c3800;
  --success-container: #e3f4e8;
  --on-success-container: #146c2e;
  --inverse: #2f2b27;
  --on-inverse: #ffffff;
  --scrim: rgba(38, 35, 31, 0.42);
  --shadow-float: 0 12px 36px rgba(38, 35, 31, 0.18), 0 2px 6px rgba(38, 35, 31, 0.08);
  --font-sans: var(--font-google-sans-flex), var(--font-outfit), "Google Sans Flex", Outfit, system-ui, sans-serif;
}
/* + o mesmo bloco no escuro (accent #e0906a, shell #161513, pane #201e1b...). Arquivo completo:
   hibritoficial/hbt-obra, app/tokens.css */

As 7 leis

  1. Moldura sem conteúdo.
  2. Conteúdo em painel com raio 28, a 8px das bordas.
  3. Zero bordas e divisórias: a hierarquia vem do tom.
  4. Uma cor do tenant.
  5. Item ativo é pílula tingida.
  6. Sombra só no que flutua.
  7. No máximo um alerta por coluna.

E mais: alvos de toque ≥ 44px; menu lateral a partir de 900px, abaixo disso barra inferior de 5 itens; estado (verde/âmbar/vermelho) sempre com ícone + texto, nunca só cor; nada de <select> nem input de data nativos.

Componentes

1. Moldura: AppShell, Pane, Marca, SeletorTopo, Avatar

import { AppShell, Avatar, Marca, type ItemNav } from "@hibrit/moldura";
import { House, LayoutGrid, Layers, ReceiptText, ShoppingCart } from "lucide-react";

const NAV: ItemNav[] = [
  { href: "/", rotulo: "Início", Icone: House },
  { href: "/gastos", rotulo: "Gastos", Icone: ReceiptText },
  { href: "/compras", rotulo: "Compras", Icone: ShoppingCart },
  { href: "/etapas", rotulo: "Etapas", Icone: Layers },
  { href: "/mais", rotulo: "Mais", Icone: LayoutGrid },
];

<AppShell
  topo={<Marca href="/" nome="Obrita" icone={<House size={20} strokeWidth={2.2} aria-hidden />} />}
  acoes={<Avatar href="/conta" nome={pessoa.nome} foto={pessoa.foto} />}
  nav={NAV}                       // null = só o topo (.frame.bare), para listas e assistentes
  navExtra={<div className="navsec lab">Seus projetos</div>}
  flutuante={<button className="fab">Novo gasto</button>}
>
  {children}
</AppShell>

O item ativo usa usePathname (raiz só em /; os outros também nas sub-rotas). SeletorTopo é a troca de contexto no topo (ponto de cor + nome + seta). Pane é o painel sozinho (main.pane > .inner).

2. Folha e popover responsivos: Folha, Flutuante

<Folha titulo="Novo gasto" onFechar={fechar} rodape={<button className="btn full">Lançar gasto</button>}>
  …campos…
</Folha>

Abaixo de 900px sobe da base; a partir de 900px vira diálogo centralizado (dialogo força o diálogo também no celular). Esc e o fundo fecham; o foco vai para o campo com autoFocus e volta ao fechar. Flutuante é o popover ancorado (desktop) / folha acima de outra folha (celular) usado pela lista e pelo calendário; use para os seus próprios popovers (PONTO_DESKTOP = (min-width: 900px)).

3. Lista de escolha: Escolha, ListaEscolha

<Escolha rotulo="Etapa" valor={etapa} opcoes={[{ valor: "e1", rotulo: "Fundação", detalhe: "mês 2" }]} onChange={setEtapa} />

role="listbox", teclado (setas, Home, End, Enter), busca acima de 8 opções (sem acento, sem caixa), item escolhido em pílula tingida com marca. ListaEscolha é só a lista, para usar dentro de outro flutuante.

4. Calendário pt-BR: SeletorData, Calendario, Meses

<SeletorData rotulo="Data" valor={data} onChange={setData} />                 // "AAAA-MM-DD"
<SeletorData rotulo="Do mês" modo="mes" valor={mes} onChange={setMes} limpavel /> // "AAAA-MM"

Semana começando no domingo, atalho "Hoje" (ou "Este mês"), "Limpar" com limpavel, grade navegável por setas/PageUp/PageDown/Home/End. "Hoje" é o dia em São Paulo (Calendario e Meses aceitam hoje fixo).

5. Dinheiro e número: CampoMoeda, MoedaInline, CampoNumero

<CampoMoeda rotulo="Valor" valor={valor} onChange={setValor} grande autoFocus />  // R$ 1.234,56, digitação por centavos
<MoedaInline ariaLabel="Preço por m³" valor={v} onChange={setV} />                // sem rótulo, dentro de linha
<CampoNumero rotulo="Área construída" valor={area} onChange={setArea} sufixo="m²" casas={2} />  // 1.240,5

Colar "1.234,56", "1234.56" ou "R$ 1.234" lê o valor como está escrito. As funções puras estão em @hibrit/moldura/formato (formatarMoeda, moedaDigitada, moedaColada, numeroDigitado, formatarNumero, lerNumero, dataCurta, rotuloMes, hojeNoFuso, chaveBusca), sem React, usáveis no servidor.

6. Cidade do IBGE: @hibrit/moldura/ibge

import { CampoCidade, type Municipio } from "@hibrit/moldura/ibge";
<CampoCidade valor={municipio} onChange={setMunicipio} />   // "lond" → "Londrina · PR"; sem texto livre

Subexportação separada: quem não importa daqui não carrega a lista. A lista (5.571 municípios, ≈170 KB) fica num chunk próprio, baixado no primeiro foco do campo. No servidor, confie no código, nunca no texto: municipioPorCodigo(codigo) (assíncrona) ou a lista crua em @hibrit/moldura/ibge/dados.

7. Cartões de escolha: Opcao (rádio), Marcar (seleção), Chips, Segmentado

<div className="pick" role="radiogroup" aria-label="Objetivo">
  <Opcao on={obj === "venda"} titulo="Construir para vender" descricao="Mostra o lucro previsto." onClick={() => setObj("venda")} />
</div>
<Marcar on={feito} rotulo="Comprado" onClick={alternar} />
<Chips valor={cat} opcoes={[["material", "Material"], ["mo", "Mão de obra"]]} onChange={setCat} rotulo="Categoria" />
<Segmentado valor={tema} opcoes={[["", "Automática"], ["light", "Clara"], ["dark", "Escura"]]} onChange={setTema} rotulo="Aparência" />

.pick.cartoes põe os cartões em duas colunas a partir de 640px. Entrada (texto) e Campo (rótulo + erro/dica em volta de qualquer controle) completam o formulário.

8. Assistente em passos: AssistentePassos + rascunho injetável

<AssistentePassos
  passo={passo} total={4}
  onVoltar={voltar}                                   // no passo 1 o botão diz "Cancelar"
  onAvancar={avancar} rotuloAvancar={passo === 4 ? "Criar projeto" : "Continuar"}
  onDepois={passo >= 3 ? preencherDepois : undefined} // sem ele, não há "Preencher depois"
  ocupado={ocupado}
>
  …conteúdo do passo…
</AssistentePassos>

O rascunho guarda onde o produto mandar:

import { salvarRascunho, apagarRascunho, useRascunho, type ArmazenamentoRascunho } from "@hibrit/moldura";

salvarRascunho(`novo-projeto:${userId}`, { passo, dados });           // padrão: localStorage deste aparelho
const r = useRascunho<Dados>(`novo-projeto:${userId}`);               // { dados, salvoEm } | null (SSR: null)

const noServidor: ArmazenamentoRascunho = { ler, gravar, apagar, ouvir };  // ex.: sessionStorage, IndexedDB, API
salvarRascunho(chave, dados, noServidor);

9. Toast com "Desfazer": useToast + Toast

const { toast, aviso, fechar } = useToast();
aviso("Gasto excluído.", { rotulo: "Desfazer", fn: desfazer });  // 5 s com ação, 2,8 s sem
<Toast toast={toast} onFechar={fechar} />                        // monte uma vez, no provider do app

role="status", aria-live="polite". Sobe para o topo quando há folha aberta (body.lock). A lógica de "apagar só depois de 5 s sem Desfazer" é do produto (no Obrita: excluirComDesfazer).

10. Botão do Google: BotaoGoogle, GoogleG

<BotaoGoogle onClick={entrarComGoogle} disabled={pendente} aria-busy={pendente || undefined} className={roboto.className} />

Só o visual do guia de marca do Google ("G" oficial colorido, fundo neutro, sem borda, pílula). O fluxo de login (Google Identity Services, OAuth, server action) fica em cada produto.

O que NÃO é do pacote

  • Regra de negócio de qualquer produto: cálculos, comandos, has_feature, planos, permissões.
  • KPIs, selos de estado, linhas de lista, gráficos e ilustrações de cada produto (ficam no CSS e nos componentes do produto, sobre os tokens).
  • Cores de produto (tokens.css é de cada produto).
  • Fluxo de login, sessão, dados e rotas.
  • A chave de comparação de nomes que espelha índice de banco (ex.: chave_nome do Obrita): o chaveBusca daqui é só para busca de tela.

Desenvolvimento

npm install
npm run typecheck && npm test && npm run build

Publicação: tag vX.Y.Z igual à versão do package.json dispara .github/workflows/publish.yml (npm publish --access public com o secret NPM_TOKEN do repositório, mesmo padrão do @hibrit/connect). Semver: mudança visual ou de API pública é minor (0.x) e vai no CHANGELOG.