Padrões de UI — Frontend (SSOT)
Documento mestre dos padrões visuais e de composição de componentes do frontend OLP. Regra de ouro: quando um componente da biblioteca (src/components/ui/*) precisa de ajuste consistente, corrija na primitiva, nunca caso a caso no consumer.
1. Princípios
- shadcn/Radix como base. Toda primitiva visual vive em
src/components/ui/*. Consumers compõem, não reescrevem. - Correção na raiz. Se um padrão (ex: altura, espaçamento, cor) precisa valer em todo o sistema, ajuste o componente base — não o cliente.
- Override consciente. Consumers podem sobrescrever via
classNameapenas com justificativa clara (ex: lista que precisa de mais altura por agrupamento). Documentar inline. - Tokens semânticos. Cores via tokens HSL (
bg-primary,text-foreground), nunca cores diretas (bg-blue-500).
2. Select / Dropdown — max-h-[200px]
Regra: todo <SelectContent> exibe no máximo ~6 itens (200px) antes de ativar scroll interno.
// src/components/ui/select.tsx — SelectContent
className={cn(
"... max-h-[200px] ...",
className,
)}Por quê: listas longas (ex: 10+ olimpíadas) "vazam" o layout do modal pai. Cap fixo garante UX previsível e sem regressões em qualquer modal/popover.
Cálculo: SelectItem (~32px com py-1.5 + text-sm) × 6 + Viewport p-1 (8px) ≈ 200px.
Override autorizado:
<SelectContent className="max-h-[280px]"> {/* +2 itens — listas com agrupamento */}Anti-padrão:
{/* ❌ Decidir altura ad-hoc em cada consumer (gera inconsistência) */}
<SelectContent className="max-h-72">
<SelectContent className="max-h-[350px]">
<SelectContent> {/* sem cap → vaza layout */}3. Tooltips em primitivas Radix — Proibido
Não envolver SelectItem, DropdownMenuItem ou similares com <TooltipTrigger>. Radix exige controle interno de ref/foco; o wrapper de Tooltip quebra a navegação por teclado e gera warning cannot be given refs.
Alternativa: usar title="..." HTML nativo no próprio item.
Memória relacionada:
mem://ui/radix-primitive-composition-restriction
4. Cabeçalhos de Seção (TutorialModal)
O container que envolve <title + subtítulo> e <TutorialModal /> deve usar items-start (não items-center) para alinhar o botão "Como usar" ao topo do título — cria linha horizontal harmoniosa.
{/* ✅ Correto */}
<div className="flex items-start justify-between">
<div>
<h1>Agenda</h1>
<p className="text-muted-foreground">Subtítulo...</p>
</div>
<TutorialModal slug="..." />
</div>5. Modais (Dialog)
- Usar
<Dialog>da biblioteca; nunca rolar manual. - Conteúdo extenso: aplicar
max-h-[80vh] overflow-y-autonoDialogContentinterno (não no body). hideCloseButtonapenas em fluxos onde o fechamento é controlado por ação de negócio (ex: confirmação obrigatória).
6. Toast — olpToast
Sempre olpToast (@/lib/olp-toast). Proibido sonner direto, toast() cru ou alert(). Em onError: getUserFriendlyError(error) antes do toast — nunca error.message.
7. Loading / Anti-Flash
Todo componente com fetch+cache deve guardar com:
if (isLoading && !data) return <Skeleton />;Evita flash quando a query é re-buscada em background com cache pré-populado.
Referência completa:
ATOMIC_RENDERING.md
8. Tokens Visuais — Referência Rápida
| Contexto | Token / Classe |
|---|---|
| Background app | bg-background |
| Texto principal | text-foreground |
| Texto secundário | text-muted-foreground |
| Card | bg-card text-card-foreground |
| Ação primária | bg-primary text-primary-foreground |
| Ação destrutiva | bg-destructive text-destructive-foreground |
| Header de tabela (Controle) | bg-blue-400 (decisão de negócio — coordenador) |
| Borda padrão | border-border |
| Foco | focus-visible:ring-ring |
9. Espaçamento Vertical Global
Container de página (src/App.tsx): pt-3 / pb-3 no header de novidades para reduzir gap exagerado. Conteúdo interno usa space-y-6 por padrão.
10. Quando criar/atualizar este documento
- Nova primitiva em
src/components/ui/*→ adicionar seção aqui. - Decisão visual aplicada em ≥3 telas → padronizar e documentar aqui.
- Override de regra existente → registrar exceção com justificativa nesta seção.
Referências cruzadas:
CODING_STANDARDS.md— padrões gerais de códigoATOMIC_RENDERING.md— renderização atômicamem://ui/select-dropdown-max-height-standard— regra do cap em Selectmem://ui/radix-primitive-composition-restriction— restrições de composição Radix