Schumann.ui
Biblioteca de componentes React dark-first, orientada a tokens, extraída do design system do FinTrackr e desacoplada de qualquer regra de negócio.
- Tipada (TypeScript) · acessível (Radix + ARIA) · temável (CSS custom properties) · tree-shakeable (ESM +
sideEffectssó em CSS). - Documentada e desenvolvida no Storybook.
Instalação
npm install @schumann-dev/ui
# peers (provavelmente já instalados no seu app):
npm install react react-dom
Uso
Importe os componentes e o stylesheet uma única vez (na raiz do app):
import '@schumann-dev/ui/styles.css';
import { Button, Card, Input } from '@schumann-dev/ui';
export function Exemplo() {
return (
<Card>
<Card.Header>
<Card.Title>Resumo</Card.Title>
</Card.Header>
<Card.Content>
<Input placeholder="Buscar..." />
</Card.Content>
<Card.Footer>
<Button variant="primary">Salvar</Button>
</Card.Footer>
</Card>
);
}
Temas
O tema dark é o padrão. Para trocar, defina data-theme no <html>:
<html data-theme="light"> … </html>
Customização pontual = redefinir a CSS var num escopo:
.minha-area { --interactive-primary: #0ea5e9; }
Os componentes aceitam className e style, permitindo sobrescrita controlada sem alterar a biblioteca.
Tipografia
A fonte padrão é Roboto. A lib não embute fontes (responsabilidade do app) — carregue a família com os pesos que o DS usa (400/500/600/700/800):
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
A família é o token --font-sans. Para trocar por outra fonte, sobrescreva a variável:
:root { --font-sans: 'Inter', system-ui, sans-serif; }
(O mono --font-mono — JetBrains Mono, usado em valores/códigos — segue o mesmo princípio.)
Ícones (~2.000)
O set base do DS vem embutido. Para o set Lucide completo (~2.000 ícones, ISC), importe o entry opt-in uma vez no bootstrap do app:
import '@schumann-dev/ui/icons'; // registra tudo (~100 kB gzip — só quem importa paga)
<Icon name="rocket" /> // qualquer nome kebab-case do Lucide
<IconPicker icons={getIconNames()} ... /> // picker com todos + busca
- Os nomes originais do DS (
menu,chevDown,tag…) têm precedência e não são sobrescritos. - Ícones próprios:
registerIcons({ nome: '<path .../>' }). Nomes legados:registerIconAliases({ FiHome: 'home' }). - Precisa de mais? O mesmo mecanismo aceita qualquer set do Iconify (ex.:
@iconify-json/tabler, ~5.900 ícones).
Desenvolvimento
npm run storybook # ambiente de dev/documentação
npm run test # Vitest + Testing Library + jest-axe
npm run build # gera dist/ (ESM + styles.css + .d.ts)
npm run lint
Componentes (v0.2.0)
Atoms: Button, IconButton, Input, Text, Heading, Label, Badge, Spinner, Icon/IconBox (registro extensível via registerIcons/registerIconAliases), Avatar, Switch, Skeleton, ProgressBar.
Molecules: FormField, Combobox, MultiSelect, SegmentedControl (pill/card/tab), SwitchCard, MoneyInput, DateTimeInput, MonthPicker, ColorPicker, IconPicker (busca embutida), StatCard, EmptyState, Tooltip.
Organisms: Card, Modal, ConfirmModal, Menu, DataTable.
Templates: PageContainer, AppShell, PageHeader.
Mapeamento frontend-v3 → Schumann.ui
| App (frontend-v3) | Lib | Observação |
|---|---|---|
Field |
FormField |
fiação ARIA automática via contexto |
TextInput |
Input |
addons via leftAddon/rightAddon |
MoneyInput / DateTimeInput |
idem | moeda configurável; máscara pt-BR |
Select / IconSelect |
Combobox |
um só componente; visual da opção via leading (app monta o AccountLogo) |
MultiSelect |
MultiSelect |
igual |
StatusSegment |
SegmentedControl (pill) |
opções vêm do app |
BucketPick |
SegmentedControl (card) |
opções 50/30/20 ficam no app |
Toggle / ToggleCard |
Switch / SwitchCard |
role="switch" acessível |
ColorSwatches/ColorSelect/HSVColorPicker |
ColorPicker |
visual V2 (handoff ColorPickerV2.dc.html): swatches + botão de cor personalizada + painel inline hex/rgb; paleta via prop swatches |
IconPick |
IconPicker |
ícones via prop |
Icon/IconBox |
idem | aliases legados: registerIconAliases() no bootstrap do app |
Avatar |
Avatar |
app resolve a URL antes (resolveImageUrl) |
RowMenu |
Menu |
ícone por nome ou ReactNode |
DataGrid |
DataTable |
mesma API; strings via props/labels |
Modal/ModalHeader |
Modal + Modal.Content |
Radix (focus-trap/ESC) |
ConfirmModal |
ConfirmModal |
mapa de erros do backend vai em formatError |
Shell (layout) |
AppShell |
nav/slots via props; router/logout ficam no app |
stepper de mês do topbar (Shell) |
MonthPicker |
mesma pill ‹ mês ›, agora com painel 4×3 |
| cabeçalho inline (eyebrow + h1) das páginas | PageHeader |
12/12 páginas usavam o mesmo bloco |
| barra de progresso inline (orçamento/fatura) | ProgressBar |
limiar de perigo embutido |
| tiles de resumo do dashboard | StatCard |
acento por cor via prop |
| lista/card vazio ("Nenhum…") | EmptyState |
o DataTable já cobre o caso de tabela |
| barra de abas inline (Settings/Categorias…) | SegmentedControl variante tab |
sem componente novo |
hint title= nativo |
Tooltip |
acessível (Radix), hover + teclado |
AccountLogo, FamilyCard, TransferModal |
— (ficam no app) | domínio; compõem sobre os genéricos |
Atualização
Versionamento por SemVer com Changesets. Veja o CHANGELOG. Para gravar uma mudança:
npx changeset # descreve a mudança + tipo (patch/minor/major)
- patch — correção compatível.
- minor — novo componente/variante/prop.
- major — quebra de API ou remoção de prop (props depreciadas emitem
console.warnpor 1 minor antes da remoção).