@wellingtonhlc/shared-ui
v0.55.11
Published
Conjunto de componentes visuais padronizados, criado por Wellington Henrique para aplicacoes React.
Readme
Wellington Shared UI
Conjunto de componentes visuais padronizados, criado por Wellington Henrique para aplicacoes React.
O objetivo deste pacote e concentrar componentes base, tokens, presets e helpers de UI reutilizaveis, sem regras de negocio de dominio.
Pacote
npm install @wellingtonhlc/shared-uiUso basico:
import { Button, Card, Page, Table } from '@wellingtonhlc/shared-ui';
export function ExamplePage() {
return (
<Page.Root>
<Card.Root>
<Card.Header>
<Card.Title>Registros</Card.Title>
<Card.Description>Consulta de dados</Card.Description>
</Card.Header>
<Card.Body>
<Button>Salvar</Button>
</Card.Body>
</Card.Root>
</Page.Root>
);
}Principios
Este pacote foi desenhado para oferecer infraestrutura visual generica:
- componentes base de interface;
- campos de formulario reutilizaveis;
- primitivas de acao;
- tokens, estilos e presets;
- helpers sem dependencia de dominio;
- composicao por slots quando o componente possui partes internas.
Validacao de Page Actions
O pacote publica o CLI shared-ui-check-page-actions para validar o contrato de actions em uma aplicacao.
Contrato esperado:
AppLayout.actionse somente o slot de layout.Page.Actionse o root visual da barra.- Props como
helpContent,helpLabel,position,alignesizepertencem aoPage.Actions. - Nao usar
ActionBar.*,Page.Root actions=...dentro deAppLayout, Fragment,divouPage.ActionButtonsolto como root deAppLayout.actions.
Uso recomendado:
{
"scripts": {
"check:page-actions": "shared-ui-check-page-actions"
}
}Tambem e possivel apontar um diretorio explicitamente:
shared-ui-check-page-actions ./srcPage Actions
Page.Actions e a raiz visual das acoes de tela. O pacote suporta left, top, right e
bottom, com left como padrao desktop e bottom como fallback mobile quando permitido.
Persistencia de preferencia continua sendo responsabilidade do consumidor. Use
Page.ActionsProvider para fornecer a preferencia global sem acoplar o pacote a
localStorage:
import { useCallback, useMemo, useState } from 'react';
import { Page, type PageActionsPosition, type PageActionsPreferences } from '@wellingtonhlc/shared-ui';
const storageKey = '@my-app/preference/page-actions-position';
const positions: PageActionsPosition[] = ['left', 'top', 'right', 'bottom'];
function readStoredPosition() {
const stored = window.localStorage.getItem(storageKey);
return positions.includes(stored as PageActionsPosition) ? (stored as PageActionsPosition) : undefined;
}
export function AppLayout({ children }: { children: React.ReactNode }) {
const [position, setPosition] = useState<PageActionsPosition | undefined>(readStoredPosition);
const handlePositionChange = useCallback(function handlePositionChange(nextPosition: PageActionsPosition) {
setPosition(nextPosition);
window.localStorage.setItem(storageKey, nextPosition);
}, []);
const preferences = useMemo<PageActionsPreferences>(
() => ({
mobilePosition: 'bottom',
movable: true,
onPositionChange: handlePositionChange,
position,
}),
[handlePositionChange, position],
);
return (
<Page.ActionsProvider preferences={preferences}>
<Page.ActionsSlot position="left" />
<main>{children}</main>
<Page.ActionsSlot position="right" />
<Page.ActionsSlot position="top" />
<Page.ActionsSlot position="bottom" />
</Page.ActionsProvider>
);
}Acoes de uma tela devem continuar usando Page.Actions; o provider apenas controla onde elas serao exibidas:
import { Page } from '@wellingtonhlc/shared-ui/components/Page';
import { Save } from 'lucide-react';
export function EditPageActions() {
return (
<Page.Actions helpLabel="Ajuda" helpContent="Revise os dados antes de salvar.">
<Page.ActionButton icon={<Save />} label="Salvar" />
</Page.Actions>
);
}Telas podem limitar o contrato localmente:
<Page.Actions allowedPositions={['top', 'bottom']} movable={false}>
<Page.ActionButton label="Salvar" />
</Page.Actions>Acoes auxiliares podem ser passadas por helpContent, metaActions ou por componentes
marcados como meta. Em barras verticais, o grupo meta e renderizado em modo compacto
e alinhado na borda externa da barra para preservar leitura visual; as labels continuam
obrigatorias no contrato e sao usadas para acessibilidade e tooltip.
Exportacoes
Entrada principal:
import { Button, Card, Modal, Table, cn } from '@wellingtonhlc/shared-ui';Subpaths recomendados para telas grandes ou rotas lazy-loaded:
import { Button } from '@wellingtonhlc/shared-ui/components/Button';
import { Table } from '@wellingtonhlc/shared-ui/components/Table';
import { cn } from '@wellingtonhlc/shared-ui/utils/cn';Estilos:
import '@wellingtonhlc/shared-ui/styles.css';Foundations e tokens
styles.css define os tokens semânticos consumidos pelos componentes. Prefira sempre
esses tokens a valores locais:
- cores de superfície, texto, borda, marca e estados (
--background,--surface,--brand,--status-*-*); - tipografia (
--font-family-*,--font-size-*,--line-height-*); - espaçamento (
--space-*); - forma e elevação (
--radius-*,--shadow-*); - movimento (
--duration-*).
O Storybook documenta as escalas em Foundations e organiza os exemplos públicos em
Components, por função. Consumidores podem sobrescrever tokens de tema, mas componentes
não devem introduzir hexadecimais, sombras ou escalas paralelas.
O tema padrão usa azul como identidade de marca (--brand/--primary). No tema claro,
Sidebar e Top Bar usam --surface; no tema escuro, usam --background-secondary, a mesma
superfície de Page.Actions. TabsUnderlined possui um token próprio (--tabs-background)
para manter contraste entre abas selecionadas e não selecionadas mesmo quando as superfícies
do consumidor possuem a mesma cor.
Preset Tailwind:
import sharedUiPreset from '@wellingtonhlc/shared-ui/tailwind-preset';Componentes
Componentes disponiveis na API publica:
| Grupo | Componentes |
|-------|-------------|
| Layout | AppShell, Page, Sidebar, Card, StatCard |
| Acoes | Button, ActionPrimitives, AppShellActions, ConfirmationDialog |
| Feedback | Badge, EmptyState, PageMessage, Tooltip, CopyableField, NotificationCard |
| Identidade | Avatar |
| Formularios | FieldControl, FieldGroup, TextField, TextareaField, SelectField, SelectionField, DateField, DecimalField, MultiSelectField, PasswordInput, Switch, RadioGroup |
| Consulta | Filter, Workspace |
| Dados | Table, Pagination, TabsUnderlined |
| Controle de renderizacao | RenderIf, RenderCase |
| Tema | ThemePreferencesSelector |
DateField
DateField centraliza entrada de data, data e hora, ou apenas hora. Use mode
para declarar o formato esperado pelo campo:
<DateField label="Data" value="2026-06-16" onChange={setDate} mode="date" />
<DateField label="Data e hora" value="2026-06-16T14:30" onChange={setDateTime} mode="dateTime" />
<DateField label="Hora" value="14:30" onChange={setTime} mode="time" />enableTime permanece suportado como alias legado para mode="dateTime".
FieldGroup
FieldGroup renderiza um grupo visual de campos com legenda. A borda vem ativa por
padrao; use bordered={false} quando o consumidor precisar manter a semantica de
grupo sem adicionar contorno visual.
import { FieldGroup, TextField } from '@wellingtonhlc/shared-ui';
<FieldGroup title="Endereco" bordered={false}>
<TextField label="CEP" />
<TextField label="Cidade" />
</FieldGroup>;RadioGroup
RadioGroup segue a mesma escala de altura dos campos base (xs, sm, md,
lg) para alinhar visualmente com TextField, SelectField e demais controles
na mesma linha de formulario.
O padrao boxed renderiza um fieldset com legenda, fazendo a borda contornar
o grupo a partir da label. Use variant="plain" quando precisar do grupo sem
borda.
import { RadioGroup } from '@wellingtonhlc/shared-ui';
<RadioGroup label="Tipo Pessoa" value={1} onChange={setType}>
<RadioGroup.Item value={1} label="Fisica" />
<RadioGroup.Item value={2} label="Juridica" />
</RadioGroup>;AppShell.Topbar.Button
Use AppShell.Topbar.Button para acoes de icone na barra superior, como notificacoes,
configuracoes e atalhos globais. O componente reutiliza o mesmo contrato visual dos
primitivos de acao, mas aplica o tamanho esperado para a topbar.
import { AppShell } from '@wellingtonhlc/shared-ui';
import { Bell, Settings } from 'lucide-react';
<AppShell.Actions>
<AppShell.Topbar.Button icon={<Bell />} tooltip="Notificacoes" />
<AppShell.Topbar.Button icon={<Settings />} tooltip="Configuracoes" />
</AppShell.Actions>AppShell.Topbar.Button usa 32 px por padrão, a mesma escala do Avatar size="md".
Avatar
Avatar exibe uma imagem ou as iniciais acessíveis do nome informado. As cores do fallback
usam --avatar-background e --avatar-foreground, com contraste mais escuro no tema dark.
import { Avatar } from '@wellingtonhlc/shared-ui/components/Avatar';
<Avatar name="Wellington Silva" src={avatarUrl} />Sidebar
Use Sidebar.ActionButton, Sidebar.SubActionButton e Sidebar.NavGroup para navegação.
Não use variantes de Button para simular itens do menu. O botão sanduíche é fornecido por
Sidebar.ToggleButton e compartilha os mesmos estados de hover e foco dos itens principais.
No modo compacto, o Sidebar reserva um slot estável para cada ícone. Labels e chevrons são revelados durante a expansão sem deslocar o ícone ou provocar layout shift.
TabsUnderlined
TabsUnderlined renderiza abas sublinhadas com conteudo controlado ou nao controlado.
As abas ja possuem tamanho minimo padrao e padding horizontal para comportar labels
medias, como Configuracoes e Fornecedor, sem ficarem apertadas.
Quando houver labels longas, use tabWidth e truncateLabels para manter abas
uniformes e aplicar ellipsis no texto que exceder o limite.
import { TabsUnderlined } from '@wellingtonhlc/shared-ui';
<TabsUnderlined
items={items}
tabWidth="12rem"
truncateLabels
/>;Quando a largura puder variar dentro de uma faixa, use tabMinWidth e tabMaxWidth.
truncateLabels nao altera o comportamento sozinho; defina tambem tabWidth ou
tabMaxWidth para que o navegador saiba quando abreviar o texto.
ThemePreferencesSelector
ThemePreferencesSelector centraliza a escolha de cor e aparencia (system, light, dark) usada pelos consumidores.
Contrato minimo:
import { ThemePreferencesSelector, type ThemePreferencesValue } from '@wellingtonhlc/shared-ui';
const value: ThemePreferencesValue = {
color: 'ocean',
appearance: 'system',
};
<ThemePreferencesSelector
value={value}
options={[
{ key: 'ocean', label: 'Ocean', primary: '#2563eb' },
{ key: 'emerald', label: 'Emerald', primary: '#059669' },
]}
onChange={setValue}
/>;Pontos de validacao:
- O botao de aparencia selecionado deve ficar perceptivelmente destacado sem depender de hover.
- Opcoes de cor bloqueadas usam
lockede podem informarrequiredPlan. - Use
showColorSelector={false}oushowAppearanceSelector={false}quando o consumidor precisar exibir apenas uma parte do controle. - A story
AppearanceStatesmostra as tres aparencias ja selecionadas para validacao visual rapida.
SelectionField
SelectionField padroniza campos de selecao que exibem um registro, abrem uma busca externa e permitem limpar o valor selecionado. O componente nao gerencia modal nem estado interno do registro; o consumidor controla value, onClick e onClear.
Contrato minimo:
import { SelectionField, type SelectionDisplayValue } from '@wellingtonhlc/shared-ui';
import { UserCheck } from 'lucide-react';
const value: SelectionDisplayValue = {
title: 'Registro selecionado',
subtitle: 'Descricao complementar',
meta: 'COD-1024',
};
<SelectionField
label="Registro"
value={value}
icon={<UserCheck />}
clearable
size="sm"
selectTooltip="Selecionar registro"
clearTooltip="Limpar registro"
onClick={openSearch}
onClear={clearSelection}
/>;Pontos de validacao:
sizesegue a mesma escala visual dos campos base (xs,sm,md,lg).- Use
triggerSlotquando o consumidor precisar injetar um botao proprio para abrir a selecao. clearableexibe a acao de limpar somente quando havalueeonClear.shortcutHintpode exibir atalhos de teclado, comoF1.
Filter (contrato legado)
Filter.Root organiza filtros compactos, ocupando apenas o espaço necessário. Quando houver muitos filtros, somente o corpo do painel rola e as ações continuam visíveis.
import { Button, Filter, TextField } from '@wellingtonhlc/shared-ui';
export function CustomersFilters() {
return (
<Filter.Root description="Refine os critérios antes de consultar os resultados.">
<TextField label="Buscar" size="sm" />
<Filter.Footer>
<Button type="submit" size="sm">
Pesquisar
</Button>
</Filter.Footer>
</Filter.Root>
);
}Padrões do painel:
- Usa
h-fitew-fitpor padrão, sem ocupar a altura inteira da tela. - Limita a altura com
maxHeightClassName; o default émax-h-[calc(100vh-12rem)]. - Aplica rolagem apenas na área de filtros.
- Use
Filter.Footerpara manter botões sempre visíveis no rodapé do painel. - Em interfaces densas, campos dentro do painel devem preferir
size="sm"e nao devem usar formatorounded-full.
Filter atual
Filter.Root usa descrição vazia por padrão e rola apenas o viewport dos campos. Informe onSubmit e onClear para obter as ações automáticas Pesquisar e Redefinir; o submit cria um único <form> interno. submitLabel, clearLabel, isSubmitting, actionsDisabled e fieldsClassName são opcionais. Um Filter.Footer explícito substitui as ações automáticas e continua indicado para formulários externos.
Filter.Visibility oferece estado controlado ou não controlado. Filter.Trigger aceita o botão padrão ou clona um Page.ActionButton via triggerSlot, preservando seu estilo e handler. Dentro de Workspace.Root, o estado fechado recolhe a coluna de filtros.
Workspace.Root appearance="joined" compõe filtros e resultados sem gap em uma moldura única. O padrão continua separated; Table.Root permanece responsável por viewport, moldura e paginação.
<Filter.Root onSubmit={handleSearch} onClear={handleClear} isSubmitting={isLoading}>
<TextField label="Buscar" size="sm" />
</Filter.Root>Contrato publico 0.1.0
A API pública pré-v1 foi limpa para evitar compatibilidade artificial em produção.
Exports removidos:
ActionBar: usePage.Actions,Page.ActionButtonePage.ActionsSeparator.Dialog: useModal.ChoiceGroup: useRadioGroup.ConditionalCase: useRenderCase.ConditionalRender: useRenderIf.ToggleSwitch: useSwitch.SearchFilter: useFilter.SearchWorkspace: componha layout no consumidor ou com componentes genéricos.TabsUnderlineeUnderlinedTabs: useTabsUnderlined.