Solutio Design System — referência de componentes

Este app valida que apps/docs resolve @solutio/design-system via workspace do Yarn, sem publish, e serve como referência anotada de cada componente (nome, código, entrada/saída, mock) — ver openspec/changes/add-docs-component-reference. Com migrate-datatable, os 31 componentes vendorizados + o hook useDialogA11y estão migrados (roadmap de 9 changes, completo).

Guia numerado, seção por seção

Esta página (/) é a referência completa, cada componente com código/entrada/saída/mock. /guia organiza os mesmos componentes em seções numeradas (3.1, 3.2…), pensado para navegar sequencialmente pelo catálogo em vez de procurar um componente específico.

Ir para /guia →

Exemplo de aplicação real (réplica do mockup)

Réplica funcional, com os componentes reais deste pacote, da tela "Administração do Ambiente" de SolutioDesignSystemSAPFiori/SolutioDesignSystem.dc.html — o protótipo HTML usa um motor de template próprio (support.js) incompatível com React/Next.js, então a réplica é feita compondo NavBar, Banner e Tile reais, não copiando o HTML. Esta seção é uma tela composta, não um catálogo — por isso não é anotada instância a instância como as seções abaixo (ver "Non-Goals" de add-docs-component-reference).

Demonstração do contrato visual — não é o produto
Esta aplicação demonstra como o contexto visual irá funcionar. Todos os itens exibidos são ilustrativos e esta exibição não contempla as configurações de Identity (usuários, roles, permissões). Esta é a visão do administrador da plataforma, não a visão do cliente. A finalidade é apoiar o mapeamento das alterações de layout e levantar as discussões de arquitetura e infraestrutura.

Administração do Ambiente

Workspace
Administração do Ambiente
Administração de usuários
Administração do Ambiente
Roles
Criar roles globais e associar permissões a cada role.
Permissões
Visualizar o catálogo de permissões disponíveis na plataforma.
Grupo de Aplicações
Administração do Ambiente
Fase futura
Administração de integrações
Administração do Ambiente
Fase futura

Dados Mestres

Empresa
Dados Mestres
Materiais
Dados Mestres
Parceiros de Negócio
Dados Mestres

Icon

Icon — name="chevron-left"
Seta de voltar — usada no ShellBar e na seta esquerda do NavBar.
<Icon name="chevron-left" />
Entrada: IconProps — name: IconName (união fechada de 11 valores: chevron-left, chevron-right, chevron-down, close, check-circle, error-circle, warning-triangle, info-circle, value-help, apps, pin), size?: number (default 14)
Icon — name="check-circle"
Ícone semântico de sucesso — usado por StatusIndicator variant="success".
<Icon name="check-circle" />
Entrada: IconProps — name: IconName (união fechada de 11 valores: chevron-left, chevron-right, chevron-down, close, check-circle, error-circle, warning-triangle, info-circle, value-help, apps, pin), size?: number (default 14)
Icon — name="value-help"
Ícone de ajuda de valor — usado pelo botão do ValueHelpInput.
<Icon name="value-help" />
Entrada: IconProps — name: IconName (união fechada de 11 valores: chevron-left, chevron-right, chevron-down, close, check-circle, error-circle, warning-triangle, info-circle, value-help, apps, pin), size?: number (default 14)
Icon — name="apps"
Ícone de grade — usado nos Tile de navegação e no seletor de apps.
<Icon name="apps" />
Entrada: IconProps — name: IconName (união fechada de 11 valores: chevron-left, chevron-right, chevron-down, close, check-circle, error-circle, warning-triangle, info-circle, value-help, apps, pin), size?: number (default 14)

Button

Button — variant="emphasized"
Ação principal da tela (ex.: Salvar, Criar registro, Iniciar busca).
<Button variant="emphasized">Emphasized</Button>
Entrada: ButtonProps — variant?: 'emphasized' | 'default' | 'transparent' | 'destructive' (default 'default'), loading?: boolean, disabled?: boolean, ref?: Ref<HTMLButtonElement>, + atributos nativos de <button>Saída: onClick: () => void — chamado ao clicar, exceto quando disabled ou loading
Button — variant="default"
Ação neutra (ex.: Cancelar, Adicionar, Criar registro).
<Button variant="default">Default</Button>
Entrada: ButtonPropsSaída: onClick: () => void
Button — variant="transparent"
Ação secundária de baixo destaque (ex.: Atualizar, Visualizar filtros).
<Button variant="transparent">Transparent</Button>
Entrada: ButtonPropsSaída: onClick: () => void
Button — variant="destructive"
Ação irreversível (ex.: excluir, confirmar exclusão).
<Button variant="destructive">Destructive</Button>
Entrada: ButtonPropsSaída: onClick: () => void
Button — disabled
disabled bloqueia o clique e desabilita o <button> nativo — nenhuma lógica extra necessária no handler.
<Button disabled>Disabled</Button>
Entrada: ButtonProps
Button — loading
loading mostra um spinner, força disabled e bloqueia onClick mesmo sem disabled explícito.
<Button variant="emphasized" loading>
  Salvando…
</Button>
Entrada: ButtonProps

Formulário

FormField — editável
Input de texto base — label + value/onChange (ou defaultValue) como um <input> nativo. register() do React Hook Form funciona diretamente, sem Controller.
<FormField label="Nome" defaultValue="" placeholder="Digite para filtrar" />
Entrada: FormFieldProps — label: string, value?/defaultValue?: string, placeholder?: string, immutable?: boolean, + atributos nativos de <input>Saída: onChange: ChangeEvent<HTMLInputElement>
FormField — immutable
immutable renderiza como texto somente leitura em vez de input — útil para campos não editáveis pós-criação (ex.: código gerado).
<FormField label="Código (imutável)" value="ABC-123" immutable />
Entrada: FormFieldProps
TextArea
Mesmo padrão de FormField (label + <textarea> nativo), resize:vertical apenas.
<TextArea label="Observações" defaultValue="" />
Entrada: TextAreaProps — label: string, value?/defaultValue?: string, + atributos nativos de <textarea>Saída: onChange: ChangeEvent<HTMLTextAreaElement>
Select
<select> nativo — recebe options como array ({ value, label }), não filhos <Option>.
<Select
  label="Status"
  options={[
    { value: "active", label: "Ativo" },
    { value: "draft", label: "Rascunho" },
  ]}
  defaultValue="active"
/>
Entrada: SelectProps — label: string, options: SelectOption[], value?/defaultValue?: stringSaída: onChange: ChangeEvent<HTMLSelectElement>
Switch
Controlado (checked/onChange) — não integra com register() diretamente, usar Controller ou espelhar o estado manualmente.
<Switch label="Sincronização automática" checked={checked} onChange={setChecked} />
Entrada: SwitchProps — label: string, checked: boolean, onChange: (checked: boolean) => void, hint?: stringSaída: onChange(checked: boolean)
StatusRadioGroup
Par de rádios fixo Ativar/Inativar — value é 'active' | 'inactive'.
Status
Registros inativos não aparecem em buscas
<StatusRadioGroup
  value={status}
  onChange={setStatus}
  hint="Registros inativos não aparecem em buscas"
/>
Entrada: StatusRadioGroupProps — value: Status ('active' | 'inactive'), onChange: (status: Status) => void, hint?: stringSaída: onChange(status: 'active' | 'inactive')
DatePicker
<input type='date'> nativo, mesma caixa do FormField — sem calendário customizado, o seletor nativo já é acessível por teclado.
<DatePicker label="Data de início" defaultValue="" />
Entrada: DatePickerProps — label: string, value?/defaultValue?: stringSaída: onChange: ChangeEvent<HTMLInputElement>
ValueHelpInput + ValueHelpDialog
Padrão "ajuda de valor" (F4): input somente leitura + botão que abre um ValueHelpDialog de seleção com busca. Padrão recomendado: o clique no botão-ícone chama o endpoint por fora do componente, monta os rows, e só então abre o dialog — o ValueHelpDialog nunca busca dados sozinho (mesma regra do DataTable). Este exemplo usa o default (selectionMode="multiple", comportamento preservado sem alteração — ver seção seguinte para selectionMode="single"): "Cliente" e "Relatório" são as duas instâncias, lado a lado, com dados e ValueHelpDialogs diferentes. "Relatório" também demonstra tabela de múltiplas colunas e uma seção de filtros — um dos filtros (País/região) é, ele mesmo, um ValueHelpInput que abre um segundo ValueHelpDialog aninhado (filterFields type "value-help", ver openspec/changes/valuehelpdialog-nested-filters). O botão "Visualizar/Ocultar filtros" só aparece quando filterFields tem ao menos um item. Conhecido pelo time de negócios como "ajuda de pesquisa" — o campo "Cliente" abaixo também demonstra o autocomplete aditivo (rows/query/onQueryChange/onSelectRow): digitar filtra CLIENT_ROWS localmente, e selecionar uma sugestão já marca o item em selected (o mesmo array do ValueHelpDialog). minWidth/maxWidth (contrato: o campo nunca muda de tamanho conforme selected muda) são ajustáveis pelo slider acima.
Cliente
Relatório
Selecione um relatório
<ValueHelpInput
  label="Cliente"
  selected={selected}
  rowKey={(row) => row.id}
  renderChip={(row) => row.nome}
  onRemove={(row) => setSelected((sel) => sel.filter((s) => s.id !== row.id))}
  placeholder="Selecione um cliente"
  onOpen={async () => {
    // padrão recomendado: buscar por fora, o componente só recebe rows prontos
    const rows = await fetchClientesDoEndpoint();
    setRows(rows);
    setDialogOpen(true);
  }}
/>
<ValueHelpDialog
  open={dialogOpen}
  title="Selecionar cliente"
  columns={[{ key: "nome", label: "Nome" }]}
  rows={rows}
  rowKey={(row) => row.id}
  totalCount={rows.length}
  selected={selected}
  onToggleRow={(row) =>
    setSelected((sel) =>
      sel.some((s) => s.id === row.id) ? sel.filter((s) => s.id !== row.id) : [...sel, row],
    )
  }
  onClearSelection={() => setSelected([])}
  searchValue={search}
  onSearchChange={setSearch}
  onSearch={() => fetchClientesDoEndpoint(search).then(setRows)}
  onOk={() => setDialogOpen(false)}
  onCancel={() => setDialogOpen(false)}
/>

// "Relatório" — mesmo modo chips do "Cliente", tabela de 3 colunas, filterFields
// com um filtro type: "value-help" (País/região abre um 2º ValueHelpDialog aninhado)
<ValueHelpInput
  label="Relatório"
  selected={reportSelected}
  rowKey={(row) => row.id}
  renderChip={(row) => row.nome}
  onRemove={(row) => setReportSelected((sel) => sel.filter((s) => s.id !== row.id))}
  onOpen={() => setReportDialogOpen(true)}
/>
<ValueHelpDialog
  open={reportDialogOpen}
  columns={[{ key: "id", label: "ID" }, { key: "nome", label: "Nome" }, { key: "autoridade", label: "Autoridade" }]}
  rows={filteredReportRows}
  selected={reportSelected}
  onToggleRow={(row) =>
    setReportSelected((sel) =>
      sel.some((s) => s.id === row.id) ? sel.filter((s) => s.id !== row.id) : [...sel, row],
    )
  }
  filterFields={[
    { type: "text", key: "autoridade", label: "Autoridade", value: reportAutoridade, onChange: setReportAutoridade },
    {
      type: "value-help",
      key: "pais",
      label: "País/região",
      selected: reportCountrySelected,
      rowKey: (row) => row.id,
      renderChip: (row) => row.nome,
      onOpen: () => setReportCountryDialogOpen(true),
      onRemove: (row) => setReportCountrySelected((sel) => sel.filter((s) => s.id !== row.id)),
    },
  ]}
  onOk={() => setReportDialogOpen(false)}
  onCancel={() => setReportDialogOpen(false)}
/>
{/* ValueHelpDialog aninhado — aberto pelo filtro "value-help" acima */}
<ValueHelpDialog
  open={reportCountryDialogOpen}
  columns={[{ key: "nome", label: "Nome" }]}
  rows={COUNTRY_ROWS}
  selected={reportCountrySelected}
  onOk={() => setReportCountryDialogOpen(false)}
  onCancel={() => setReportCountryDialogOpen(false)}
/>
Entrada: ValueHelpInputProps — label, selected: ValueHelpRow[], rowKey?/valueColumn? (ao menos um), renderChip, onRemove, placeholder?, onOpen: () => void, selectionMode?: 'single' | 'multiple' (default 'multiple', comportamento deste exemplo), minWidth?/maxWidth? (number | string, default 220/480), rows?/query?/onQueryChange?/onSelectRow? (autocomplete aditivo). ValueHelpDialogProps — open, title, columns, rows, rowKey?/valueColumn?, totalCount, selected, onToggleRow (modo 'multiple') ou onSelectRow (modo 'single'), onClearSelection, searchValue, onSearchChange, onSearch, filterFields?, onOk, onCancel — ver exemplo seguinte para selectionMode='single'Saída: onToggleRow(row: ValueHelpRow), onSelectRow(row: ValueHelpRow), onOk: () => void, onCancel: () => voidMock: CLIENT_ROWS (8 linhas); REPORT_ROWS (14 linhas, 3 países); COUNTRY_ROWS (5 linhas) — estáticas, substituem chamadas reais de endpoint
ValueHelpInput + ValueHelpDialog — seleção única
selectionMode="single" (default "multiple", preservado sem alteração no exemplo acima). O ValueHelpDialog renderiza radio em vez de checkbox, e clicar numa linha diferente da já selecionada substitui a seleção (onSelectRow dedicado, não onToggleRow) — clicar na linha já selecionada não faz nada, mesma semântica de um <input type="radio"> nativo; só o "×" do chip do rodapé ou "Limpar seleção" esvaziam. valueColumn="codigo" é usado como default de rowKey (rowKey não é informado aqui) — não controla exibição, que continua livre via renderChip, aqui concatenando razaoSocial + cidade com "-". A busca do autocomplete continua cobrindo todas as colunas do row (código, razão social, cidade), igual ao modo múltiplo — dar foco no campo já lista os fornecedores (mesmas SUPPLIER_ROWS do ValueHelpDialog); escolher uma sugestão fecha a lista imediatamente (em vez de permanecer aberta como no modo múltiplo), porque selectionMode="single" também controla esse fechamento em ValueHelpInput.
Fornecedor
<ValueHelpInput
  label="Fornecedor"
  selected={selected}
  valueColumn="codigo"
  renderChip={(row) => `${row.razaoSocial} - ${row.cidade}`}
  onRemove={() => setSelected([])}
  onOpen={() => setDialogOpen(true)}
  selectionMode="single"
  rows={rows}
  query={query}
  onQueryChange={setQuery}
  onSelectRow={(row) => {
    setSelected([row]);
    setQuery('');
  }}
/>
<ValueHelpDialog
  open={dialogOpen}
  title="Selecionar fornecedor"
  columns={[
    { key: "codigo", label: "Código" },
    { key: "razaoSocial", label: "Razão social" },
    { key: "cidade", label: "Cidade" },
  ]}
  rows={rows}
  valueColumn="codigo"
  totalCount={rows.length}
  selected={selected}
  selectionMode="single"
  onSelectRow={(row) => setSelected([row])}
  onClearSelection={() => setSelected([])}
  searchValue={search}
  onSearchChange={setSearch}
  onSearch={() => {}}
  onOk={() => setDialogOpen(false)}
  onCancel={() => setDialogOpen(false)}
/>
Entrada: selectionMode?: 'single' | 'multiple' (default 'multiple') em ValueHelpInput/ValueHelpDialog; valueColumn?: string (default de rowKey quando rowKey é omitido); ValueHelpDialogProps em modo single troca onToggleRow por onSelectRow: (row) => void; rows?/query?/onQueryChange?/onSelectRow? em ValueHelpInput ligam o autocomplete (aditivo, mesmo mecanismo do modo múltiplo)Saída: onSelectRow(row: ValueHelpRow) — substitui a seleção (não faz toggle)Mock: SUPPLIER_ROWS (4 linhas, colunas codigo/razaoSocial/cidade) — mesmo dataset do autocomplete e do ValueHelpDialog
Chip
Chip de texto genérico (não se limita a permissões — usado para tags, papéis, itens selecionados de qualquer natureza), somente leitura ou removível via onRemove, com 5 variantes semânticas de cor (neutral/info idênticos, mesmo padrão do StatusIndicator).
workspace.readworkspace.write
neutralinfosuccesswarningerror
{chips.map((chip) => (
  <Chip key={chip} label={chip} onRemove={() => removerChip(chip)} />
))}

<Chip label="neutral" />
<Chip label="info" variant="info" />
<Chip label="success" variant="success" />
<Chip label="warning" variant="warning" />
<Chip label="error" variant="error" />
Entrada: ChipProps — label: ReactNode, variant?: 'neutral' | 'info' | 'success' | 'warning' | 'error' (default 'neutral'), onRemove?: () => voidSaída: onRemove: () => voidMock: chips = ["workspace.read", "workspace.write"]
MultiSelectDropdown
Combo de busca + checkboxes — abertura controlada externamente via open/onToggleOpen (não é auto-gerenciado).
<MultiSelectDropdown
  searchValue={search}
  onSearchChange={setSearch}
  options={options}
  open={open}
  onToggleOpen={() => setOpen((o) => !o)}
/>
Entrada: MultiSelectDropdownProps — searchValue, onSearchChange, options: MultiSelectOption[] (ou groups), open: boolean, onToggleOpen: () => voidSaída: onToggleOpen: () => void, cada option.onToggle: () => voidMock: msdOptions (2 opções estáticas, checked espelhado em msdChecked)
SearchSelectInput + Chip
Busca com sugestões inline + botão Adicionar — não é um dropdown, é busca-e-adiciona (ex.: montar uma lista de membros de um grupo, exibida abaixo como chips).
<SearchSelectInput
  options={options}
  query={query}
  onQueryChange={setQuery}
  selected={selected}
  onSelect={setSelected}
  onAdd={() => setMembros((m) => [...m, selected])}
/>
Entrada: SearchSelectInputProps — options: SearchSelectOption[], query: string, onQueryChange, selected: SearchSelectOption | null, onSelect, onAdd: () => voidSaída: onSelect(option: SearchSelectOption), onAdd: () => voidMock: SEARCH_OPTIONS (Marina Costa, Marcelo Alves)

Layout / navegação

ObjectPageTabs
Tabs do padrão de página de objeto (floorplan), usado junto de ObjectPageHeader.
<ObjectPageTabs
  tabs={[{ key: "geral", label: "Geral" }, { key: "membros", label: "Membros" }]}
  active={activeTab}
  onChange={setActiveTab}
/>
Entrada: ObjectPageTabsProps — tabs: Tab[], active: string, onChange: (key: string) => voidSaída: onChange(key: string)
NavBar
Barra de navegação horizontal com scroll (setas esquerda/direita) e botão de overflow que abre OverflowMenu.
<NavBar
  groups={navGroups}
  overflowOpen={overflowOpen}
  onToggleOverflow={() => setOverflowOpen((o) => !o)}
/>
Entrada: NavBarProps — groups: NavGroup[], overflowOpen: boolean, onToggleOverflow: () => voidSaída: cada group.onSelect: () => void, onToggleOverflow: () => void
NavBar — variante surface
Navegação por âncora fora do AppShell: fundo full-bleed, paleta clara, item ativo decidido por scroll (IntersectionObserver) — o NavBar mantém o item ativo visível no scroll horizontal automaticamente, e o botão de overflow abre OverflowMenu com todos os itens.
Visão geral

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Débitos

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Créditos

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Ajustes

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Apuração

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Histórico

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Anexos

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

Configurações

Conteúdo de exemplo — role este container pra ver o item ativo mudar, e o scroll horizontal do NavBar acompanhar sozinho.

// onSelect rola até a seção — não troca painel, NavBar não sabe de "seções"
<NavBar
  variant="surface"
  groups={sections.map((s) => ({ id: s.id, nome: s.nome, active: s.id === activeId, onSelect: () => scrollToSection(s.id) }))}
  overflowOpen={overflowOpen}
  onToggleOverflow={() => setOverflowOpen((o) => !o)}
/>

// activeId vem de um IntersectionObserver nas seções da página, não do clique
Entrada: NavBarProps — groups: NavGroup[], overflowOpen: boolean, onToggleOverflow: () => void, variant: 'surface'Saída: cada group.onSelect: () => void, onToggleOverflow: () => void
AvatarMenu + AppsMenuPopover
Popovers de propósito fixo, sem prop open — usam useDialogA11y(true, onClose, { trap: false }) hardcoded, o pai controla visibilidade montando/desmontando. Acionados aqui pelos botões do avatar e do título no ShellBar real no topo desta página, não por um gatilho isolado.

Clique no avatar ou no título "TC Cloud" no ShellBar, no topo da página, para ver os dois popovers.

{avatarMenuOpen && (
  <AvatarMenu userName="Maria Silva" items={items} onClose={() => setAvatarMenuOpen(false)} />
)}
{appsMenuOpen && (
  <AppsMenuPopover groups={groups} onClose={() => setAppsMenuOpen(false)} />
)}
Entrada: AvatarMenuProps — userName: string, items: AvatarMenuItem[], onClose: () => void. AppsMenuPopoverProps — groups: AppGroup[], onClose: () => voidSaída: cada item/app.onSelect: () => void, onClose: () => void
CollapsibleGroup
Seção colapsável com contador opcional, defaultOpen controla o estado inicial.
Permissões(2)
fiscal:readfiscal:write
<CollapsibleGroup title="Permissões" count={2}>
  <Chip label="fiscal:read" />
  <Chip label="fiscal:write" />
</CollapsibleGroup>
Entrada: CollapsibleGroupProps — title: string, count?: number, defaultOpen?: boolean, children: ReactNode
FilterBar
Contêiner puro de moldura — os campos de filtro são montados pelo consumidor via children (FilterBar não implementa nenhum campo). Botão 'Aplicar filtros' (canto inferior direito) e toggle de expandir/recolher (centralizado sobre a borda inferior) têm posição fixa por contrato, não configurável via prop.
<FilterBar onApply={aplicarFiltros}>
  <FormField label="Nome" value={nome} onChange={(e) => setNome(e.target.value)} />
  <Select
    label="Status"
    options={[{ value: "active", label: "Ativo" }, { value: "draft", label: "Rascunho" }]}
    value={status}
    onChange={(e) => setStatus(e.target.value)}
  />
</FilterBar>
Entrada: FilterBarProps — children: ReactNode, onApply: () => void, applyLabel? (default 'Aplicar filtros'), defaultOpen? (default true, estado interno não controlado externamente — mesmo padrão de CollapsibleGroup)Saída: onApply: () => void
ShellBar
Header fixo de topo, propósito único — recebe handlers por slot (onBack, onLogoClick, onTitleClick, onAvatarClick, etc.). O ShellBar real desta página está no topo (fora de qualquer seção); esta é uma segunda instância isolada, só para referência, sem afetar o header real.
<ShellBar
  title="TC Cloud"
  avatarInitials="MG"
  titleMenuOpen={appsMenuOpen}
  onTitleClick={() => setAppsMenuOpen((v) => !v)}
  avatarMenuOpen={avatarMenuOpen}
  onAvatarClick={() => setAvatarMenuOpen((v) => !v)}
/>
Entrada: ShellBarProps — title, avatarInitials, onLogoClick, onTitleClick, onAvatarClick (obrigatórios), onBack?, logo?, titleMenuOpen?, avatarMenuOpen?, onSearchClick?, onHelpClick?, onNotificationsClick? (opcionais)Saída: onTitleClick: () => void, onAvatarClick: () => void, onLogoClick: () => void
ObjectPageHeader
Cabeçalho de página de detalhe (floorplan) — kicker, título, status, ações salvar/cancelar.
Projeto
Projeto AlphaAtivo
<ObjectPageHeader
  kicker="Projeto"
  title="Projeto Alpha"
  status={{ variant: "success", label: "Ativo" }}
  onSave={salvar}
  onCancel={cancelar}
/>
Entrada: ObjectPageHeaderProps — kicker?: string, title: string, status?: { variant: StatusVariant, label: string }, onSave: () => void, onCancel: () => void, saving?: booleanSaída: onSave: () => void, onCancel: () => void

Dados

StatusIndicator — as 4 variantes
Texto + ícone semântico — variant é 'success' | 'error' | 'warning' | 'info'.
AtivoInativoEm andamentoInformação
<StatusIndicator variant="success" label="Ativo" />
<StatusIndicator variant="error" label="Inativo" />
<StatusIndicator variant="warning" label="Em andamento" />
<StatusIndicator variant="info" label="Informação" />
Entrada: StatusIndicatorProps — variant: StatusVariant, label: string
Card
Contêiner com title/subtitle/action (slot de ação no canto superior direito) + children.
Projetos
Workspace atual

3 projetos ativos

<Card title="Projetos" subtitle="Workspace atual" action={<Button variant="transparent">Ver todos</Button>}>
  <p>3 projetos ativos</p>
</Card>
Entrada: CardProps — title: string, subtitle?: string, action?: ReactNode, children: ReactNode
Tile — sem link
Bloco de navegação sem href — titleHint indica que não é clicável (cursor: help).
Sem link
cursor: help
<Tile title="Sem link" subtitle="cursor: help" titleHint="Não clicável" />
Entrada: TileProps — title: ReactNode, subtitle?: string, icon?: ReactNode, href?: string, linkComponent?: TileLinkProps, titleHint?: string, futurePhase?: boolean
Tile — com next/link
href + linkComponent={Link} prova a composição com o next/link real deste app — sem essa prop, Tile usaria <a> nativo (linkComponent é uma decisão de API deliberada, não um next/link embutido no pacote).
Com next/link
Navegação client-side
<Tile title="Com next/link" subtitle="Navegação client-side" href="/" linkComponent={Link} />
Entrada: TilePropsSaída: navegação via linkComponent (default 'a')
Tile — futurePhase
futurePhase mostra um badge 'Fase futura' sobre o Tile.
Fase futura
Ainda não disponível
Fase futura
<Tile title="Fase futura" subtitle="Ainda não disponível" futurePhase />
Entrada: TileProps

Feedback

BusyIndicator — os 3 tamanhos
Três pontos animados, size é 'small' | 'medium' | 'big' (default 'big').
<BusyIndicator size="small" />
<BusyIndicator size="medium" />
<BusyIndicator size="big" />
Entrada: BusyIndicatorProps — size?: 'small' | 'medium' | 'big'
Banner
Faixa de aviso persistente (não auto-some) — variant é 'info' | 'warning', onClose opcional mostra botão de fechar.
Este registro está pendente de aprovação.
<Banner variant="warning" onClose={fechar}>Este registro está pendente de aprovação.</Banner>
Entrada: BannerProps — variant: BannerVariant, onClose?: () => void, children: ReactNodeSaída: onClose: () => void
EmptyState
variant='empty' (padrão) ou 'error', com title/subtitle e ação opcional (renderiza um Button variant='default').
Nenhum projeto encontrado
Crie o primeiro projeto do workspace para começar.
<EmptyState
  title="Nenhum projeto encontrado"
  subtitle="Crie o primeiro projeto do workspace para começar."
  actionLabel="Criar projeto"
  onAction={criarProjeto}
/>
Entrada: EmptyStateProps — title: string, subtitle?: string, variant?: EmptyStateVariant, actionLabel?: string, onAction?: () => voidSaída: onAction: () => void
useToast
Diferente dos demais componentes (open/onChange por instância), Toast usa um contexto global — ToastProvider envolve a árvore uma vez, showToast dispara de qualquer lugar.
const { showToast } = useToast();
showToast({ message: "Salvo com sucesso!", variant: "success" });
Entrada: ToastOptions — message: string, variant: ToastVariant ('success' | 'error' | 'info'), durationMs?: number (default 4000)
ConfirmDialog
Dialog de confirmação de propósito fixo (confirmar/cancelar), controlado por open — foco preso, Esc fecha, foco inicial no botão Cancelar.
<ConfirmDialog
  open={confirmOpen}
  title="Confirmar exclusão"
  message="Esta ação não pode ser desfeita. Deseja continuar?"
  onConfirm={excluir}
  onCancel={() => setConfirmOpen(false)}
/>
Entrada: ConfirmDialogProps — open: boolean, title: string, message: string, confirmLabel?: string, onConfirm: () => void, onCancel: () => voidSaída: onConfirm: () => void, onCancel: () => void

DataTable

DataTable
Componente único cobrindo cabeçalho, ações, estado vazio e estado de carregamento — não é composto de Table/TableRow/TableCell. Recebe rows como prop (busca de dados fica no consumidor); os botões abaixo alternam busy/vazio só para demonstrar esses estados.

busy · onCreate + createLabel · onRefresh · onNavigate · onSelect

Projetos (2)
Nome
Status
Projeto Alpha
Ativo
Projeto Beta
Em andamento

Selecionados: nenhum

navigateIcon/navigateIconSize — a coluna de navegação continua sempre a última e com largura fixa; só o ícone é configurável (default chevron-right):

onNavigate · navigateIcon="apps"

Projetos (ícone customizado) (2)
Nome
Status
Projeto Alpha
Ativo
Projeto Beta
Em andamento

pagination modo cliente — rows já está inteiro em memória (34 itens); DataTable fatia sozinho por pageSize (default 10) e agrega mais a cada clique no rodapé, sem chamar nenhum endpoint:

pagination · pageSize (default 10, sem onLoadMore = modo cliente)

Projetos (paginação — modo cliente, 34 itens) (34)
Nome
Status
Projeto 1
Ativo
Projeto 2
Em andamento
Projeto 3
Inativo
Projeto 4
Ativo
Projeto 5
Em andamento
Projeto 6
Inativo
Projeto 7
Ativo
Projeto 8
Em andamento
Projeto 9
Inativo
Projeto 10
Ativo

pagination modo servidor — presença de onLoadMore muda o modo: rows é só a página já carregada, e clicar no rodapé só chama onLoadMore (aqui simulado com setTimeout); DataTable nunca fatia ou acumula nada sozinho. loadingMore exibe um BusyIndicator real no rodapé, não um spinner reimplementado:

pagination · onLoadMore (presença = modo servidor) · hasMore · loadingMore · totalCount

Projetos (paginação — modo servidor) (34)
Nome
Status
Projeto 1
Ativo
Projeto 2
Em andamento
Projeto 3
Inativo
Projeto 4
Ativo
Projeto 5
Em andamento
Projeto 6
Inativo
Projeto 7
Ativo
Projeto 8
Em andamento
Projeto 9
Inativo
Projeto 10
Ativo

onSortChange/onGroupChange tornam o cabeçalho clicável, abrindo o DataTableFeatureDialog ancorado na coluna. DataTable nunca reordena/reagrupa rows sozinho — quem aplica a ordenação e o agrupamento escolhidos é este mesmo componente de página (o "consumidor"), reordenando o array de 34 itens (3 status distintos) antes de repassá-lo. Agrupar não é ordenar: com groupBy, o próprio DataTable insere uma linha divisória (cor leve, label "Coluna: valor") antes da primeira linha de cada grupo — desde que as linhas já cheguem clusterizadas (o consumidor ainda decide a ordem, `DataTable` só detecta a mudança de valor entre linhas consecutivas):

onSortChange + sortBy/sortDirection · onGroupChange + groupBy

Sem agrupamento

Projetos (clique no cabeçalho para ordenar/agrupar) (34)
Projeto 1
Ativo
Projeto 2
Em andamento
Projeto 3
Inativo
Projeto 4
Ativo
Projeto 5
Em andamento
Projeto 6
Inativo
Projeto 7
Ativo
Projeto 8
Em andamento
Projeto 9
Inativo
Projeto 10
Ativo
Projeto 11
Em andamento
Projeto 12
Inativo
Projeto 13
Ativo
Projeto 14
Em andamento
Projeto 15
Inativo
Projeto 16
Ativo
Projeto 17
Em andamento
Projeto 18
Inativo
Projeto 19
Ativo
Projeto 20
Em andamento
Projeto 21
Inativo
Projeto 22
Ativo
Projeto 23
Em andamento
Projeto 24
Inativo
Projeto 25
Ativo
Projeto 26
Em andamento
Projeto 27
Inativo
Projeto 28
Ativo
Projeto 29
Em andamento
Projeto 30
Inativo
Projeto 31
Ativo
Projeto 32
Em andamento
Projeto 33
Inativo
Projeto 34
Ativo

Correção: a coluna de navegação continua sempre encostada na borda direita mesmo quando todas as colunas de columns têm width explícito (antes, o espaço sobrando do table-layout: fixed era redistribuído também para a coluna de navegação, afastando o ícone da borda):

columns[].width em todas as colunas · onNavigate

Projetos (colunas com width — navegação encostada na borda) (2)
Nome
Status
Projeto Alpha
Ativo
Projeto Beta
Em andamento
<DataTable
  title="Projetos"
  columns={[{ key: "nome", label: "Nome" }, { key: "status", label: "Status" }]}
  rows={projects}
  rowKey={(row) => row.id}
  busy={isPending}
  onCreate={criarProjeto}
  createLabel="Criar Projeto"
  onRefresh={atualizar}
  onNavigate={(row) => router.push(`/projects/${row.id}`)}
  onSelect={(row) => alternarSelecao(row.id)}
/>
Entrada: DataTableProps<T> — title, columns: Column<T>[], rows: T[], rowKey, totalCount?, onCreate?/createLabel?, onNavigate?, onRefresh?, readOnly?, actions?, onSelect?, density?: 'cozy' | 'compact', busy?, emptyMessage?, navigateIcon?: IconName (default 'chevron-right'), navigateIconSize?: number (default 13), pagination?: boolean, pageSize?: number (default 10), onLoadMore?: () => void, hasMore?: boolean, loadingMore?: boolean, onSortChange?: (key, direction) => void, sortBy?, sortDirection?, onGroupChange?: (key | null) => void, groupBy?, reorderableColumns?: boolean, columnOrder?: string[], onColumnOrderChange?: (order: string[]) => void, horizontalScroll?: boolean, toolbarActions?: DataTableToolbarAction[], exportable?: boolean, exportFileName?, exportSheetName?, getExportRows?; Column<T>.exportValue? — veja /visoes/bigtableSaída: onSelect(row: T), onNavigate(row: T), onCreate: () => void, onRefresh: () => void, onLoadMore: () => void, onSortChange(key, direction), onGroupChange(key | null)Mock: DT_ROWS (Projeto Alpha, Projeto Beta) / DT_COLUMNS (nome, status)

DetailPanel

DetailPanel
Título principal + grade de campos "Título: Valor" organizados em colunas — cada coluna empilha seus próprios campos na ordem recebida, sem exigir a mesma quantidade entre colunas. Réplica do painel real "Apuração CBS" (Empresa/Exercício/Período, Data Limite Conclusão/Data de Recolhimento, Status — compõe StatusIndicator já migrado, não texto colorido cru). Um campo sem label ocupa a linha inteira — demonstrado separadamente abaixo, fora da réplica, pra não confundir com o layout do exemplo visual.
Apuração CBS
Empresa: 1000
Exercício: 2025
Período: 1
Data Limite Conclusão: 02.02.2025
Data de Recolhimento: 10.02.2025
Status: Concluído

Campo sem label — ocupa a linha inteira, não confinado à largura de uma coluna:

Apuração CBS
Empresa: 1000
Observação: todos os documentos fiscais deste período já foram conciliados.
<DetailPanel
  title="Apuração CBS"
  columns={[
    [
      { label: "Empresa", value: "1000" },
      { label: "Exercício", value: "2025" },
      { label: "Período", value: "1" },
    ],
    [
      { label: "Data Limite Conclusão", value: "02.02.2025" },
      { label: "Data de Recolhimento", value: "10.02.2025" },
    ],
    [{ label: "Status", value: <StatusIndicator variant="success" label="Concluído" /> }],
  ]}
/>

// campo sem label ocupa a linha inteira, não confinado à largura de uma coluna
<DetailPanel
  title="Apuração CBS"
  columns={[
    [{ label: "Empresa", value: "1000" }],
    [{ value: "Observação: todos os documentos fiscais deste período já foram conciliados." }],
  ]}
/>
Entrada: DetailPanelProps — title: string, columns: DetailPanelField[][] (cada array interno é uma coluna, empilhada na ordem recebida). DetailPanelField — label?: string, value: ReactNode (campo sem label ocupa a linha inteira, não confinado à largura de uma coluna)

DetailPanelNav

DetailPanelNav
Compõe DetailPanel + NavBar (variant="surface") reais, com uma linha de controles entre os dois: toggle (mostra/oculta o painel, título sempre visível) e fixação (fixa painel+navbar juntos abaixo do topo). Réplica do mesmo painel "Apuração CBS" acima, agora com as abas Débitos/Créditos/Ajustes/Apuração da tela real. stickyOffset é calc(var(--sol-shell-h) * 2), pra colar logo abaixo do ShellBar + NavBar de âncora reais desta página (ambos sticky) em vez de sobrepor os dois. Clique em "Fixar painel" e role a página — os parágrafos abaixo, mais o resto do catálogo, servem de conteúdo pra rolar. NavBar variant="surface" é full-bleed (alcança a borda real da viewport) incondicionalmente — por isso este demo usa fullBleed, pra o painel acompanhar a mesma largura em vez de ficar confinado ao container enquanto o NavBar sangra sozinho; e por isso também não usa um quadro de scroll local (overflow diferente de visible quebraria o full-bleed do NavBar).
Apuração CBS
Empresa: 1000
Exercício: 2025
Período: 1
Data Limite Conclusão: 02.02.2025
Data de Recolhimento: 10.02.2025
Status: Concluído

Conteúdo de exemplo #1 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #2 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #3 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #4 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #5 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #6 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #7 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #8 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #9 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #10 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #11 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #12 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #13 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

Conteúdo de exemplo #14 — role a página pra ver o painel (ou, com "Fixar painel", o painel inteiro) congelar logo abaixo do ShellBar + NavBar de âncora.

<DetailPanelNav
  title="Apuração CBS"
  columns={[...]}
  groups={[
    { id: "debitos", nome: "Débitos", active: tab === "debitos", onSelect: () => setTab("debitos") },
    { id: "creditos", nome: "Créditos", active: tab === "creditos", onSelect: () => setTab("creditos") },
    { id: "ajustes", nome: "Ajustes", active: tab === "ajustes", onSelect: () => setTab("ajustes") },
    { id: "apuracao", nome: "Apuração", active: tab === "apuracao", onSelect: () => setTab("apuracao") },
  ]}
  overflowOpen={false}
  onToggleOverflow={() => {}}
  stickyOffset="calc(var(--sol-shell-h) * 2)"
  fullBleed
/>
Entrada: DetailPanelNavProps — title, columns (→ DetailPanel), groups, overflowOpen, onToggleOverflow (→ NavBar variant=surface), defaultExpanded?: boolean (default true), stickyOffset?: string | number (default 'var(--sol-shell-h)'), toggleIcon?: IconName (default 'chevron-down'), pinIcon?: IconName (default 'pin'), toggleButtonTitle?, pinButtonTitle?, background?: string (default var(--sol-surface)), dividerColor?: string (default var(--sol-border)), fullBleed?: boolean (default false)

Checkbox

Checkbox controlado com rótulo e descrição opcional. Um por linha (ex.: uma permissão por linha). Para um liga/desliga de configuração use Switch. Receita completa: docs/recipes/checkbox-e-layout-duas-colunas.md.

PropTipoDefaultDescrição
labelReactNode—Rótulo principal. Clicar nele (ou na descrição) alterna o checkbox.
descriptionReactNode—Texto secundário; ligado ao input por aria-describedby; quebra linha em qualquer ponto.
checkedboolean—Valor atual (controlado). Ignorado com locked: sempre aparece marcado.
onChange(checked: boolean) => void—Nunca chamado com disabled ou locked.
disabledbooleanfalseEsmaecido, cursor not-allowed, não alterna, sem selo.
lockedbooleanfalseMarcado e não editável, com selo dentro do <label> (anunciado por leitores de tela).
lockedLabelstring'obrigatória'Texto do selo quando locked.
Checkbox — estados
Os quatro estados: normal, marcado, disabled e locked (selo padrão e personalizado). Os dois primeiros são interativos.
<Checkbox label="Visualizar empresas" description="company.read" checked={v} onChange={setV} />
<Checkbox label="Editar empresas" description="company.write" checked disabled onChange={() => {}} />
<Checkbox label="Visualizar empresas" description="company.read" checked locked lockedLabel="mínima" onChange={() => {}} />
<Checkbox label="Acesso base" checked locked onChange={() => {}} />
Entrada: CheckboxProps — label: ReactNode, description?: ReactNode, checked: boolean, onChange: (checked: boolean) => void, disabled?: boolean, locked?: boolean, lockedLabel?: string (default 'obrigatória')Saída: onChange(checked: boolean)
Checkbox — texto longo
Rótulo e descrição longos quebram linha dentro de uma coluna de ~15rem, sem estourar a largura nem criar scroll horizontal.
<div style={{ width: "15rem" }}>
  <Checkbox label="Revogar o acesso permanente de empresas parceiras" description="company.access.revoke.permanently" ... />
</div>
Entrada: CheckboxProps

TwoColumnLayout

Formulário (main) à esquerda e painel lateral (aside) à direita. Em telas largas (≥ 900px) o painel fica fixo e rola por dentro quando é mais alto que a janela; abaixo de 900px vira coluna única. É só CSS — o breakpoint fica dentro do componente. Não é o DetailPanelNav (navegação por seções de uma página de objeto): é outro tipo de painel.

PropTipoDefaultDescrição
mainReactNode—Coluna principal (formulário). Primeiro no DOM.
asideReactNode—Painel lateral; fixo (sticky) com rolagem interna em telas largas; abaixo de main em telas estreitas.
mainMaxWidthstring | number'40rem'Largura máxima de main (≥ 900px). Número = px.
stickyOffsetstring | number'7.5rem'Distância do topo onde o aside fica fixo (altura dos cabeçalhos fixos acima). Número = px.
bottomOffsetstring | number'5.5rem'Espaço do rodapé fixo, descontado da altura máxima do aside. Número = px.

Atenção: position: sticky não funciona se algum ancestral tiver overflow diferente de visible. Os defaults (7.5rem/5.5rem) são os do cabeçalho de objeto + ShellBar e do rodapé fixo da tela de aplicativos do TCCloud — ajuste à sua página.

Controles do protótipo (não fazem parte do componente)
TwoColumnLayout — formulário + painel de permissões
Painel longo: role a página e veja o aside permanecer visível, com barra de rolagem própria. Com o painel curto não há barra. Abaixo de 900px (redimensione a janela) vira coluna única.

1 permissão(ões) selecionada(s).

Permissões do aplicativo
Dados mestres (1/6)
Financeiro (0/5)
Fiscal (0/4)
Administração (0/4)
<TwoColumnLayout
  main={<form>{/* campos */}</form>}
  aside={<Card>{/* CollapsibleGroup + Checkbox por permissão */}</Card>}
  stickyOffset="4rem"
  bottomOffset="1rem"
/>
Entrada: TwoColumnLayoutProps — main: ReactNode, aside: ReactNode, mainMaxWidth?: string | number (default '40rem'), stickyOffset?: string | number (default '7.5rem'), bottomOffset?: string | number (default '5.5rem')

Lacunas da tela "Parâmetros de DF-e"

Componentes e props adicionados para a tela de parâmetros de DF-e do TCCloud (DS-GAP-1 a 7). Tudo é opcional e aditivo. Receitas: docs/recipes/dialog-erro-de-campo-e-breadcrumb.md e docs/recipes/datatable-report-screen.md (itens 17 a 19).

Dialog

PropTipoDefaultDescrição
openboolean—false não renderiza nada.
titleReactNode—Título e nome acessível (aria-labelledby com id único por instância).
childrenReactNode—Conteúdo livre; rola por dentro quando é alto.
footerReactNode—Ações fixas no rodapé.
onClose() => void—Esc, clique no fundo e botão Fechar (exceto com busy).
widthnumber | string520Número = px.
closeOnBackdropbooleantruefalse ignora o clique no fundo; Esc continua fechando.
busybooleanfalseBloqueia Esc, fundo e botão Fechar.
role'dialog' | 'alertdialog''dialog'alertdialog para exigir resposta.
Dialog — conteúdo livre e busy
Abre com conteúdo próprio e rodapé. Com 'Simular operação' o diálogo fica busy por 2 s: Esc, fundo e Fechar não fecham.
<Dialog open={aberto} title="Histórico de alterações" width={560} busy={busy} onClose={() => setAberto(false)}
  footer={<Button onClick={() => setAberto(false)}>Fechar</Button>}>
  conteúdo livre
</Dialog>
Entrada: DialogProps — open, title, children, footer?, onClose, width?, closeOnBackdrop?, busy?, role?Saída: onClose()

ConfirmDialog — conteúdo e estado

PropTipoDefaultDescrição
childrenReactNode—Entre a mensagem e os botões (ex.: campo de motivo).
messageReactNode—Antes só string.
confirmDisabledbooleanfalseDesabilita o botão de confirmar.
confirmLoadingbooleanfalseSpinner no confirmar, botões desabilitados; Esc e fundo não fecham.
widthnumber | string420Número = px.
ConfirmDialog — motivo obrigatório
Confirmar só habilita com um motivo digitado; ao confirmar, simula 1,5 s de gravação (confirmLoading).
<ConfirmDialog open title="Editar campos" message="Informe o motivo." confirmLabel="Salvar"
  confirmDisabled={motivo.trim() === ''} confirmLoading={salvando} width={520} ...>
  <TextArea label="Motivo" maxLength={500} showCount value={motivo} onChange={...} />
</ConfirmDialog>
Entrada: ConfirmDialogPropsSaída: onConfirm() / onCancel()

Switch e Checkbox — nome acessível

PropTipoDefaultDescrição
hideLabelbooleanfalseEsconde o rótulo só visualmente (continua para leitores de tela).
aria-labelstring—Nome acessível explícito, repassado ao input.
aria-labelledbystring—Id de elemento que nomeia o controle.
Switch.labelReactNode—Antes só string.
Checkbox.indeterminatebooleanfalseEstado misto (aria-checked="mixed").
Switch — hideLabel
Rótulo escondido: o leitor de tela anuncia 'Editável'. Inspecione o input na árvore de acessibilidade.
<Switch label="Editável" hideLabel checked={v} onChange={setV} />
<Checkbox label="Selecionar C001" hideLabel checked={c} onChange={setC} />
Entrada: SwitchProps / CheckboxProps — hideLabel?, aria-label?, aria-labelledby?Saída: onChange(checked: boolean)

Erro de campo

PropTipoDefaultDescrição
errorReactNode—FormField, TextArea e Select. Borda --sol-error, mensagem, aria-invalid e aria-describedby.
showCountbooleanfalseTextArea: contador n/maxLength (precisa de maxLength).
FormField — error
Validação é do formulário; o DS só apresenta. Apague/preencha os campos para ver o erro sumir.
Informe o código
O motivo é obrigatório
Escolha um modelo
<FormField label="Código" value={codigo} onChange={...} error={codigo ? undefined : 'Informe o código'} />
<TextArea label="Motivo" maxLength={500} showCount error={...} />
<Select label="Modelo" options={...} error={...} />
Entrada: FormFieldProps / TextAreaProps / SelectProps — error?: ReactNodeSaída: onChange(event)

DetailPanel — wrap

PropTipoDefaultDescrição
wrapbooleanfalsePainel: permite quebra de linha em todos os campos com rótulo.
DetailPanelField.wrapboolean—Campo: vale só para ele e vence o painel.
DetailPanel — wrap
Campo com rótulo normalmente não quebra. Com wrap, o texto longo quebra dentro da coluna.
Campo C001
Motivo (wrap): Ajuste solicitado pelo time fiscal após a atualização do layout da SEFAZ para o ano corrente.
Origem (nowrap): Importação manual feita pelo usuário administrador
<DetailPanel title="Campo" columns={[[{ label: "Motivo", value: texto, wrap: true }]]} />
Entrada: DetailPanelProps — wrap?: boolean; DetailPanelField — wrap?: boolean

Breadcrumb

PropTipoDefaultDescrição
itemsBreadcrumbItem[]—{ label, href?, onClick?(event) }. O último é a página atual (aria-current="page", sem link).
ariaLabelstring'Você está em'Nome acessível do <nav>.
Breadcrumb — trilha
Link (href), botão (só onClick) e página atual. O separador › é CSS e não é lido por leitores de tela.
Última ação: —
<Breadcrumb items={[
  { label: 'Início', href: '/' },
  { label: 'Fiscal', onClick: () => router.push('/fiscal') },
  { label: 'Parâmetros de DF-e' },
]} />
Entrada: BreadcrumbProps — items: BreadcrumbItem[], ariaLabel?: stringSaída: onClick(event: MouseEvent)

DataTable — seleção de linhas, faixa, overflow e tooltip

PropTipoDefaultDescrição
selectedKeysstring[] | Set<string>—Seleção controlada (chave = rowKey). Liga a coluna de seleção.
defaultSelectedKeysstring[]—Seleção inicial do modo não controlado.
onSelectedKeysChange(keys: string[]) => void—Lista completa a cada mudança.
isRowSelectable(row) => booleantodasfalse: checkbox desabilitado e fora do "selecionar todas".
selectAllScope'page' | 'all''page''all' = linhas que passam o filtro por coluna (servidor: as carregadas).
selectionActionsReactNode | DataTableToolbarAction[]—Ações da faixa "N selecionadas" (no lugar de actions).
DataTableToolbarAction.overflowbooleanfalseVai para o menu "…".
DataTableToolbarAction.groupstring—Separa grupos de itens dentro do menu "…".
Column.tooltip(row) => string—Tooltip de célula com render; célula focável por teclado.
DataTable — seleção de linhas
C003 não é selecionável (órfão). Foque a Observação (Tab) para o tooltip de Column.tooltip; nas colunas sem render o tooltip automático só aparece quando o texto é cortado (estreite a janela). Alterne o escopo do cabeçalho.
Escopo do cabeçalho:
Campos (5)
1 selecionada
Campo
Descrição
Observação (célula com render)
infNFe.emit.CNPJ
CNPJ do emitente da nota fiscal eletrônica
Campo obrigatório em todos os modelos de documento fiscal eletrônico homologados.
infNFe.dest.xNome
Razão social do destinatário
Texto livre
infNFe.total.vNF
Valor total da nota fiscal
Órfão: sem correspondência no layout atual
Selecionadas: C002 · Última ação: —
<DataTable title="Campos" columns={columns} rows={rows} rowKey={(r) => r.id}
  selectedKeys={sel} onSelectedKeysChange={setSel}
  isRowSelectable={(r) => r.id !== 'C003'} selectAllScope="all" filterableColumns
  selectionActions={[{ key: 'ok', label: 'Aprovar', onClick }, { key: 'x', label: 'Excluir', onClick, overflow: true }]} />
Entrada: DataTableProps — selectedKeys?, defaultSelectedKeys?, onSelectedKeysChange?, isRowSelectable?, selectAllScope?, selectionActions?Saída: onSelectedKeysChange(keys: string[])Mock: 5 campos de DF-e (inline)

A implementação completa do site (Fumadocs, páginas MDX por componente) é escopo de uma proposta futura.