@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-ui

Uso 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.actions e somente o slot de layout.
  • Page.Actions e o root visual da barra.
  • Props como helpContent, helpLabel, position, align e size pertencem ao Page.Actions.
  • Nao usar ActionBar.*, Page.Root actions=... dentro de AppLayout, Fragment, div ou Page.ActionButton solto como root de AppLayout.actions.

Uso recomendado:

{
  "scripts": {
    "check:page-actions": "shared-ui-check-page-actions"
  }
}

Tambem e possivel apontar um diretorio explicitamente:

shared-ui-check-page-actions ./src

Page 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 locked e podem informar requiredPlan.
  • Use showColorSelector={false} ou showAppearanceSelector={false} quando o consumidor precisar exibir apenas uma parte do controle.
  • A story AppearanceStates mostra 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:

  • size segue a mesma escala visual dos campos base (xs, sm, md, lg).
  • Use triggerSlot quando o consumidor precisar injetar um botao proprio para abrir a selecao.
  • clearable exibe a acao de limpar somente quando ha value e onClear.
  • shortcutHint pode exibir atalhos de teclado, como F1.

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-fit e w-fit por 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.Footer para 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 formato rounded-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: use Page.Actions, Page.ActionButton e Page.ActionsSeparator.
  • Dialog: use Modal.
  • ChoiceGroup: use RadioGroup.
  • ConditionalCase: use RenderCase.
  • ConditionalRender: use RenderIf.
  • ToggleSwitch: use Switch.
  • SearchFilter: use Filter.
  • SearchWorkspace: componha layout no consumidor ou com componentes genéricos.
  • TabsUnderline e UnderlinedTabs: use TabsUnderlined.