Contrato visual — Solutio IT / TCCloud

Réplica de GuiaVisualContrato.html usando os componentes reais de @solutio/design-system — tema claro único (sem modo escuro). Cada bloco "Ver código" mostra o uso real do pacote, não o HTML/TSX do protótipo original (que em alguns pontos já documentados em CLAUDE.md ficou desatualizado em relação à API real — ex. DataTable, SearchSelectInput).

1. Cores
Shell
--sol-shell-bg
Barra superior e navegação
Brand
--sol-brand
Ações, links, ícones ativos
Brand Hover
--sol-brand-hover
Hover / pressed
Brand Light
--sol-brand-light
Seleção, hover em lista
Página
--sol-page-bg
Fundo de página
Superfície
--sol-surface
Cartões, tabelas, tiles
Borda
--sol-border
Bordas de cartão e linha
Texto
--sol-text
Texto principal
Texto sec.
--sol-text-secondary
Labels e apoio
Sucesso
--sol-success
Ativo / validado
Erro
--sol-error
Inativo / inválido
Alerta
--sol-warning
Em andamento
2. Tipografia
font-family: var(--sol-font)
22px / 700 — Título de página de objeto
18px / 400 — Cabeçalho de grupo de tiles
16px / 700 — Título de seção e de tabela
14px / 400 — Corpo, células de tabela, campos (tamanho base)
12px / 400 — Labels de campo, textos de apoio
3. Componentes
Botões<Button>
Ver código — uso real (@solutio/design-system)
<Button variant="emphasized">Emphasized</Button>
<Button variant="default">Default</Button>
<Button variant="transparent">Transparent</Button>
<Button variant="destructive">Destructive</Button>
<Button disabled>Disabled</Button>
<Button variant="emphasized" loading>Salvando…</Button>
Status<StatusIndicator>
AtivoInativoEm andamentoInformação
Ver código — uso real (@solutio/design-system)
<StatusIndicator variant="success" label="Ativo" />
<StatusIndicator variant="error" label="Inativo" />
<StatusIndicator variant="warning" label="Em andamento" />
<StatusIndicator variant="info" label="Informação" />
Indicador de carregamento<BusyIndicator>
Ver código — uso real (@solutio/design-system)
<BusyIndicator size="small" />
<BusyIndicator size="medium" />
<BusyIndicator size="big" />
Campo de entrada<FormField>
Ver código — uso real (@solutio/design-system)
<FormField label="Nome" defaultValue="" placeholder="Digite para filtrar" />
<FormField label="Código (imutável)" value="ABC-123" immutable />
3.1 Barra de filtros<FilterBar>
Ver código — uso real (@solutio/design-system)
<FilterBar onApply={() => {}}>
  <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>
3.2 Tabela — última coluna ">"<DataTable>

busy · onCreate + createLabel · onRefresh · onNavigate · onSelect

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

Selecionados: nenhum

navigateIcon customiza o ícone da coluna (sempre última, sempre largura fixa):

onNavigate · navigateIcon="apps"

Projetos (ícone customizado) (2)
Nome
Status
Projeto Alpha
Ativo
Projeto Beta
Em andamento
Ver código — uso real (@solutio/design-system)
<DataTable
  title="Projetos"
  columns={[{ key: 'nome', label: 'Nome' }, { key: 'status', label: 'Status' }]}
  rows={rows}
  rowKey={(row) => row.id}
  busy={busy}
  onCreate={() => {}}
  createLabel="Criar Projeto"
  onRefresh={() => {}}
  onNavigate={() => {}}
  onSelect={(row) => { /* ... */ }}
/>
3.2b Paginação (growing)<DataTable>

Modo cliente — 34 itens já em memória, DataTable fatia sozinho por pageSize (default 10):

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

Projetos (paginação cliente) (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

Modo servidor — onLoadMore simulado com setTimeout; DataTable nunca fatia/acumula sozinho. loadingMore exibe um BusyIndicator real, não um spinner reimplementado:

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

Projetos (paginação 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
Ver código — uso real (@solutio/design-system)
{/* Modo cliente: rows já está inteiro em memória, DataTable fatia sozinho por pageSize */}
<DataTable title="Projetos" columns={columns} rows={todosOsProjetos} rowKey={(row) => row.id} pagination />

{/* Modo servidor: presença de onLoadMore muda o modo — rows é só a página atual */}
<DataTable
  title="Projetos"
  columns={columns}
  rows={paginaAtual}
  rowKey={(row) => row.id}
  totalCount={total}
  pagination
  onLoadMore={buscarProximaPagina}
  hasMore={paginaAtual.length < total}
  loadingMore={carregando}
/>
3.2c Ordenar e agrupar (DataTableFeatureDialog)<DataTable><DataTableFeatureDialog>

Clique no cabeçalho de uma coluna para abrir o DataTableFeatureDialog. DataTable nunca reordena/reagrupa rows sozinho — este exemplo (o "consumidor") aplica a ordenação e o agrupamento escolhidos ao 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:

onSortChange + sortBy/sortDirection · onGroupChange + groupBy

Sem agrupamento

Projetos (clique no cabeçalho) (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
Ver código — uso real (@solutio/design-system)
<DataTable
  title="Projetos"
  columns={columns}
  rows={rowsOrdenadas}
  rowKey={(row) => row.id}
  sortBy={sort?.key}
  sortDirection={sort?.direction}
  onSortChange={(key, direction) => setSort({ key, direction })}
  groupBy={groupBy}
  onGroupChange={setGroupBy}
/>
3.2d Correção — coluna de navegação encostada na borda<DataTable>

columns[].width em todas as colunas · onNavigate

Projetos (colunas com width) (2)
Nome
Status
Projeto Alpha
Ativo
Projeto Beta
Em andamento
Ver código — uso real (@solutio/design-system)
{/* Antes: com todas as colunas dimensionadas, o ícone de navegação se afastava da borda.
   Agora: DataTable injeta uma célula spacer sem width, protegendo a coluna de navegação. */}
<DataTable title="Projetos" columns={[{ key: 'nome', label: 'Nome', width: '200px' }, { key: 'status', label: 'Status', width: '160px' }]} rows={rows} rowKey={(row) => row.id} onNavigate={() => {}} />
3.3 Barra de navegação<NavBar>
Ver código — uso real (@solutio/design-system)
<NavBar
  groups={[
    { id: 'grupo-a', nome: 'Grupo A', active: true, onSelect: () => {} },
    { id: 'grupo-b', nome: 'Grupo B', active: false, onSelect: () => {} },
  ]}
  overflowOpen={overflowOpen}
  onToggleOverflow={() => {}}
/>
3.3b Barra de navegação — variante surface<NavBar>

Navegação por âncora, não troca de painel: as seções abaixo ficam todas renderizadas; clicar rola até a seção correspondente, e o item "ativo" é decidido por um IntersectionObserver olhando o scroll do container abaixo — não pelo clique em si.

Filiais

Conteúdo de exemplo da seção "Filiais". Ela nunca é desmontada — só entra/sai de vista.

Centros de custo

Conteúdo de exemplo da seção "Centros de custo". Ela nunca é desmontada — só entra/sai de vista.

Ver código — uso real (@solutio/design-system)
// onSelect rola até a seção — não troca painel, NavBar não sabe de "seções"
<NavBar
  variant="surface"
  groups={[
    { id: 'filiais', nome: 'Filiais', active: activeKey === 'filiais', onSelect: () => scrollToSection('filiais') },
    { id: 'centros-custo', nome: 'Centros de custo', active: activeKey === 'centros-custo', onSelect: () => scrollToSection('centros-custo') },
  ]}
  overflowOpen={overflowOpen}
  onToggleOverflow={() => setOverflowOpen((o) => !o)}
  // showOverflowButton default já é 'auto' em variant="surface" — não precisa passar
/>

// activeKey vem de um IntersectionObserver nas seções da página, não do clique —
// mesmo padrão de CompanyDetailPage.jsx (tccloud-web). O NavBar já rola sozinho
// seu scroller horizontal pra manter o item ativo visível (comportamento padrão).
3.4 Menus<AvatarMenu><OverflowMenu><AppsMenuPopover>
Ver código — uso real (@solutio/design-system)
{/* Sem prop "open" — o pai controla a visibilidade montando/desmontando */}
<div style={{ position: 'relative' }}>
  <AvatarMenu
    userName="Maria Silva"
    items={[{ key: 'logout', label: 'Sair', onSelect: () => {} }]}
    onClose={() => setAvatarMenuOpen(false)}
  />
</div>
3.5 Componente de seleção múltipla<MultiSelectDropdown>
Ver código — uso real (@solutio/design-system)
<MultiSelectDropdown
  searchValue={search}
  onSearchChange={setSearch}
  options={[
    { value: 'a', label: 'Opção A', checked, onToggle: () => {} },
    { value: 'b', label: 'Opção B', checked, onToggle: () => {} },
  ]}
  open={open}
  onToggleOpen={() => setOpen((o) => !o)}
/>
3.6 Diálogo de ajuda de valor<ValueHelpInput><ValueHelpDialog>
Cliente
Relatório
Selecione um relatório
Ver código — uso real (@solutio/design-system)
<ValueHelpInput
  label="Cliente"
  selected={selected}
  rowKey={(row) => row.id}
  renderChip={(row) => row.nome}
  onRemove={(row) => setSelected((sel) => sel.filter((s) => s.id !== row.id))}
  onOpen={() => setDialogOpen(true)}
/>

<ValueHelpDialog
  open={dialogOpen}
  title="Selecionar cliente"
  columns={[{ key: 'nome', label: 'Nome' }]}
  rows={rows}
  rowKey={(row) => row.id}
  selected={selected}
  onToggleRow={(row) =>
    setSelected((sel) =>
      sel.some((s) => s.id === row.id) ? sel.filter((s) => s.id !== row.id) : [...sel, row],
    )
  }
  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)}
/>
3.6b Diálogo de ajuda de valor — seleção única<ValueHelpInput><ValueHelpDialog>
Fornecedor
Ver código — uso real (@solutio/design-system)
<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([])}
  onOk={() => setDialogOpen(false)}
  onCancel={() => setDialogOpen(false)}
/>
3.7 Ícone central<Icon>
chevron-left
chevron-right
chevron-down
close
check-circle
error-circle
warning-triangle
info-circle
value-help
apps
Ver código — uso real (@solutio/design-system)
import { Icon } from '@solutio/design-system';

<Icon name="chevron-right" size={16} />
3.8 Estado vazio/erro de página<EmptyState>
Nenhum projeto encontrado
Crie o primeiro projeto do workspace para começar.
Ver código — uso real (@solutio/design-system)
<EmptyState
  title="Nenhum projeto encontrado"
  subtitle="Crie o primeiro projeto do workspace para começar."
  actionLabel="Criar projeto"
  onAction={() => {}}
/>
Seleção única<Select>
Ver código — uso real (@solutio/design-system)
<Select
  label="Status"
  options={[{ value: 'active', label: 'Ativo' }, { value: 'draft', label: 'Rascunho' }]}
  defaultValue="active"
/>
Data<DatePicker>
Ver código — uso real (@solutio/design-system)
<DatePicker label="Data de início" defaultValue="" />
Texto multilinha<TextArea>
Ver código — uso real (@solutio/design-system)
<TextArea label="Observações" defaultValue="" />
3.10 Feedback efêmero<ToastProvider><useToast>
Ver código — uso real (@solutio/design-system)
const { showToast } = useToast();

<Button onClick={() => showToast({ message: 'Salvo com sucesso!', variant: 'success' })}>
  Disparar toast
</Button>
3.11 Barra superior<ShellBar>

Preview dentro do card só para esta página — na aplicação real fica fixo no topo (ver o topo desta própria página).

Ver código — uso real (@solutio/design-system)
<ShellBar
  title="TC Cloud"
  avatarInitials="MG"
  logo={<img src="/brand/logoIcon.png" alt="Solutio TCCloud" style={{ height: 28 }} />}
  onLogoClick={() => {}}
  onTitleClick={() => {}}
  onAvatarClick={() => {}}
  onSearchClick={() => {}}
  onHelpClick={() => {}}
  onNotificationsClick={() => {}}
/>
3.12 Tile do launchpad<Tile>
Sem link
cursor: help
Com next/link
Navegação client-side
Fase futura
Ainda não disponível
Fase futura
Ver código — uso real (@solutio/design-system)
<Tile title="Sem link" subtitle="cursor: help" titleHint="Não clicável" />
<Tile title="Com next/link" subtitle="Navegação client-side" href="/" linkComponent={Link} />
<Tile title="Fase futura" subtitle="Ainda não disponível" futurePhase />
3.13 Cartão de conteúdo<Card>
Projetos
Workspace atual

3 projetos ativos

Ver código — uso real (@solutio/design-system)
<Card title="Projetos" subtitle="Workspace atual" action={<Button variant="transparent">Ver todos</Button>}>
  <p>3 projetos ativos</p>
</Card>
3.14 Faixa de aviso<Banner>
Este registro está pendente de aprovação.
Ver código — uso real (@solutio/design-system)
{bannerVisible && (
  <Banner variant="warning" onClose={() => setBannerVisible(false)}>
    Este registro está pendente de aprovação.
  </Banner>
)}
3.15 Radio de status<StatusRadioGroup>
Status
Registros inativos não aparecem em buscas
Ver código — uso real (@solutio/design-system)
<StatusRadioGroup value={status} onChange={setStatus} hint="Registros inativos não aparecem em buscas" />
3.16 Toggle de configuração<Switch>
Ver código — uso real (@solutio/design-system)
<Switch label="Sincronização automática" checked={checked} onChange={setChecked} />
3.17 Chip<Chip>
workspace.readworkspace.write
neutralinfosuccesswarningerror
Ver código — uso real (@solutio/design-system)
<Chip label="workspace.read" onRemove={() => {}} />
<Chip label="success" variant="success" />
<Chip label="warning" variant="warning" />
<Chip label="error" variant="error" />
Painel colapsável<CollapsibleGroup>
Permissões(2)
fiscal:readfiscal:write
Ver código — uso real (@solutio/design-system)
<CollapsibleGroup title="Permissões" count={2}>
  <Chip label="fiscal:read" />
  <Chip label="fiscal:write" />
</CollapsibleGroup>
3.18 Cabeçalho de página de detalhe<ObjectPageHeader>
Projeto
Projeto AlphaAtivo
Ver código — uso real (@solutio/design-system)
<ObjectPageHeader
  kicker="Projeto"
  title="Projeto Alpha"
  status={{ variant: 'success', label: 'Ativo' }}
  onSave={() => {}}
  onCancel={() => {}}
/>
3.19 Abas de página de objeto<ObjectPageTabs>
Ver código — uso real (@solutio/design-system)
<ObjectPageTabs
  tabs={[{ key: 'geral', label: 'Geral' }, { key: 'membros', label: 'Membros' }]}
  active={activeTab}
  onChange={setActiveTab}
/>
3.20 Diálogo de confirmação<ConfirmDialog>
Ver código — uso real (@solutio/design-system)
<ConfirmDialog
  open={open}
  title="Confirmar exclusão"
  message="Esta ação não pode ser desfeita. Deseja continuar?"
  onConfirm={() => setOpen(false)}
  onCancel={() => setOpen(false)}
/>
3.21 Combo de busca de seleção única<SearchSelectInput>
Ver código — uso real (@solutio/design-system)
<SearchSelectInput
  options={options}
  query={query}
  onQueryChange={setQuery}
  selected={selected}
  onSelect={setSelected}
  onAdd={() => { /* adiciona "selected" a uma lista de chips */ }}
/>
3.22 Painel de detalhes<DetailPanel>
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
Ver código — uso real (@solutio/design-system)
<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" /> }],
  ]}
/>
3.23 Painel de detalhes + navegação fixável<DetailPanelNav>

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. NavBar variant="surface" é full-bleed (alcança a borda real da viewport): não funciona dentro de um container com overflow diferente de visible, por isso este demo não usa um quadro de scroll local.

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.

Ver código — uso real (@solutio/design-system)
<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)"
/>
4. Espaçamento e medidas
Grid e medidas
  • Grid base 4px — escala 4 / 8 / 12 / 16 / 24 / 32 / 48.
  • Altura da shell bar: 44px. Barra de navegação horizontal: 44px.
  • Altura de linha de tabela: 40px (compact) / 44px (cozy, padrão). Cabeçalho: 44px.
  • Altura de campo e botão: 36px. Raio de canto: 4px em cartões, campos e botões.
  • Tile: 176 × 176px, gap de 12px. Padding de página: 24px 32px.
  • Sombra de popover: 0 8px 24px rgba(0,0,0,0.24). Sem sombra em cartões (usar borda).