# GLOBAL STYLE GUIDE — IDeia > Versão 1.12.0 · 2026-11-04 · Vale para todos os sistemas da IDeia, em qualquer stack. > **Arquivo gerado** pelo build a partir de `apps/site/guide/` no repositório ideia-design-system. Não edite aqui: mude a seção e rode `pnpm build:site`. > Guia visual: https://design.ideiabusiness.com.br · Demonstração ao vivo: https://design.ideiabusiness.com.br/live · MCP: https://design.ideiabusiness.com.br/mcp --- ## Como usar este guia - **Para quem:** as IAs e as pessoas que criam ou alteram qualquer tela de um sistema da IDeia. **Leia antes de criar ou alterar uma tela, um componente, um botão.** Toda vez, não só na primeira. - **Demonstração ao vivo:** https://design.ideiabusiness.com.br/live mostra um sistema de exemplo com todos os componentes e padrões deste guia funcionando: menus, formulários, tabelas, filtros, kanban, rodapé, login, troca de produto, tema e idioma, e os estados de rede (lenta e com erro). Na dúvida sobre um comportamento, veja lá. - **Escopo:** sistemas (painéis e aplicações com login). Landing pages e sites institucionais não seguem este guia. - **Neutro de stack:** este guia diz como cada coisa é e como se comporta, não como se escreve em React, Vue, Flutter ou HTML puro. Cada projeto tem um `docs/DESIGN.md` local que mapeia este guia para a biblioteca dele: qual componente faz o quê, como se instala, como os tokens são declarados. O local nunca contradiz o global; ele só diz como implementar. Projeto na **stack padrão do front** (shadcn/ui + Tailwind, Parte 4) usa a Parte 4 como mapa, e o `DESIGN.md` local só para o que é específico dele. - **Precedência:** este guia > `DESIGN.md` local > o jeito de uma tela antiga. Na dúvida entre o guia e uma tela existente, vale o guia. Mudar uma regra daqui é decisão humana, nunca da IA. - **Quatro partes:** o **Style Guide** (o visual: logo, cores, tipografia, ícones, espaçamento), o **UI Kit** (os componentes: como cada um é, variantes e estados), a **Pattern Library** (os padrões: layout, navegação, formulários, tabelas, feedback, i18n, acessibilidade, texto) e a **Implementação de referência** (como tudo isso vira código na stack padrão do front: shadcn/ui + Tailwind). - No fim há um **checklist**: a tela só está pronta quando passa nele. --- ## Princípios 1. **Mesma experiência em todos os produtos.** A cor primária e o logo mudam de produto para produto. Todo o resto é igual: neutros, semânticas, tipografia, ícones, espaçamento, componentes e comportamento. 2. **Menos é mais.** Layout limpo, espaço em branco generoso, uma ação principal por contexto. 3. **Tudo vive numa box.** Nenhum conteúdo solto sobre o fundo da página: tabela, formulário, lista, KPI, estado vazio e erro ficam dentro de uma superfície elevada. 4. **Hierarquia dentro da box:** título > descrição > conteúdo > ação secundária. A tela em si não tem título: o nome dela é o do breadcrumb. 5. **Feedback para toda ação.** Carregando, deu certo, deu errado, nada aqui: cada estado tem um componente e um lugar. 6. **Mobile-first e container-first.** A tela nasce no celular e cresce. Dentro do shell, o componente responde à largura do seu contêiner, não da janela. 7. **Acessível por padrão.** Contraste mínimo, foco visível, nome acessível em todo controle, teclado em tudo. 8. **Texto curto.** Uma frase por aviso. O usuário decide com o que lê, não estuda. 9. **Tema claro e escuro, sempre.** Nenhuma cor fixa na tela: só tokens. 10. **Preparado para três idiomas.** Nenhum texto fixo no código: tudo vem de dicionário. --- # Parte 1 — Style Guide ## 1.1 Marca e logo Cada produto tem o seu logo e a sua cor primária. Os logos oficiais ficam no **CDN da IDeia** (`cdn.ideiabusiness.com.br`) e se enviam pelo painel administrativo, neste padrão de caminho: ```text https://cdn.ideiabusiness.com.br/logos//-clara.png logo completo, colorido, para o tema claro https://cdn.ideiabusiness.com.br/logos//-escura.png logo completo, todo branco, para o tema escuro https://cdn.ideiabusiness.com.br/logos//-icone-clara.png só o símbolo, quadrado, colorido, para o tema claro https://cdn.ideiabusiness.com.br/logos//-icone-escura.png só o símbolo, quadrado, branco, para o tema escuro https://design.ideiabusiness.com.br/logos/index.json o índice: quais arquivos cada produto tem, com largura, altura e bytes ``` - **O logo troca sozinho.** Um envio novo pelo painel grava na mesma URL e limpa o cache do CDN; o navegador pode mostrar o anterior por até uma hora. Pela API dos produtos, a URL vem com `?v=` da versão atual, e a troca aparece na hora. Os endereços antigos, em `design.ideiabusiness.com.br/logos/…`, redirecionam para o CDN. - **Do CDN direto para o navegador:** ``, ou `` no Next. Sem o `unoptimized`, o otimizador de imagens da Vercel baixa cada logo e entra no uso cobrado do projeto. Nunca copie o arquivo para dentro do projeto: ele perderia a troca. - O `slug` é o nome do produto em minúsculas, sem acento, com hífen no lugar de espaço: `rede-ideia`, `ideia-chat`, `ideia-devtasks`, `ideia-signer`, `lapidai`. - **O sistema confere que os arquivos existem antes de usar**, pelo `index.json` ou pelo MCP (`get_logo_urls`). Produto sem a versão escura usa a clara nos dois temas; sem o ícone, usa o logo completo; nunca inventa um logo nem escreve o nome do produto em texto no lugar dele. - **Famílias:** Rede Ideia e os produtos IDeia (Business, Academy, Benefits, Signer, Pay, Partner, Sales, Publi, MEI, DevTeam, Timestamp, Client, NF) compartilham o navy `#153261` e o verde `#26c148`. IDeia Chat e MultiChat usam o azul `#265e9f`. Os demais (DevTasks, Agenda, Vault, Cockpit, Lapid.ai, Arsenal, Polaria, Plure, Lumoria, MDE, B2B, Conecta RI, DEIA) têm cor própria. | Onde | Qual versão | Tamanho | |---|---|---| | Login, painel da marca | logo completo na versão escura (branca), canto superior esquerdo do painel | 32px de altura | | Sidebar expandida, no topo | logo completo, na primeira linha do topo, com a empresa abaixo (2.15) | 32px de altura | | Sidebar recolhida em ícones | ícone | 32px de altura, a mesma do logo completo | | Favicon e ícone de app | ícone | o que a plataforma pedir | Regras: - **Na sidebar, o logo completo e o ícone têm a mesma altura, 32px**, e o topo não muda de altura ao recolher *(decisão do usuário, 2026-10-26)*. O logo fica numa linha de 48px, alinhado à esquerda com o avatar da empresa logo abaixo. - O logo já traz o nome do produto: **nunca** escreva o nome ao lado dele. - Área de respiro de pelo menos metade da altura do logo em volta. Largura mínima do logo completo: 120px. - Nunca distorça, recolora, rotacione, aplique sombra ou ponha sobre fundo que não seja o `background` ou o `sidebar` do tema. - No tema escuro entra a versão escura (branca). Nunca o logo colorido sobre fundo escuro. - O logo não muda de cor com a primária. Quem deriva da cor do logo é o token, nunca o contrário. ## 1.2 Cores As cores são **tokens** com papel fixo. A tela nunca usa um valor de cor: usa o papel. Tokens vêm em pares: a superfície e o `-foreground`, que é a cor do texto e dos ícones sobre ela. ### Neutros (iguais em todos os produtos) No claro, neutros frios, com leve viés azul (slate). No escuro, grafite sem matiz: o tema "Modern Minimal" do tweakcn.com, só no escuro *(decisão do usuário, 2026-10-26)*. Do tweakcn vêm só os neutros: a primária, as semânticas, o `secondary` e o `accent` são nossos, e a sombra é a do shadcn. | Token | Papel | Claro | Escuro | |---|---|---|---| | `background` | fundo da página; só o vazio entre as boxes | `#f8fafc` | `#171717` | | `foreground` | texto e ícones padrão | `#0f172a` | `#e5e5e5` | | `card` / `card-foreground` | superfície elevada: card, box, tabela, formulário | `#ffffff` / `#0f172a` | `#262626` / `#e5e5e5` | | `popover` / `popover-foreground` | superfície flutuante: menu, popover, modal, toast | `#ffffff` / `#0f172a` | `#262626` / `#e5e5e5` | | `sidebar` | menu lateral e painel da direita | `#ffffff` | `#171717` | | `muted` / `muted-foreground` | superfície sutil e texto de menor peso (descrição, placeholder, ajuda) | `#f1f5f9` / `#475569` | `#1f1f1f` / `#a8a8a8` | | `secondary` / `secondary-foreground` | ação secundária preenchida | `#f1f5f9` / `#0f172a` | `#404040` / `#e5e5e5` | | `accent` | hover de item de menu e de linha; igual ao `muted` | `#f1f5f9` | `#1f1f1f` | | item ativo do menu | `primary` cheia com texto `primary-foreground` *(2026-10-22: a 12% o texto não ficava nítido no escuro)* | = `primary` | = `primary` | | `border` | bordas e separadores | `#e2e8f0` | `#404040` | | `input` | borda dos controles de formulário (campo, select, checkbox); 3:1 contra card e fundo | `#8592a6` | `#737373` | | `ring` | anel de foco | = `primary` | = `primary` | | `link` | texto de link (a variante `link` do botão, link dentro de texto) | = `primary` (escurecida só se não chegar a 4,5:1) | a primária clareada até 4,5:1 contra fundo, card, popover e muted *(decisão do usuário, 2026-10-26)* | No escuro, `card` e `popover` são mais claros que o `background`; a `sidebar` é igual ao fundo, separada pela borda. O `muted` fica entre os dois. No claro, card, popover e sidebar são brancos sobre o cinza-claro da página, e a borda separa. Se faltar separação, ajusta-se o token, nunca o componente. O que não vem do tweakcn, e por quê: - `secondary` escuro `#404040`: no tweakcn é a cor do card, e o botão secundário sumiria sobre ele. - `accent` = `muted`, nos dois temas: o hover do shadcn é `bg-muted` (botão ghost e outline, toggle, item do Command), e os itens de menu e de select usam `bg-accent`. Iguais, todo hover tem a mesma cara, e o `muted-foreground` continua com 4,5:1 sobre a linha em hover. O do tweakcn é azul. - `muted-foreground` escuro `#a8a8a8`, um degrau acima do `#a3a3a3` do tweakcn: o placeholder fica sobre o fundo do campo do shadcn (`dark:bg-input/30`), e ali o `#a3a3a3` dava 4,3:1. ### Primária e destaque (as únicas cores que mudam por produto) | Token | Papel | |---|---| | `primary` / `primary-foreground` | a ação principal, o item ativo do menu, o foco, o avatar sem foto, o contador do sino | | `link` | todo texto que é link. Nunca `primary` como cor de texto: no escuro, ela tem 3:1, e texto precisa de 4,5:1 | | `accent-brand` | a segunda cor do logo, quando existe; só decoração (gráfico, ilustração, detalhe). Nunca texto, nunca fundo de botão | | `brand` | a primária **clara**, fixa nos dois temas. Só o painel do login (2.20) a usa | **Como derivar a primária do logo.** O logo nunca muda; o token deriva dele. - **Claro:** a cor dominante do logo. Se o texto branco sobre ela não chega a **4,5:1**, escurece-se o mesmo matiz até chegar. - **Escuro:** o mesmo matiz, clareado até ter **3:1** contra o `card` escuro (`#262626`). O texto sobre ela é branco se der 4,5:1; senão, é o `background` escuro (`#171717`). - `primary-foreground` é branco no claro. No escuro, branco ou `#171717`, conforme a regra acima. - O script `apps/site/scripts/derive-product-colors.py` aplica a receita e lê o card de `packages/tokens/base.json`. Mudou o tema escuro, roda-se com `--escuro`: recalcula só a primária escura de todos os produtos. Valores já calculados (os outros produtos seguem a mesma receita): | Produto | Cor do logo | `primary` claro | `primary` escuro | texto sobre ela no escuro | |---|---|---|---|---| | Rede Ideia e família IDeia | `#153261` | `#153261` | `#2e6cd2` | branco | | IDeia Chat / MultiChat | `#265e9f` | `#265e9f` | `#2e71c0` | branco | | IDeia DevTasks | `#396cae` | `#396cae` | `#3c71b6` | branco | | Ideia Agenda | `#004ffb` | `#004ffb` | `#1b62ff` | branco | | Ideia Vault | `#027771` | `#027771` | `#02817b` | branco | | Ideia Cockpit | `#fd7f01` | `#b65b01` (escurecida: a original dá 2,2:1) | `#fd7f01` | `#171717` | | Lapid.ai | `#1f216e` | `#1f216e` | `#6265d3` | branco | Produto cujo matiz primário coincide com uma semântica (o laranja do Cockpit, do Arsenal e da Polaria é o matiz da atenção) mantém as semânticas como estão: o ícone carrega o significado, e a primária aparece só em ação principal, menu e foco. ### Semânticas (iguais em todos os produtos) | Token | Significado | Ícone fixo | Claro | Escuro | |---|---|---|---|---| | `success` | deu certo; estado bom; ligado | `circle-check` | `#15803d` | `#4ade80` | | `warning` | atenção: aconteceu ou vai acontecer algo com consequência | `triangle-alert` | `#b45309` | `#fbbf24` | | `danger` | erro, recusa, ação destrutiva; desligado | `circle-alert` | `#b91c1c` | `#f87171` | | `info` | contexto neutro: o que vale, o que muda depois | `info` | `#0369a1` | `#38bdf8` | O `-foreground` das quatro (texto sobre a cor cheia) é branco no claro e `#171717` no escuro. Cada semântica tem ainda um tom **`-strong`**, só para **texto e ícone sobre a superfície tingida** (badge e alerta). A cor base perde contraste quando ganha uma tinta por baixo: o `success` sobre a própria tinta de 12% dá 4,27:1 no claro, abaixo do mínimo *(achado pela auditoria automática da demonstração, 2026-10-23)*. | Token | Claro | Escuro | |---|---|---| | `success-strong` | `#166534` | `#86efac` | | `warning-strong` | `#92400e` | `#fcd34d` | | `danger-strong` | `#991b1b` | `#fca5a5` | | `info-strong` | `#075985` | `#7dd3fc` | Sobre a tinta de 12%, no pior fundo (card, muted ou página), o texto forte fica entre 5,5:1 e 6,2:1 no claro e entre 6,7:1 e 8,4:1 no escuro. Três intensidades, e só estas: - **Texto e ícone no tom:** a cor direto no texto ou no ícone. - **Superfície tingida:** fundo com a cor a 12% (badge), a 8% com borda a 30% (alerta) ou a 10% (botão de perigo, o `destructive` do shadcn; 20% no escuro), texto e ícone no tom **`-strong`**. É o padrão para badge de status, alerta e botão de perigo. - **Superfície cheia:** fundo na cor, texto no `-foreground`. Só para selo de destaque. Não existe quinto tipo. Não se usa a primária com sentido semântico, nem uma semântica como decoração. ### Contraste - Mínimo **4,5:1** para texto, **3:1** para texto grande (≥ 24px, ou ≥ 19px em peso 600), ícone, borda de controle e contorno. Nos dois temas. - **Quem cumpre o 3:1 da borda de controle é o token `input`** (campo, select, checkbox), contra o `card` e o fundo: claro `#8592a6` (3,15:1 no card, 3,01:1 no fundo), escuro `#737373` (3,19:1 e 3,78:1). O `border` dos separadores pode ser suave: não é ele que mostra que ali é um campo *(decisão do usuário, 2026-10-26)*. - O build confere os pares de cada tema e de cada produto (texto 4,5:1; `input`, primária e ícone 3:1) e falha se algum sair do mínimo. - **Contraste se corrige onde nasce.** Se falta no valor de um token, corrige-se o token (decisão humana), nunca um remendo numa tela só. Se falta por uma opacidade que a tela pôs, a tela tira a opacidade. - Texto que carrega informação (descrição, erro, ajuda) nunca leva opacidade. O esmaecido vai no rótulo, no ícone e no controle desabilitado. ### Cor que vem do banco A cor que o usuário escolhe para uma fila, etiqueta, categoria ou avatar é a única cor que não é token. Ela entra por variável, nunca escrita na tela, e **toda marca com cor do banco leva um contorno de 1px em `foreground` a 20%**: a cor pode ser quase branca ou quase preta, e o contorno garante que ela apareça nos dois temas. Nunca se troca a cor do usuário por tema. ### Gráficos Séries na primária e em degraus dela (mais clara e mais escura); neutros para o que é referência. Semântica só quando o dado é semântico (vencido em `danger`, no prazo em `success`). Cada série precisa de 3:1 contra o `card` do tema. Rótulos no `muted-foreground`, grade na `border`. ## 1.3 Tipografia - **Fonte: Roboto** em títulos e corpo, em todos os produtos. Pesos: 400 (texto, item de menu, célula, tooltip), 500 (rótulo, botão, badge, cabeçalho de tabela, título de box), 600 (título de seção e título grande), 700 (só número de KPI e a vitrine do login). São os pesos dos componentes do shadcn: não se muda o peso de um componente. - **Roboto Mono** só para bloco de código e citação literal do sistema: log, payload, ID, hash, chave. Nunca em texto corrido, rótulo ou tabela comum. - **Tamanho base do sistema: 14px.** Controles, menus, células e descrições são 14px. O texto digitado num campo tem 16px abaixo de 768px de janela, para o iPhone não ampliar a tela ao focar (o shadcn já faz: `text-base md:text-sm`). - Números em coluna ou em KPI usam dígitos tabulares. - Nunca um tamanho fora da escala. Nunca outra fonte, nem por CSS, nem por imagem com texto. | Papel | Tamanho / linha | Peso | Onde | |---|---|---|---| | Legenda | 12 / 16 | 400 | tooltip, ajuda curta, rodapé de tabela, hora (o badge usa 12 em 500) | | Corpo do sistema | 14 / 20 | 400 | controle, célula, menu, descrição, parágrafo de tela | | Rótulo e botão | 14 / 20 | 500 | label de campo, botão, cabeçalho de tabela | | Leitura | 16 / 24 | 400 | mensagem de chat, e-mail, texto longo | | Título de box | 16 / 22 | 500 | título de card, de modal, de folha lateral (o `CardTitle` e o `DialogTitle` do shadcn) | | Título de seção | 20 / 28 | 600 | seção dentro de uma tela longa, título de página de login | | Título grande | 24 / 32 | 600 | login, onboarding, telas fora do shell | | Número de KPI | 30 / 36 | 700 | o valor de um card de KPI | | Vitrine | 36 / 44 | 700 | só a frase do produto no painel do login (2.20) | Hierarquia dentro de uma box: título (16/500) › descrição (14, `muted-foreground`) › conteúdo (14) › ação secundária. A tela não tem título nem subtítulo: o nome dela é o do breadcrumb, e o `h1` existe só para leitor de tela. ## 1.4 Ícones - **Biblioteca: Lucide**, em todos os produtos (existe para web, React, Vue, Svelte, Flutter e SVG puro). Traço de 2px, cantos arredondados, sem preenchimento. Nenhuma outra biblioteca, nenhum emoji como ícone. - Tamanhos: o componente do shadcn dimensiona o ícone. **16px** no tamanho padrão (botão de qualquer altura a partir do padrão, campo, célula, alerta, item de menu e da sidebar, estado vazio), **14px** no botão `sm` e no separador do breadcrumb, **12px** no botão `xs` e no badge. **20px** só solto, ao lado de um título de seção. A tela não passa tamanho dentro de botão ou campo. - O ícone acompanha a cor do texto onde está. Ícone com cor própria só nas semânticas e no indicador de estado. - Ícone nunca vai sozinho sem nome acessível: botão só de ícone tem `aria-label` e tooltip com o mesmo texto. | Contexto | Ícone | |---|---| | Campo e-mail · senha · nome · busca | `mail` · `lock` · `user` · `search` | | Adicionar · editar · excluir · voltar · tentar de novo · fechar | `plus` · `pencil` · `trash-2` · `arrow-left` · `refresh-cw` · `x` | | Sucesso · erro · atenção · informação | `circle-check` · `circle-alert` · `triangle-alert` · `info` | | Ações da linha ("…") | `ellipsis` | | Abrir o menu do usuário | `chevrons-up-down` | | Recolher a sidebar · notificações | `panel-left` · `bell` | | Tema claro · tema escuro · idioma | `sun` · `moon` · `languages` | | Perfil · configurações · sair | `user` · `settings` · `log-out` | | Colunas da tabela · filtro | `columns-3` · `filter` | | Carregando | não é ícone: Skeleton ou Spinner (UI Kit) | ## 1.5 Espaçamento, tamanhos, raio e elevação **Os componentes têm as medidas do shadcn/ui, estilo `nova`, e o guia não muda nenhuma** *(decisão do usuário, 2026-10-26)*. As tabelas abaixo dizem quais são, para conferir uma tela e para quem implementa em outra stack. Dentro de um componente valem os valores dele (6, 10, 20px…); **o layout da tela** (margens, distância entre boxes, entre botões, entre colunas) usa a **escala de 4px: 4, 8, 12, 16, 24, 32, 48**. | Onde | Valor | |---|---| | Padding da página (dentro do shell) | 24px no desktop, 16px no celular | | Entre boxes (cards, seções) | 16px | | Padding de card | 16px (12px no card `sm`) | | Padding de modal e de folha lateral | 16px | | Entre cabeçalho, conteúdo e rodapé de card e de modal | 16px | | Entre campos de formulário | 20px (grupo dentro de grupo: 16px) | | Entre o rótulo e o controle, e o controle e a ajuda | 8px | | Entre botões numa barra | 8px | | Botão: padding horizontal · ícone até o texto | 10px · 6px (`sm` e `xs`: 4px; `xs` com 8px de padding) | | Campo e select: padding horizontal | 10px | | Célula de tabela | 8px | | Item de menu, de select e de lista de opções | 4px × 6px | | Elemento | Altura / largura | |---|---| | Controle padrão (botão, campo, select, toggle) | 32px | | Controle `sm` | 28px | | Controle `lg` | 36px; não se usa nas telas: numa barra, tudo tem a altura padrão | | Controle `xs` (dentro de outro componente: chip removível, botão dentro do campo) | 24px | | Botão só de ícone | quadrado na altura do tamanho: `icon` 32, `icon-sm` 28, `icon-lg` 36, `icon-xs` 24 | | Área de texto | no mínimo 64px; cresce com o texto | | Checkbox · rádio · switch | 16px · 16px · 32 × 18px (`sm` 24 × 14) | | Badge | 20px de altura | | Item de menu suspenso e de select | 28px | | Cabeçalho de tabela | 40px | | Item do menu da sidebar | 32px (`sm` 28; `lg` 48, o do topo e o do usuário) | | Header da tela | 64px (48px com a sidebar recolhida) | | Sidebar expandida · recolhida · gaveta do celular | 256px · 48px · 288px | | Painel da direita | 256px | | Card de login | até 384px de largura | | Tela de configuração sem tabela | até 720px de largura, centralizada (3.1) | | Modal | 384px (o padrão) · 512px · 672px, largura máxima (2.13) | | Confirmação (diálogo de alerta) | 384px; 320px no celular | | Folha lateral | 3/4 da tela, até 384px | | Popover · menu suspenso | 288px · no mínimo 128px | | Tooltip | até 320px | | Avatar | 32px (`sm` 24, em lista densa; `lg` 40, em cabeçalho de perfil); 96px só na foto do Perfil (2.19) | | Logo na sidebar (completo e ícone) · topo da sidebar | 32px de altura, aberta e recolhida · duas linhas de 48px (logo e empresa), sem mudar ao recolher | Não existe tamanho `xl`. Altura, padding, raio e tamanho de texto de componente **não se ajustam por `className`** (`h-10`, `px-4`, `text-base`…): se uma medida parecer errada, quem muda é o guia. **O tamanho se escolhe pela classe, nunca por valor à mão** *(decisão do usuário, 2026-10-26)*. No shadcn, é a prop `size` do componente (`size="sm"`; sem a prop, o padrão). Fora dele (outra stack, a página do guia, a demonstração ao vivo), as alturas vêm de uma escala só, gerada dos tokens no `tokens.css`: `--size-xs` (24), `--size-sm` (28), `--size-default` (32) e `--size-lg` (36), mais `--size-item` (28, item de menu) e `--size-sidebar-lg` (48). O componente lê a altura dessa escala, e a tela só escolhe a classe (`.btn`, `.btn-sm`…). Nenhum `height` escrito num elemento ou numa regra de tela. **Raio:** um valor só, `--radius: 0.5rem` (8px), igual em todos os produtos. Os outros saem dele pela escala do shadcn, que multiplica: | Classe | Raio | Onde | |---|---|---| | `rounded-lg` (o raio) | 8px | botão, campo, select, toggle, alerta, popover, menu suspenso | | `rounded-xl` (× 1,4) | 11px | card, modal, diálogo de alerta, lista de comandos, estado vazio | | `rounded-md` (× 0,8) | 6px | item de menu, de select e da sidebar, tooltip, skeleton, botão `sm` e `xs` | | 4px | 4px | checkbox | | `rounded-full` | redondo | badge, avatar, switch, rádio, bolinha de cor | **Elevação:** plana por padrão. - **Box** (card, tabela, formulário, painel): fundo `card`, **contorno de 1px em `foreground` a 10%** (`ring-1 ring-foreground/10`, como o shadcn desenha o card), **sem sombra**. A cor `border` fica para separadores, linhas de tabela e a borda da sidebar. - **Flutuante** (menu, popover, select): fundo `popover`, o mesmo contorno e a sombra média (`shadow-md`: `0 4px 6px -1px` e `0 2px 4px -2px`, preto a 10%, nos dois temas). A folha lateral usa `shadow-lg`. - **Modal e confirmação:** contorno, sem sombra, sobre um véu preto a 10% com desfoque leve. - Nada mais tem sombra. Profundidade vem dos tokens de superfície, não de sombra. ## 1.6 Tema claro e escuro - **Os dois temas são obrigatórios** em todo sistema. Toda tela é feita e conferida nos dois. - A escolha fica no **menu do usuário**, na linha "Tema", com o Sol (claro) e a Lua (escuro). A escolha é guardada por usuário e vale em toda sessão seguinte. - Tema inicial de quem nunca escolheu: **claro**, ou o do sistema operacional quando a stack permitir sem complicação. - O tema troca sem piscar e sem animação. Nenhum componente decide o que renderizar pelo tema: tudo é token, e o token responde ao tema. - Imagens que dependem do tema (o logo) têm as duas versões e a tela mostra a certa. ## 1.7 Movimento - Controles (hover, foco, toggle): 150ms, a transição padrão do Tailwind. - Flutuantes: as animações do próprio shadcn, sem edição. Modal, menu, popover e select aparecem em 100ms (opacidade e escala de 95% a 100%); a folha lateral desliza em 200ms; o tooltip aparece em 150ms. - Nada anima na troca de tema. Nada anima em loop, exceto o Spinner e o pulsar do Skeleton. - Quem pede movimento reduzido no sistema operacional não vê animação nenhuma. --- # Parte 2 — UI Kit Cada componente abaixo é canônico: toda tela usa o componente da biblioteca do projeto que corresponde a ele (o `DESIGN.md` local diz qual). A ordem para qualquer coisa nova é: (1) um componente do kit, (2) uma composição de componentes do kit, (3) um componente próprio, só quando nada disso existe, e montado com os do kit. HTML puro fica para a estrutura do layout, nunca para controle interativo. ## 2.1 Botão | Variante | Visual | Uso | Quantos por contexto | |---|---|---|---| | `primary` | fundo `primary`, texto `primary-foreground` | a ação principal do contexto | **1** | | `secondary` | fundo `secondary` | ação de apoio preenchida | vários | | `outline` | borda `border` e fundo `background` (no escuro, borda `input` e fundo `input` a 30%) | ação de apoio, filtros, "Editar" | vários | | `ghost` | sem fundo nem borda; `muted` no hover | cancelar, voltar, botão só de ícone em barra | com moderação | | `link` | texto no token `link` (a primária; no escuro, mais clara, 1.2), sublinhado no hover | navegação dentro de texto | com moderação | | `danger` | tinta de `danger` (10%; 20% no escuro) com o texto em `danger-strong` | só ação destrutiva, só depois de confirmação ou dentro dela | 1 | - Tamanhos, os do shadcn: `xs` 24px, `sm` 28px, padrão 32px, `lg` 36px. **Não existe `xl`.** Só de ícone: quadrado na mesma altura (`icon-xs`, `icon-sm`, `icon`, `icon-lg`). **Padrão em tudo, inclusive no "Novo…"**: botões, campos e filtros de uma mesma barra têm a mesma altura. `sm` e `xs` só dentro de outro componente. `lg` não se usa nas telas dos sistemas. - Ícone à **esquerda** do rótulo, 16px (14 no `sm`, 12 no `xs`), a 6px do texto (4 no `sm` e no `xs`). Ícone à direita só para "abre algo" (seta, chevron, link externo). - O `danger` é a tinta do `destructive` do shadcn com o texto no tom `-strong`: com o texto em `danger` puro, o escuro fica abaixo de 4,5:1 (1.2). - **Rótulo é verbo no infinitivo:** "Salvar", "Excluir fila", "Nova fila". Nunca "OK", "Sim", "Clique aqui". - **Botão só de ícone:** `aria-label` **e** tooltip com o mesmo texto. Sem exceção. - **Carregando:** desabilitado, com o Spinner no lugar do ícone e o rótulo mantido ("Salvar", não "Salvando…"). - Desabilitado: 50% de opacidade, sem hover, cursor padrão. Desabilitado não some: fica visível com tooltip explicando por quê, quando o motivo não é óbvio. - Link com cara de botão é um link com o estilo do botão. Nunca um link dentro de um botão. - **"Novo…" (criar) fica dentro do card da lista que ele alimenta, no canto superior direito** *(decisão do usuário, 2026-10-26)*: `primary`, no `size` padrão do `Button` (sem a prop `size`, 32px), da mesma altura da busca, dos filtros e dos outros botões da barra *(o usuário reviu a D5: em `lg`, ficava desalinhado deles)*; ícone `plus`, o único primário do card. Card com título: nas ações do cabeçalho (`CardAction`). Card sem título: no fim da primeira linha da barra de ferramentas, depois da busca e dos filtros (3.5). Abaixo de 40rem de largura do card, vira botão só de ícone (`icon`) com `aria-label` e tooltip ("Nova fila"). Nunca no header nem numa barra acima do card (3.3). - Não existe botão "Atualizar": os dados se atualizam sozinhos (3.8). "Tentar de novo" continua nos estados de erro. ## 2.2 Campo de texto e área de texto Anatomia, de cima para baixo, com 8px entre as partes: **rótulo** (14/500, sempre ligado ao controle) › **controle** (32px, borda `input`, raio 8, 10px de padding, fundo transparente; no escuro, `input` a 30%) › **ajuda** (14, `muted-foreground`) ou **erro** (14, `danger`, com a borda do controle em `danger` e um anel de 3px em `danger` a 20%). - Placeholder é exemplo do formato ("dd/mm/aaaa", "nome@empresa.com.br"), nunca o rótulo. - Campo opcional diz "Opcional" na ajuda. Obrigatório não leva asterisco. - Ícone dentro do campo, à esquerda, 16px, `muted-foreground` (e-mail, senha, busca). Ação dentro do campo (mostrar senha, limpar) à direita, como botão só de ícone `ghost` `icon-xs` (24px). - Área de texto: altura mínima de 64px; cresce com o texto. - Foco: a borda vira `ring` e ganha um anel de 3px em `ring` a 50%, colado ao controle. Erro: borda `danger` e mensagem; a mensagem diz o que fazer ("Informe um número maior que zero."). - Campo de busca: ícone `search` à esquerda, placeholder "Buscar ", largura máxima de 320px numa barra. ## 2.3 Select, combobox e data - **Select:** mesma anatomia e altura do campo (32px; `sm` 28), chevron à direita, itens de 28px, até 8 opções. Acima disso, **combobox** com busca. Seleção múltipla mostra chips dentro do controle, cada chip com o seu "×" nomeado. - **Data:** campo digitável no formato do idioma (`dd/mm/aaaa` em pt-BR) com o ícone `calendar` dentro do campo, à direita, que abre o calendário. No calendário, **mês e ano são dropdowns**, nunca só setas. Data pela metade não é erro enquanto se digita: quem valida é o envio. - Período: dois campos de data lado a lado ("De", "Até") ou um atalho de período (Hoje, 7 dias, 30 dias, Personalizado). Como filtro, é o controle "Período" (2.22) e mora na linha de filtros do card de relatório (2.24). Dashboard não tem período: o recorte é fixo (3.1). ## 2.4 Checkbox, rádio e switch - **Checkbox** para escolha múltipla e para "aceito". Rótulo à direita, clicável. - **Rádio** para escolha única entre 2 e 5 opções visíveis. Acima disso, select. - **Switch** para ligar e desligar algo **com efeito imediato** (uma configuração que salva sozinha). Dentro de um formulário que tem "Salvar", é checkbox, não switch. - Checkbox e rádio têm 16px (a área de clique do checkbox é maior que o desenho); o switch tem 32 × 18px (`sm` 24 × 14). Os três com foco visível e o estado marcado na `primary`. ## 2.5 Badge - Pílula de 20px de altura (raio redondo), 12/500, 2px × 8px de padding, ícone de 12px à esquerda quando há, a 4px do texto. - Variantes: `neutral` (fundo `muted`), `primary` (cheia), `success`, `warning`, `danger`, `info` (tingidas a 12%, texto e ícone no tom `-strong`; ver 1.2). - **Badge de status sempre leva ícone e texto**, para não depender só da cor: "● Ativa" com `circle-check` em `success`; "Inativa" com `circle-x` em `danger`. - Contador (não lidos, pendências): badge na `primary` com o número, 20px de altura e no mínimo 20px de largura. No menu da sidebar, o `SidebarMenuBadge` (20px, 12/500). ## 2.6 Alerta A caixa que fica na tela até a condição mudar. Quatro tipos, e o tipo escolhe o ícone e a cor; a tela não escolhe nenhum dos dois. | Tipo | Quando | |---|---| | `success` | deu certo e precisa ficar à vista. Se é passageiro, é toast | | `danger` | erro ou recusa: a ação não aconteceu | | `warning` | aconteceu ou vai acontecer, e tem consequência que a pessoa precisa saber | | `info` | contexto neutro: o que está valendo, o que muda depois | - Anatomia: ícone fixo 16px › texto (14). Título (14/500) só quando a mensagem precisa de manchete; com uma frase, só a descrição. - Fundo tingido a 8%, borda a 30%, texto e ícone no tom `-strong` (1.2). Raio 8, padding 8 × 10. - Pode ficar dentro de um modal: é o lugar da recusa do servidor numa confirmação. - **Texto: uma frase**, com o que a pessoa precisa para decidir. - Quando NÃO é alerta: o que fica à vista enquanto uma condição vale (modo do compositor, conexão caída) é **indicador de estado**, não alerta: variante neutra, `role="status"`, ícone próprio. ## 2.7 Toast Aviso passageiro, no canto inferior direito (no celular, embaixo, largura total). - Anatomia: ícone do tipo › **título de até 5 palavras** › descrição opcional de uma linha › "×". Ação opcional ("Ver", "Desfazer") como botão `ghost` `sm`. - Some em **6 segundos**; erro fica até fechar. Pausa enquanto o mouse está sobre ele. No máximo 3 ao mesmo tempo; o mais antigo sai. - Toast é para o que **não precisa ficar**: "Fila criada", "Alterações salvas". O que precisa ficar é alerta. Erro de formulário nunca é toast: vai no campo ou acima do rodapé. - Leitor de tela anuncia o toast de erro na hora e os outros quando puder. ## 2.8 Tooltip - Toda dica ao passar o mouse ou focar é tooltip: fundo `foreground` e texto `background` (invertido: escuro no claro, claro no escuro), 12/400, 6px × 12px, raio 6, até 320px de largura. Abre sem atraso, como o `TooltipProvider` do shadcn vem. - **Nunca o atributo nativo `title`**, nem junto do tooltip, nem sozinho. - Obrigatório em todo botão só de ícone, com o mesmo texto do `aria-label`. Atalho de teclado na dica vai num `kbd`. - Botão desabilitado não dispara tooltip: envolve-se num elemento que dispara. - Tooltip não guarda informação necessária: o que a pessoa precisa saber está na tela. ## 2.9 Card e box - A superfície que embala todo conteúdo: fundo `card`, contorno de 1px em `foreground` a 10%, raio 11 (`rounded-xl`), padding 16 (12 no card `sm`), sem sombra. É o `Card` do shadcn como vem. - Anatomia opcional, com 16px entre as partes: **cabeçalho** (título 16/500 e descrição 14 `muted-foreground`; ação do cabeçalho no canto direito: o "Novo…" da lista, 2.1, ou as ações do registro no detalhe) › **conteúdo** › **rodapé** (faixa com borda em cima e fundo `muted` a 50%; ações alinhadas à direita, ou contagem à esquerda e "Carregar mais" à direita). - **Card de KPI:** rótulo (14, `muted-foreground`) em cima, valor (30/700, tabular) embaixo, variação opcional como badge. No dashboard, uma linha de 12 `muted-foreground` diz o recorte e contra o que a variação compara ("Últimos 30 dias, contra os 30 anteriores", 3.1). Grade de 1 coluna no celular, 2 a partir de 28rem de contêiner, 3 ou 4 a partir de 42rem. - Card clicável inteiro tem hover (`muted` leve) e foco visível; e tem uma ação só. ## 2.10 Tabela de dados Toda listagem tabular é uma tabela de dados completa, nunca uma `` solta. **Toda listagem tem o padrão completo** *(decisão do usuário, 2026-10-26)*: busca, colunas ordenáveis, paginação, "…" de ações e os estados (skeleton, vazio, erro). Lista curta não é desculpa para faltar nenhum. Anatomia, dentro de um card: 1. **Barra de ferramentas** (acima da tabela): busca à esquerda (máx. 320px), **um filtro por coluna de conjunto fechado** ao lado (até três à vista, o resto em "Filtros", 3.5), e à direita o menu "Colunas" e, no fim, o **"Novo…"** (`primary`, na altura padrão, como o resto da barra), o único primário do card; com título no card, o "Novo…" vai nas ações do cabeçalho (2.1). 2. **Cabeçalho:** 40px, 14/500 em `foreground`. **Toda coluna de valor único é ordenável** (abaixo). Número alinhado à direita, com dígitos tabulares. 3. **Linhas:** 14, padding 8, separadas por `border`, hover em `muted` a 50%. Status como badge com ícone. Cor do banco como bolinha com contorno. 4. **Ações da linha:** todas num único botão "…" (`ellipsis`) na última coluna, `ghost` `icon-sm`, com `aria-label` e tooltip "Ações", que abre um menu: itens com ícone; o que desliga ou apaga vem por último, depois de um separador; o que pede confirmação abre o diálogo a partir do item. Nunca botões soltos na linha. A exceção é a primeira etapa da listagem em duas etapas (3.18): a ação que leva à segunda etapa é um botão com o verbo ("Ver acessos ›"). 5. **Rodapé:** "N de M linhas" à esquerda; "Linhas por página", página atual e setas à direita. Lista paginada por cursor no servidor não pagina no cliente: mostra o que veio e "Carregar mais" no rodapé do card, ao lado de "N carregados". **Ordenação** *(decisão do usuário, 2026-10-26)*: - **Toda coluna de valor único ordena**, inclusive telefone, e-mail, código e data. Não ordenam: a coluna de ações, a de seleção (caixa de marcação), coluna com vários valores na célula (etiquetas, avatares) e texto livre longo (descrição, observações). Coluna de vários valores com um número natural ordena por ele: "Atendentes" ordena pela quantidade. - O título do cabeçalho é um botão: um clique ordena crescente, outro, decrescente. O `th` leva `aria-sort` (`ascending` ou `descending` na coluna ordenada, `none` nas outras ordenáveis, nada nas que não ordenam), e o ícone (`arrow-up` ou `arrow-down`, 14px) aparece só na coluna ordenada. - Texto pela ordem alfabética do idioma, sem diferença de acento e de caixa; número e data pelo valor; status, prioridade e etapa pela ordem do conjunto (Ativa antes de Inativa, Baixa até Urgente, Entrada até Concluído), não pelo alfabeto. Vazio vai para o fim, nos dois sentidos. - A tabela abre numa ordem que o cabeçalho mostra (em geral a coluna principal, crescente). A ordem vive na URL, como os filtros (3.5), e mudá-la volta à primeira página. - Tela que também tem "Ordenar" na barra (a de tarefas, porque o kanban não tem cabeçalho): na lista, o "Ordenar" e o cabeçalho são a mesma ordenação; escolher num desliga o outro. **Coluna de conjunto fechado vira filtro** *(decisão do usuário, 2026-10-26)*: - Coluna cujo valor vem de um conjunto fechado (Status, Setor, Origem, Prioridade, Tipo, Cargo, Pessoa, Etiquetas…) tem o seu filtro na barra de ferramentas, com o nome da coluna no gatilho. - **Até 5 valores: escolha única**, no select de filtro ("Status: todos"). **Mais de 5, ou quando o normal é combinar valores** (Pessoa, Etiquetas): seletor múltiplo com busca (2.22). - Até três filtros à vista, os mais usados; os excedentes vão para "Filtros" (3.5). - Texto livre (nome, empresa, título) não vira filtro: é da busca. Estados: - **Primeira carga:** linhas de Skeleton no formato da tabela. - **Recarregando** (busca, filtro, ordenação, página): os dados ficam na tela e um Spinner aparece na barra de ferramentas. Nunca na releitura automática, em segundo plano (3.8). - **Vazio:** estado vazio dentro do card (2.12). Com filtro ativo, o texto diz que não há resultado para o filtro e oferece limpar. - **Erro ao carregar:** estado vazio com `circle-alert`, a mensagem e "Tentar de novo". - Busca vai ao servidor com atraso de 300ms; quando o servidor entrega a lista inteira, filtra no cliente, sem atraso. - Largura: a tabela mede o próprio contêiner. Colunas secundárias somem abaixo da largura em que cabem, e o que elas mostrariam desce para a coluna principal. **A página nunca rola para o lado**; se precisar, só a tabela rola dentro do card. ## 2.11 Skeleton e Spinner Duas situações, um componente cada. Nunca um texto "Carregando…" solto. | Situação | Componente | |---|---| | O componente ainda não tem os dados para aparecer (primeira carga de tabela, card, conversa, menu) | **Skeleton**, no formato do conteúdo final: mesmas alturas, larguras e colunas, dentro da box | | O componente já está na tela e uma informação está sendo buscada ou processada (salvar, buscar, enviar, carregar mais) | **Spinner**, 16px, no botão ou na barra de ferramentas | - Skeleton: fundo `muted` pulsando devagar (o `animate-pulse` do shadcn), raio 6; larguras fixas, nunca aleatórias. - Spinner nunca substitui um componente inteiro, e nunca aparece na releitura periódica em segundo plano, na do foco da janela ou na releitura depois de uma ação. - Toda chamada a servidor tem um dos dois. Nenhuma tela fica muda esperando. ## 2.12 Estado vazio Dentro da box, centralizado, com 24px de padding: ícone 16px num quadrado de 32px em `muted`, raio 8 (o `EmptyMedia variant="icon"`) › **título** (14/500: "Nenhum contato ainda") › **descrição** (14 `muted-foreground`, uma frase: "Cadastre o primeiro para começar.") › **ação** (botão `primary` com o verbo: "Novo contato"), quando a pessoa pode agir. No card de uma lista, o "Novo…" já está no canto (2.1): a ação do vazio é `outline`, para o card não ter dois primários. - Primeiro uso e "nada encontrado" são textos diferentes. Com filtro: "Nenhum resultado para 'x'" e a ação é "Limpar filtros" (`outline`). - Erro ao carregar usa o mesmo formato, com `circle-alert` e "Tentar de novo". ## 2.13 Modal (diálogo) - Flutuante: fundo `popover`, contorno de 1px em `foreground` a 10%, raio 11, padding 16, sem sombra, sobre um véu preto a 10% com desfoque leve. Larguras máximas: **384px**, o padrão do shadcn (até 4 campos numa coluna); **512px** (`sm:max-w-lg`) para o formulário de até 8 campos em duas colunas; **672px** (`sm:max-w-2xl`) só para conteúdo largo. No celular, ocupa a largura toda com 16px de margem, e um formulário longo vira folha de baixo para cima. - Anatomia, com 16px entre as partes: **título** (16/500) e **descrição** (14 `muted-foreground`) à esquerda, "×" no canto (`ghost` `icon-sm`, `aria-label` "Fechar", tooltip) › **conteúdo** › **rodapé**: faixa com borda em cima e fundo `muted` a 50%, ações à direita: "Cancelar" (`ghost`) e a ação principal (`primary`, ou `danger` na confirmação destrutiva). No celular, as ações empilham com a principal em cima. - Fecha com "×", "Cancelar", Esc e clique fora. Se há formulário com alteração não salva, pergunta antes de descartar. - O foco entra no modal ao abrir e **volta ao botão que o abriu** ao fechar. - Um modal por vez. Modal não abre modal; abre, no máximo, uma confirmação. - Recusa do servidor aparece como alerta `danger` dentro do modal, acima do rodapé, sem fechar. **Confirmação** (diálogo de alerta, 384px; 320px no celular): título em pergunta ("Excluir a fila Retenção?"), uma frase de consequência ("As 12 conversas abertas voltam para a triagem."), "Cancelar" e o botão `danger` com o verbo ("Excluir fila"). Nunca "Sim" e "Não". Ação que dá para desfazer não pede confirmação: executa e oferece "Desfazer" no toast. ## 2.14 Menu suspenso (dropdown) - Flutuante (fundo `popover`, contorno, sombra média), raio 8, padding 4, largura mínima de 128px. Itens de 28px (4px × 6px) com ícone 16px à esquerda a 6px do texto, 14/400, raio 6, hover `muted`. Até 2 níveis. - Grupos separados por linha `border`. Rótulo de grupo 12/500 `muted-foreground`. - Item destrutivo por último, depois de um separador, em `danger`. - Item com atalho mostra o atalho à direita em `kbd`. Item que abre diálogo termina em "…". - Abre com clique ou Enter, navega com setas, fecha com Esc e devolve o foco ao gatilho. ## 2.15 Shell: sidebar, header e painel da direita Toda tela do sistema vive dentro do shell. A tela não monta menu, header nem painel lateral próprios. ```text +------------+------------------------------------------+------------+ | [Logo] | [≡] | Grupo › Tela [●] [?] [✨] [🔔] | Painel | | [EE] Emp. ⇕| | da | | +------------------------------------------+ direita | | GRUPO | | (opcional, | | Item 3 | +------------------------------------+ | só a | | Item | | box | | partir de | | GRUPO | | | | 1024px) | | Item ativo| +------------------------------------+ | | | | +------------------------------------+ | | | | | box | | | | [MS] Nome ⇕| +------------------------------------+ | | +------------+------------------------------------------+------------+ ``` - **Sidebar** (256px, fundo `sidebar`, borda à direita): **topo em duas linhas de 48px**, o logo do produto (32px de altura) e, logo abaixo, o seletor da empresa (componente **Troca de empresa**) *(decisão do usuário, 2026-10-26)*; **grupos** com rótulo (12/500, `sidebar-foreground` a 70%) e itens (ícone 16 + rótulo 14, 32px de altura, raio 6; **ativo em `primary` cheia com texto `primary-foreground`** *(decisão do usuário, 2026-10-22: a 12% o texto não ficava nítido no escuro)*; contador à direita, 20px, que no item ativo fica em `primary-foreground` a 20%); **rodapé** com a entrada da área de administração (2.30, só para quem está como Sysadmin) e o botão do usuário (2.16). A troca de empresa e o botão do usuário são itens de 48px (`SidebarMenuButton size="lg"`). - **Os itens do menu são os do papel ativo** (2.16). Na área de administração (2.30), a sidebar ganha o tom suave da primária e mostra só os itens da área. - Recolhe para **ícones** (48px): só o ícone de cada item, com tooltip; o logo vira o ícone do produto, **com a mesma altura de 32px**; a empresa e o usuário viram só o avatar, com tooltip. **O topo não muda de altura ao recolher**: as duas linhas continuam com 48px *(decisão do usuário, 2026-10-26)*. Estado guardado por usuário. Atalho Ctrl+B (⌘B). - Abaixo de 768px, a sidebar vira **gaveta** de 288px sobre a tela, aberta pelo botão do header. - **Header** (64px, dentro da área de conteúdo): botão de recolher (`panel-left`, `ghost`, tooltip "Recolher menu" com o atalho) › separador vertical › **breadcrumb** "Grupo › Tela" (o grupo é texto, não link; some no header compacto) › à direita, nesta ordem, os indicadores de estado (ex.: "Sem tempo real"), o **?** da ajuda (2.28), o **✨** do assistente (2.29, só nos produtos com IA) e o **sino** *(decisão do usuário, 2026-10-26)*: botões só de ícone `ghost` `icon-sm`, a altura do botão de recolher (numa barra, tudo na mesma altura). **O header é só disso. Nenhuma ação da tela vai nele:** a ajuda, o assistente e o sino são do shell. - **Painel da direita** (256px, fundo `sidebar`, borda à esquerda): informações adicionais da tela (os dados do contato num atendimento). Opcional; só existe a partir de 1024px; sem conteúdo, não aparece. A tela preenche o painel do shell, nunca monta um painel dentro do próprio conteúdo. O **assistente de IA** (2.29) usa este lugar, com 384px: aberto, toma o lugar do painel da tela, que volta quando ele fecha. - **Segundo menu** (224px, fundo `muted`, abaixo do header, à esquerda do conteúdo): só no item cujas seções são telas próprias, como as configurações (2.27). O item continua ativo na sidebar. - O conteúdo começa logo abaixo do header, com padding 24 (16 no celular), sem título e **sem barra de ações** (3.3): a primeira box vem direto. O "Novo…" fica no card que ele alimenta (2.1). ## 2.16 Menu do usuário No rodapé da sidebar, um botão de 48px com **avatar** (32px, iniciais na `primary` quando não há foto), **nome** (14/500) e **e-mail** (12 `muted-foreground`), e o ícone `chevrons-up-down` à direita. Abre um menu flutuante ao lado (embaixo, no celular), nesta ordem: 1. Cabeçalho: avatar, nome, e-mail e, numa terceira linha, o **papel**: "Papel: **Sysadmin**" (12 `muted-foreground`, o papel em `foreground` 500), como no DevTasks. Quem tem **mais de um papel** não vê essa terceira linha: o papel vai para a linha de troca, logo abaixo do cabeçalho (ver **Trocar de papel**). 2. **Perfil** e **Configurações** (ícones `user` e `settings`). Tudo o que é do usuário mora aqui, e sai do menu principal. "Configurações" só aparece para o papel que tem a tela. 3. **Ver como…** (`eye`), **só para o Sysadmin** *(decisão do usuário, 2026-10-26)*. Com o modo ligado, no lugar dele, **Voltar ao meu usuário** (`undo-2`). 4. Separador. 5. Linha **Idioma:** o rótulo à esquerda e, à direita, os três botões em sigla: **PT · EN · ES** (tooltip com o nome: Português, English, Español). Sem bandeira. 6. Linha **Tema:** o rótulo à esquerda e, à direita, o Sol (Claro) e a Lua (Escuro). 7. Separador. 8. **Sair** (`log-out`), por último. Trocar idioma ou tema não fecha o menu. Na sidebar recolhida o botão mostra só o avatar, com tooltip. **Trocar de papel.** Quem tem mais de um papel escolhe com qual está *(decisão do usuário, 2026-10-27)*. O Sysadmin tem todos: Sysadmin, Dev e Suporte. A diferença para o "Ver como": a pessoa continua sendo ela mesma, só com outro dos papéis dela. ```text +--------------------------------+ | [MS] Marcos Silva | | marcos@empresa.com.br | +---------------------------------+ | [id] Papel: Sysadmin ⇄ |->| Trocar de papel | |--------------------------------| | ✓ Sysadmin | | Perfil | | Tudo, com a administração | | Configurações | | Dev | | Ver como… | | Tarefas, relatórios e acesso | ``` - **A linha de troca**, logo abaixo do cabeçalho: um item do menu com o crachá (`id-card`), "Papel:" em `muted-foreground`, o nome do papel em 500 e, à direita, **o ícone de troca (`arrow-left-right`) no lugar da seta do submenu** *(decisão do usuário, 2026-10-27)*. O ícone diz o que a linha faz; ela continua abrindo o submenu ao lado. - **O submenu** "Trocar de papel" abre ao lado da linha (embaixo, no celular). Tem um item por papel: o nome e, embaixo, uma linha com o que o papel vê (12, `muted-foreground`). O papel atual tem o `check`. A seta para a direita abre o submenu; a seta para a esquerda volta. - **Escolher:** o menu fecha e **a tela remonta limpa** no papel novo, como na troca de empresa (2.25). Se o papel novo não tem a tela, vai para o início. Um toast avisa: "Agora você está como Dev" (2.7). Escolher o papel atual só fecha o menu. - **O papel decide o menu e as permissões:** os itens, as telas e as ações são os do papel ativo. A área de administração (2.30) e o "Ver como" só existem como Sysadmin. Para voltar, a mesma linha. - **Sem faixa e sem anel no avatar:** não é um modo de exceção, é a própria pessoa. O papel fica à vista no menu do usuário. - **O papel escolhido fica** guardado por pessoa, no servidor, até a pessoa trocar de novo, também depois de sair e entrar. O servidor revalida tudo pelo papel ativo, e a troca vai para o registro de auditoria. - **Quando a linha some:** quem tem um papel só não vê a troca; a terceira linha do cabeçalho mostra o papel como texto. No "Ver como", a linha também some, e o cabeçalho mostra o papel da pessoa escolhida. **Ver como.** O Sysadmin passa a ver o sistema como outra pessoa, para conferir o que ela vê e reproduzir um problema dela. - "Ver como…" fecha o menu e abre um diálogo (modal padrão, 384px, 2.13): título "Ver como", a consequência numa frase ("O menu e as telas passam a ser os da pessoa escolhida. O que você fizer fica registrado no seu nome.") e a **busca de pessoas** por nome ou e-mail, com a lista logo abaixo (avatar, nome, e-mail e papel; setas e Enter escolhem). Escolher liga o modo; Esc ou "×" desiste. - Com o modo ligado, **o sistema se comporta como a pessoa escolhida**: o botão e o menu do usuário (nome, e-mail, foto e papel dela), o Perfil, os filtros "meus" ("Só o que é meu" passa a ser dela), os contadores, o sino e as preferências. A tela atual remonta limpa, como na troca de empresa. - **A faixa**, fixa acima do header, na largura da coluna de conteúdo *(decisão do usuário, 2026-10-26)*: ```text +------------+------------------------------------------------------------------+ | [Logo] | Você está vendo como Ana Ribeiro [Voltar ao meu usuário] | faixa, 40px | +------------------------------------------------------------------+ | | [≡] | Grupo › Tela atual [sino] | header | | +------------------------------------------------------------+ | | [AR] Ana ⇕| | box | | +------------+------------------------------------------------------------------+ ``` - A faixa tem 40px, fundo `warning` a 12% sobre o `background`, texto em `warning-strong`, borda de baixo em `warning` a 30%, o ícone `eye`, a frase "Você está vendo como **Ana Ribeiro**" e, à direita, **"Voltar ao meu usuário"** (`outline` `sm`, ícone `undo-2`; no celular, só o ícone, com tooltip). Sem "×": só sai pelo botão. `role="status"`, para o leitor de tela anunciar a troca. É a mesma faixa da troca de empresa (2.25), com outro ícone e outro botão; quando as duas valem, uma embaixo da outra, a do "Ver como" primeiro. - O **avatar do rodapé da sidebar** ganha o anel de atenção (2.19) enquanto o modo vale. - **O modo não sobrevive ao "Sair"**: sair encerra a sessão e o "Ver como" junto; quem entra de novo entra como si mesmo. Recarregar a página mantém o modo, como mantém a sessão. - No servidor, a sessão guarda as duas pessoas: as permissões e os dados são os da escolhida, e o registro de auditoria leva quem fez ("Marcos Silva, vendo como Ana Ribeiro"). ## 2.17 Breadcrumb "Grupo › Tela" no header, 14, com o `chevron-right` de 14px como separador. O grupo em `muted-foreground`, sem link; a tela em `foreground` 500. Tela de detalhe: "Grupo › Lista › Item" com a lista como link. Segunda etapa de uma listagem em duas etapas, o mesmo: "Grupo › Tela › Escolha", com a tela como link para a primeira etapa (3.18). Seção de um segundo menu: "Grupo › Item › Seção", com o item como link para a primeira seção (2.27). Encurta com reticências antes de empurrar o que está à direita. ## 2.18 Paginação Rodapé da tabela: "N de M linhas" à esquerda; à direita, "Linhas por página" (select de 32px, 10/25/50), "Página X de Y" e as setas (`outline` só de ícone, 32px, desabilitadas no limite). Abaixo de 24rem de contêiner some a contagem; abaixo de 36rem some o "Linhas por página". ## 2.19 Avatar 32px (`sm` 24, `lg` 40), redondo. Foto, ou as iniciais sobre a `primary` (14; 12 no `sm`). Em grupo, sobrepostos em 8px, com anel de 2px na cor do fundo. Nunca um ícone genérico de pessoa como avatar de usuário real. **Foto do Perfil, 96px.** É o único avatar fora dos tamanhos do shadcn e o único `className` de tamanho permitido nele: `size-24` no `Avatar` e `text-3xl` nas iniciais (`AvatarFallback`). Fica no topo do card "Seus dados" do Perfil, com os botões à direita: - **"Trocar foto"** (`outline` `sm`) abre o seletor de arquivo. Aceita **JPG, PNG ou WebP, até 5 MB**, e a regra fica escrita abaixo dos botões (12, `muted-foreground`). Arquivo fora disso não entra, e a mensagem de erro toma o lugar da regra: "Use uma imagem JPG, PNG ou WebP." ou "A imagem tem mais de 5 MB. Escolha uma menor." - **"Remover"** (`ghost` `sm`), só quando há foto: volta às iniciais. - **Prévia na hora**, no próprio avatar, antes de subir. O **recorte** é o quadrado do centro da imagem, em 256 × 256, mostrado redondo; a pessoa não recorta à mão. - A foto nova, ou a remoção, sobe com o "Salvar" do formulário, como os outros campos; "Cancelar" volta à anterior. Salva, ela aparece em todo lugar onde a pessoa aparece: o botão e o cabeçalho do menu do usuário, as listas, os cards. **Anel de atenção.** Enquanto o "Ver como" vale (2.16), o avatar do rodapé da sidebar ganha um anel de 2px em `warning`, afastado 2px na cor da sidebar (`ring-2 ring-warning ring-offset-2 ring-offset-sidebar`). ## 2.20 Login Fora do shell, em **duas colunas** *(decisão do usuário, 2026-10-22: o padrão é o do DevTasks, não mais o card centralizado)*. ```text +-------------------------------+--------------------------------------+ | [logo branco] | | | | Bem-vindo de volta | | | Entre na sua conta para continuar | | Frase do produto | | | (até duas linhas) | E-mail [__________________] | | Descrição em uma frase | Senha Esqueci minha senha | | | [__________________] | | | [ Entrar ] | | | Problemas para entrar? Fale com… | | |--------------------------------------| | | Os negócios por trás… Quem está ↗ | | | Mais de 20 anos empreendendo, mais… | | | [‹] [▣ Produto ↗] [▣ Produto ↗] [›] | | ✦ recurso ✦ recurso ✦ rec. | Um produto da Ideia Business · … | +-------------------------------+--------------------------------------+ ``` - **Painel da marca** (esquerda, metade da largura a partir de 1024px): fundo em degradê de 135° da `brand` (a primária clara do produto) para ela 25% mais escura. **Sempre colorido, nos dois temas**, com texto branco. Logo na versão escura (branca) no canto superior esquerdo, 32px de altura. No centro vertical, a **frase do produto** (Vitrine, 36/44/700, até duas linhas) e uma descrição (16/24, branco a 85%). No rodapé do painel, até três recursos do produto com o ícone `sparkles` (14/500). Sem imagem de fundo, sem ilustração: o degradê é o painel. - **Coluna do formulário** (direita): fundo `background` do tema; formulário centralizado na vertical, até 384px: título "Bem-vindo de volta" (24/600) › "Entre na sua conta para continuar" (14 `muted-foreground`) › campo E-mail com `mail` › campo Senha com o link "Esqueci minha senha" (`link` `sm`) **à direita do rótulo, na mesma linha**, e o "mostrar senha" dentro do campo › botão `primary` de largura total, no tamanho padrão, como nos blocos de login do shadcn ("Entrar") › linha de apoio (12 `muted-foreground`): "Problemas para entrar? Fale com contato@ideiabusiness.com.br". - **Rodapé** (2.21) **na coluna da direita, embaixo do formulário, e já estendido** *(decisão do usuário, 2026-10-27: na largura toda, debaixo das duas colunas, ficou feio)*: produtos à vista, carrossel passando, sem a seta. O painel da marca ocupa a altura toda à esquerda; a página só rola quando não cabe. **No celular**, o rodapé vem recolhido, com a seta, e abre como folha de baixo, para não empurrar o formulário *(D3, decisão do usuário, 2026-10-26)*. - Enviando: botão desabilitado com Spinner. Erro de credencial: alerta `danger` acima do botão, uma frase, sem dizer qual dos dois campos está errado. - **Abaixo de 1024px:** o painel vira uma faixa no topo, com o logo e a frase; o formulário vem abaixo, em largura total. **Abaixo de 640px:** a faixa fica só com o logo. - Recuperar senha, primeiro acesso e código de verificação usam a mesma estrutura, trocando só o conteúdo da coluna do formulário. - O logo sem nada escrito ao lado. Os recursos são do produto, não da empresa. ## 2.21 Rodapé Todo sistema tem o mesmo rodapé *(decisão do usuário, 2026-10-22: o do DevTasks ocupava espaço demais; o padrão passa a ser uma linha, e os produtos da Ideia Business aparecem pela seta)*. ```text Recolhido (toda tela): ─────────────────────────────────────────────────────────────────────────────────────────────── Um produto da Ideia Business Razão social · CNPJ contato@ideiabusiness.com.br [⌃] Estendido (clique na seta; no login, já vem assim e sem a seta): ═══════════════════════════════ borda em cima, sombra só para cima ════════════════════════════ Os negócios por trás deste produto Quem está por trás ↗ Mais de 20 anos empreendendo, mais de 500 mil empresas atendidas e uma rede com mais de 5 mil… [‹] [▣ IDeia DevTasks ↗] [▣ IDeia Chat ↗] [▣ IDeia Signer ↗] [▣ NF IDeia ↗] [▣ Polaria ↗] [›] Um produto da Ideia Business Razão social · CNPJ contato@ideiabusiness.com.br [⌄] ``` - **Uma linha de 40px** no fim da coluna de conteúdo de **toda** tela, depois da última box. Rola com a página; nunca fixa, nunca cobrindo conteúdo. Borda superior em `border`, texto 12 em `muted-foreground`, padding 12 × 24. - Conteúdo, da esquerda para a direita: "Um produto da **Ideia Business**"; a razão social e o CNPJ; o e-mail de contato como texto selecionável; e, no canto direito, **a seta para cima** (`chevron-up`: botão só de ícone `ghost` `sm`, com `aria-label`, `aria-expanded` e tooltip "Produtos da Ideia Business"). - **A seta abre e fecha só no clique** (Enter ou espaço pelo teclado), nunca no hover, com cursor de mão; o `globals.css` gerado já põe `cursor: pointer` em todo botão *(decisão do usuário, 2026-10-26)*. Enquanto aberto, a seta vira `chevron-down`. Fecha pela seta ou com Esc (o foco volta para a seta). Clicar fora não fecha: o rodapé é parte da página, e fechar sozinho faria o conteúdo pular. - **Estendido, é o próprio rodapé crescendo**, não um painel flutuante *(decisão do usuário, 2026-10-26)*. A parte estendida entra **no fluxo da página**, logo acima da linha, na largura do rodapé: o rodapé fica mais alto, a página ganha a altura dele e **rola até mostrar o rodapé inteiro** (`scrollIntoView`, sem animação para quem pede movimento reduzido). **Nada fica por cima do conteúdo** *(decisão do usuário, 2026-10-26: por cima, ele cobria o fim da lista)*. A linha continua embaixo, com a seta. Fundo da página (`background`) *(D4)*; borda superior em `border`; **sem raio e sem borda nos lados**; **sombra só para cima** (a `shadow-md` do Tailwind, invertida); sem véu. - Dentro, de cima para baixo: o título "Os negócios por trás deste produto" (14/500) com **"Quem está por trás ↗"** à direita, na mesma linha, e a frase da empresa (12, uma só) na largura toda; depois, os produtos num **carrossel** de **cards finos** *(decisão do usuário, 2026-10-27: a 1024 × 768, o rodapé aberto ocupava 30% da janela)*: 240 × 54px, e **o card inteiro é o link do produto, que abre em outra aba** (cursor de mão, fundo `muted` no hover), com o ícone do logo (32px), o nome (14/500) e a descrição (12, `muted-foreground`), uma linha cada, e o `external-link` (12, `muted-foreground`) à direita. Sem botão "Conhecer". O leitor de tela ouve "(abre em outra aba)" no fim do nome do card. Uma seta em cada ponta do carrossel (`chevron-left` e `chevron-right`, botões só de ícone `outline` `sm`, com nome acessível), **sem barra de rolagem**, passando sozinho a cada 4 segundos em laço; para enquanto o mouse está em cima ou algo dentro dele tem foco, e não passa sozinho para quem pede movimento reduzido *(decisão do usuário, 2026-10-22)*. A razão social e o CNPJ só ganham uma linha embaixo dos cards quando a linha do rodapé não tem espaço para eles. - **Os textos e os produtos vêm da API** *(decisões do usuário, 2026-10-28 e 2026-10-29)*: `GET https://design-api.ideiabusiness.com.br/api/v1/footer?product=`, sem login. Nada disso fica escrito no código do sistema. - A resposta traz os textos da empresa e os cards. Os textos são o nome da marca, a razão social, o CNPJ, o e-mail, o título, a frase e o link do "Quem está por trás", editados no painel. Os cards vêm em ordem alfabética, sem o do próprio sistema: slug, nome, texto, URL e ícone. - O texto do card é o do cadastro do produto. Card de produto sem endereço sai sem link. - **Em SSG, consultando a API 1 ou 2 vezes por dia** *(decisão do usuário, 2026-10-30)*: o rodapé sai pronto do build e o sistema só volta à API quando a revalidação vence, a cada 12 ou 24 horas (no Next, `fetch` com `next: { revalidate: 43200 }` ou `86400`). Em tela com login, que se monta a cada pedido, o `fetch` vem do cache do Next, não da API. Nunca SSR sem cache (uma chamada por página), nunca do navegador, e nada de `cache: "no-store"`, `revalidate: 0` ou `fetchCache = "force-no-store"` no caminho do rodapé. Uma mudança no painel chega a todos em até um dia. - Com a API fora, o Next continua servindo o último rodapé que leu (o `fetch` só guarda resposta 200); sem nenhum guardado, o rodapé não aparece e a página segue. - **Login:** o rodapé fica **na coluna da direita, embaixo do formulário** (2.20), e **já vem estendido**: produtos à vista, carrossel passando, sem a seta *(decisão do usuário, 2026-10-27: na largura toda, debaixo das duas colunas, ficou feio)*. É o mesmo componente com `estendido`; ali ele faz parte da página e não cobre nada. - **Celular** (abaixo de 768px): a linha vira duas linhas centralizadas e o CNPJ fica só na parte estendida, que vira **folha de baixo** (`Sheet side="bottom"`), com véu, a seta para baixo no topo (fecha) e os cards empilhados; fecha pela seta, com Esc ou tocando fora. **No login pelo celular, o rodapé vem recolhido, com a seta**, para não empurrar o formulário *(D3, decisão do usuário, 2026-10-26)*. - **Tela de altura cheia:** a linha fica abaixo da área que cresce, e os 40px dela entram na conta. Estendido, a área que cresce encolhe para dar lugar a ele (o quadro do kanban fica mais baixo); a página continua sem rolar e nada fica coberto. - Nunca: botão "Conhecer" ou outro link dentro do card do produto (o card é o link); parágrafo institucional, carrossel ou cards de produtos no corpo da tela; rodapé fixo; os textos da empresa ou a lista de produtos escritos no código (vêm da API); abrir no hover; a parte estendida por cima do conteúdo ou com cara de painel flutuante (raio, borda nos lados, fundo de `popover` ou `card`, sombra para baixo, véu no desktop); esconder a linha (o CNPJ precisa estar à vista). O que fica guardado até a seta é só a parte estendida. ## 2.22 Filtro Os controles de filtro são os mesmos em toda tela; o que muda é onde ficam (3.5). | Controle | Quando | Anatomia | |---|---|---| | **Select de filtro** | escolha única, até 5 valores (Status, Setor, Origem, Prioridade) | select de 32px com o rótulo e o valor no gatilho: "Status: todos", "Setor: Vendas". Vazio = "todos" | | **Seletor múltiplo com busca** | mais de 5 valores, ou quando o normal é combinar valores (pessoa, etiqueta, cargo, produto) | gatilho `outline` com o rótulo e o contador ("Pessoa · 2"); aberto: busca no topo, caixas de marcação, opções especiais fixas no alto ("Eu", "Sem responsável"), separador, as demais em ordem alfabética; rodapé com "n marcadas" e "Limpar". Ativo: borda na `primary` e o contador | | **Período** | qualquer filtro por data | um botão `outline` com `calendar` e o que vale ("Últimos 30 dias", "01/10 a 22/10/2026"); aberto: atalhos à esquerda (7 dias, 30 dias, 90 dias, Mês atual, Mês anterior, Este ano, 12 meses) e "De" e "Até" digitáveis à direita; aplica ao mudar | | **Chip de alternância** | atalho liga/desliga que combina com os outros ("Só o que é meu", "Ocultar concluídos", "Compacto") | `Toggle` `outline` de 32px, raio 8; ligado = `primary` cheia (o toggle do shadcn liga em `muted`, sem o contraste de 3:1; ver 4.5). No máximo três por barra | | **Botão "Filtros"** | os filtros que não cabem à vista | `outline` com `filter` e o contador dos ativos ("Filtros · 2"); abre um popover (folha de baixo no celular) com todos os campos no formato de formulário, em 2 colunas, e "Limpar tudo" no rodapé; aplica ao mudar | | **Chip removível** | mostrar e tirar um filtro ativo que não está à vista | botão `secondary` `xs` (24px, 12/500) com "Rótulo: valor" e o `x` à direita; o botão inteiro remove, com nome acessível ("Remover filtro Etiqueta: Bloqueada"); a linha termina em "Limpar (n)" | | **Busca** | texto livre | campo de 32px com `search`, placeholder "Buscar ", até 320px; limpa pelo "×" dentro do campo | - **De onde vêm os filtros de uma tabela:** cada coluna de conjunto fechado tem o seu, com o nome da coluna no gatilho (2.10). Escolha única até 5 valores; múltipla acima disso ou quando o normal é combinar valores, como Pessoa *(decisão do usuário, 2026-10-26)*. - Todo controle de filtro mostra **o rótulo e o valor** no próprio gatilho; nunca só o valor. - Vazio significa "todos": o gatilho diz "Status: todos", não fica em branco. - **Filtro ativo:** borda na `primary` e, no seletor múltiplo e em "Filtros", o contador (badge na `primary`); **o texto do gatilho fica em `foreground`** *(decisão do usuário, 2026-10-26)*: texto na `primary` fica abaixo de 4,5:1 no escuro, e a borda só precisa de 3:1. Não está à vista: aparece na linha de chips. - Aplica ao mudar. Busca e filtro que vão ao servidor esperam 300ms. Sem botão "Aplicar". - Exemplo vivo dos seis controles na página do guia, seção 3.5. ## 2.23 Abas O `Tabs` do shadcn com `variant="line"`, como vem *(decisão do usuário, 2026-10-26)*: lista de 32px sem fundo, 4px entre as abas, rótulo 14/500 em `foreground` a 60% (`muted-foreground` no escuro), a aba ativa em `foreground` com um sublinhado de 2px em `foreground`. Ícone opcional de 16px à esquerda do rótulo. Nada de cor da primária, fundo ou borda por `className`. | | Abas (`variant="line"`) | Controle segmentado (`variant="default"`) | |---|---|---| | Para quê | trocar de visão: cada aba tem o seu conteúdo | trocar o recorte ou o agrupamento do mesmo conteúdo (Dia · Semana · Mês) | | Onde | no topo do card, antes dos filtros (2.24) | junto do que ele muda, dentro do conteúdo (o cabeçalho de um gráfico) | | Quantas | 2 a 6, rótulo de uma ou duas palavras | 2 a 4, rótulo de uma palavra | | Desenho | sem fundo; a ativa sublinhada | trilho em `muted`, a ativa em `background` com sombra leve | - No controle segmentado, o rótulo inativo fica sobre o trilho `muted` (em `muted-foreground` no escuro): os neutros do tema precisam dar 4,5:1 nesse par (1.2). - Os dois são o mesmo componente, com a mesma semântica: `tablist` com nome (`aria-label`: "Visão"), `tab` com `aria-selected` e `aria-controls`, `tabpanel` ligado à aba. - **Teclado**, o do Base UI: Tab entra na aba ativa; as setas movem o foco entre as abas e voltam ao início no fim; Home e End vão às pontas; Enter ou Espaço abrem a aba (`activateOnFocus` desligado, como vem: trocar de aba pode ir ao servidor). Tab de novo segue adiante: os filtros, no card de relatório, ou o painel. - O painel recebe foco (o Base UI dá `tabIndex` 0 ao painel aberto) e o `TabsContent` do shadcn vem com `outline-none`: o anel de foco do guia entra no `tabs.tsx` (nível 3, 4.5), senão quem usa o teclado perde o foco de vista. - A aba aberta vive na URL (`?visao=pessoas`), como os filtros (3.5). Sem nada na URL, a primeira. - As abas não quebram linha: sem espaço, a lista rola na horizontal dentro do card. Nunca mais de uma linha de abas nem abas dentro de abas. Uma visão só não vira aba. - Aba não é navegação: trocar de tela é o menu (3.2), e as seções de uma área (as configurações: Tags, Campos, SLA, Webhooks) vão no segundo menu (2.27). Lista ou Kanban e Compacto são modo de exibição, na barra de ferramentas (3.5), e não abas. ## 2.24 Card de relatório O relatório é **um card autossuficiente** *(decisão do usuário, 2026-10-26)*: tudo o que muda o que ele mostra mora dentro dele. Por isso entra em qualquer tela (a tela Relatórios, o detalhe de um produto, a página de uma equipe) sem depender de barra de ações (3.3). ```text +------------------------------------------------------------+ | Produtos Pessoas Equipe [ação] | abas (2.23) | ‾‾‾‾‾‾‾‾ | | [Últimos 30 dias ▾] [Produto: todos ▾] ◌ | filtros do card | | | Andamento por produto | título da visão | 31 produtos · 13 com entrega vencida | recorte e contagem | ...lista, gráfico ou tabela... | conteúdo +------------------------------------------------------------+ ``` De cima para baixo, com os 16px do `Card` entre as partes: 1. **Abas** (2.23): as visões do relatório. Uma visão só: sem abas, e o título da visão abre o card. Ação do card inteiro (exportar, por exemplo) à direita das abas. 2. **Filtros do card**, logo abaixo das abas: o período (2.22) e o que muda o recorte (produto, fila, pessoa), nos controles de 32px. Aparecem só os que valem para a aba aberta; o período vale para todas e não muda ao trocar de aba. Até três à vista, o resto em "Filtros" (3.5). 3. **Conteúdo** da aba: título da visão (16/500) e descrição (14 `muted-foreground`) com o recorte e a contagem, depois a lista, o gráfico ou a tabela. Estados, todos dentro do card, com as abas e os filtros sempre usáveis: - **Primeira carga**, e aba aberta pela primeira vez: Skeleton no formato do conteúdo (2.11). - **Mudou um filtro, ou voltou a uma aba já vista:** os dados ficam e o Spinner aparece no fim da linha de filtros. - **Erro ao carregar:** o estado de erro no lugar do conteúdo, com "Tentar de novo" (2.12). - **Nada no recorte:** estado vazio ("Nenhuma entrega no período"), com "Limpar filtros" quando há filtro além do período. - Releitura automática: só os números mudam (3.8). Estado na URL: - Aba, período e filtros vivem na URL (`?visao=pessoas&p=90d&produto=ideia-chat`): o link abre o mesmo recorte, e o voltar do navegador desfaz a última troca. Vazio = a primeira aba e o padrão. - **Com mais de um card de relatório na tela, cada um usa um prefixo** (`entregas.visao`, `conversas.p`), para um não mexer no outro. Um só na tela: sem prefixo. - Trocar de empresa zera tudo (3.5). - No celular a linha de filtros quebra linha; as abas não: rolam na horizontal dentro do card. - Dashboard não usa card de relatório: mostra recortes fixos (3.1). Quem precisa filtrar abre um relatório. ## 2.25 Troca de empresa No topo da sidebar, **logo abaixo do logo do produto**, o seletor da empresa em que a pessoa está trabalhando *(decisão do usuário, 2026-10-26: o topo tem duas linhas; o logo é a marca do produto e o seletor é o contexto)*. É o `TeamSwitcher` do bloco `sidebar-07` do shadcn. ```text ┌──────────────────────────────┐ │ [logo do produto] │ 48px · logo de 32px de altura │ [EE] Empresa Exemplo ⇕ │ 48px · SidebarMenuButton size="lg" │ 12.345.678/0001-90 │ ├──────────────────────────────┤ │ GRUPO │ ``` - **Botão de 48px** (`SidebarMenuButton size="lg"`): à esquerda, o **avatar da empresa**, um quadrado de 32px com raio 8, fundo `sidebar-primary` e as iniciais (12/500) em `sidebar-primary-foreground` (o logo da empresa, quando ela tem um); ao lado, o **nome** (14/500) e, abaixo, o **CNPJ** (12, na mesma cor, como no `TeamSwitcher`: em `muted-foreground`, sobre o fundo do botão aberto, o escuro fica abaixo de 4,5:1), os dois cortados com reticências; à direita, `chevrons-up-down`. O quadrado distingue a empresa da pessoa, que tem avatar redondo (2.19). - **Três modos**, pelo que a pessoa alcança: | Modo | Quem | Como é | |---|---|---| | **Uma empresa só** | trabalha numa empresa | o mesmo bloco, **sem a seta e sem menu**. Não é botão: não há o que clicar | | **Lista** | trabalha em duas ou mais | menu suspenso (2.14) ao lado do botão (embaixo, no celular): rótulo "Empresas", uma linha por empresa (quadrado de 24px com borda, nome) e a ativa marcada com `check` à direita | | **Busca** | alcança todas as empresas (o Sysadmin) | popover com o campo "Buscar empresa" no topo (nome, razão social ou CNPJ), a lista que o servidor devolve (nome e CNPJ), a ativa marcada, e **"Ver todas as empresas"** no fim, fora da lista, que abre a tela de empresas | - **Por que a busca é popover, e não menu:** campo de texto dentro de menu do Base UI não recebe o que se digita (o menu usa as teclas para pular até o item pela primeira letra). Então o modo busca é um `Popover` com o `Command` dentro, ancorado no mesmo botão, como o seletor múltiplo (2.22). - **Busca no servidor:** espera 300ms depois de parar de digitar (3.5). Primeira carga com skeleton de 3 linhas; enquanto a lista nova vem, a anterior fica e o Spinner aparece no rótulo "Empresas" (2.11). Sem resultado: "Nenhuma empresa encontrada". Erro: o estado vazio de erro com "Tentar de novo" (2.12). A consulta só roda com o popover aberto. - **Sidebar recolhida:** só o avatar da empresa (o botão encolhe para 32px, como no shadcn), com tooltip com o nome; o botão tem nome acessível ("Trocar de empresa: Empresa Exemplo"). A linha continua com 48px, para o topo não mudar de altura (2.15). - **Trocar de empresa:** o menu fecha e o botão mostra o Spinner no lugar da seta até o servidor confirmar. Depois, **a tela remonta limpa**: a mesma tela, sem filtro, busca, página nem linha aberta (3.5, 3.6); num detalhe, volta para a lista, porque o item era da outra empresa. Um toast avisa: **"Você está em Lumen Engenharia"** (2.7). Se falhar, toast de erro e a empresa não muda. Escolher a empresa que já está ativa só fecha o menu. - **Fora da empresa principal** *(decisão do usuário, 2026-10-30)*: quem alcança todas as empresas (o modo busca) tem uma **empresa principal**, a casa do produto (nos sistemas da IDeia, a Rede Ideia). Enquanto está em qualquer outra, uma **faixa fixa** acima do header diz onde a pessoa está e dá o caminho de volta. É a mesma faixa do "Ver como" (2.16), com outro ícone e outro botão: ```text +------------+------------------------------------------------------------------+ | [Logo] | Você está em Lumen Engenharia [Voltar para Empresa Exemplo] | faixa, 40px | [LE] Lumen⇕+------------------------------------------------------------------+ | | [≡] | Grupo › Tela atual [sino] | header | | +------------------------------------------------------------+ | | | | box | | +------------+------------------------------------------------------------------+ ``` - A faixa tem 40px, fundo `warning` a 12% sobre o `background`, texto em `warning-strong`, borda de baixo em `warning` a 30%, o ícone `building-2` (o mesmo do seletor), a frase "Você está em **Lumen Engenharia**" e, à direita, **"Voltar para Empresa Exemplo"** (`outline` `sm`, ícone `arrow-left`, com o nome da empresa principal; no celular, só o ícone, com tooltip). Sem "×": a faixa só sai quando a pessoa volta, pelo botão ou pelo seletor. `role="status"`. - **O botão é uma troca de empresa como outra qualquer:** o Spinner no lugar do ícone até o servidor confirmar, a tela remonta limpa e o toast avisa. A faixa é do shell, não da tela: não remonta, some porque a condição deixou de valer. - Fica na largura da coluna de conteúdo, acima do header e do segundo menu (2.27); as telas de altura cheia descontam os 40px (3.3). Com o "Ver como" ligado ao mesmo tempo, uma faixa embaixo da outra, a do "Ver como" primeiro. - **Não aparece** para quem troca entre as próprias empresas (modo lista): são todas dela, e o seletor já diz qual está ativa. Nem na área de administração (2.30), que vale para todas as empresas. - **Teclado:** Enter, espaço ou seta para baixo abrem; no menu, setas, Home e End; no modo busca, o foco vai para o campo e as setas andam pela lista; Enter escolhe; Esc fecha e devolve o foco ao botão. - **Na área de administração** (2.30), o seletor dá lugar ao bloco fixo "Administração · Todas as empresas", com a mesma altura: a área vale para todas as empresas. - Nunca: o seletor no header ou no menu do usuário; o nome do produto ao lado do logo ou no lugar do CNPJ (o logo já diz qual é o produto); trocar de empresa sem remontar a tela; um menu sem busca para quem alcança todas as empresas; a faixa "Você está em …" para quem troca entre as próprias empresas, ou com "×". ## 2.26 Kanban **Todo kanban de todo produto segue isto** *(decisão do usuário, 2026-10-26)*. O kanban é uma tela de altura cheia (3.1): ocupa a janela, **a página não rola**, o quadro rola na horizontal e cada coluna rola na vertical, com o cabeçalho da coluna parado. ```text ┌ header ──────────────────────────────────────────────────────────────────┐ ├ card ────────────────────────────────────────────────────────────────────┤ │ [Buscar…] [Status ▾] [Produto ▾] [Pessoa ▾] [Filtros] [+ Nova tarefa] │ barra (3.5) │ [Só o que é meu] [Ocultar concluídas] [≡|▦] [Compacto] [Ordenar ▾] │ │ ┌ ● Entrada 11 ┐ ┌ ● Backlog 14 ┐ ┌ ● Refinamento 9 ┐ ┌ ● Em exe │ cabeçalho parado │ │ ┌────────────┐ │ │ ┌────────────┐ │ │ ┌──────────────┐ │ │ ┌────── │ │ │ │ card │ │ │ │ card │ │ │ │ card │ │ │ │ card │ cada coluna rola │ │ └────────────┘ │ │ └────────────┘ │ │ └──────────────┘ │ │ └────── │ │ │ ⋮ │ │ ⋮ │ │ ⋮ │ │ │ │ └────────────────┘ └────────────────┘ └──────────────────┘ └ │ │ ◄━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━► │ barra horizontal à vista │ 72 tarefas │ ├ rodapé ──────────────────────────────────────────────────────────────────┤ ``` **Altura.** O card vai do header até o rodapé: altura = janela − header (64px; 48 com a sidebar recolhida) − rodapé (40px) − respiros (os 24px abaixo do card; 16 no celular) − a faixa do "Ver como" (40px, quando ligada, 2.16). Não se calcula na mão: quando a tela é de altura cheia, a coluna de conteúdo do shell (2.15) passa a ter a altura da janela, e cada nível até a coluna (conteúdo, card, quadro, coluna) cresce com `flex-1` e `min-h-0`. Janela muito baixa: a área tem no mínimo 560px, e só então a página rola. **Quadro.** - As colunas lado a lado, com 12px entre elas e **largura mínima de 288px** (`w-72`, a do DevTasks); quando todas cabem, dividem a largura. - Rola na horizontal (`overflow-x-auto`) **com a barra à vista**: 10px, polegar arredondado no token `input` (3:1 contra o card), nas medidas do `ScrollBar` do shadcn. A coluna cortada na borda do card é o outro sinal de que há mais. - Vai de borda a borda do card (sem os 16px laterais); a primeira coluna alinha com a barra de ferramentas. - **Celular** *(decisão do usuário, 2026-10-26)*: cerca de 1,5 coluna à vista, quase 2. Cada coluna tem 60% da largura do quadro e a próxima aparece pela metade, para mostrar que rola; ao soltar, o quadro encaixa no começo de uma coluna. A altura segue a mesma regra. **Coluna.** - Fundo `muted`, raio 11 (o do card), 8px de respiro e 8px entre os cards. - **Cabeçalho parado**, fora da rolagem: bolinha de 10px na cor do estado, nome (14/500) e contador (pílula 12/500 em `card`), que conta o que está no quadro depois dos filtros. - **Lista com rolagem própria** (`overflow-y-auto` com `min-h-0`): só ela rola. - **Coluna vazia:** caixa tracejada (borda `input`) com "Arraste tarefas para cá" (12, `muted-foreground`). - Com um card em cima, pronto para soltar: anel de 2px em `ring` por dentro da coluna. **Card da tarefa.** O `Card size="sm"` do shadcn (12px de respiro), de cima para baixo: 1. **Código** em mono (12, `muted-foreground`) e o "…" à direita (`ghost` `icon-sm`, com nome acessível "Ações de CHAT-0B2DE2" e tooltip). 2. **Título** (14/500) em **até 2 linhas**, cortado com reticências. É um botão: abre a tarefa. 3. **Badges** (2.5): tipo, produto e "Bloqueada", quando for o caso. 4. **Progresso** das subtarefas, quando há: o `Progress` do shadcn (4px) com "2/11" ao lado. 5. **Prazo:** badge com relógio, vencida em `danger`, até 7 dias em `warning`, depois em `success`; sem prazo, neutra. Concluída não mostra prazo. 6. **Responsável:** avatar de 24px e o nome, cortado; sem ninguém, "Sem responsável" em `muted-foreground`. - Bloqueada: além do badge, borda esquerda de 3px em `danger`. **Mover.** - **Arrastar e soltar** entre colunas: o card arrastado fica a 40% de opacidade; ao soltar, a tarefa entra no topo da coluna nova e o toast avisa "Movida para Revisão", com **Desfazer**, que devolve a coluna e a posição (2.7). Perto da borda, o quadro e a coluna rolam sozinhos. - **"Mover para ›" no "…" do card**, para quem usa teclado (e no celular): submenu com as outras colunas (2.14). Depois de mover, o quadro rola até o card e o foco volta ao "…" dele, na coluna nova. - O servidor recusou: o card volta para onde estava e o toast de erro avisa. - Reordenar dentro da coluna é opcional; quem faz usa `@dnd-kit` com o `KeyboardSensor`, como o DevTasks, e mantém o "Mover para". **Modo compacto** ("Compacto", na barra de ferramentas): o card fica com código, título e responsável (e "Bloqueada", quando for o caso), no mesmo `Card size="sm"`; cabem quase o dobro de tarefas na coluna. Vive na URL, como os filtros. **Barra de ferramentas** (3.5) *(decisão do usuário, 2026-10-26)*: à esquerda, a busca, até três filtros, "Filtros", os chips de alternância, "Lista/Kanban" e "Compacto"; à direita, só "Ordenar" e "Nova tarefa". No celular: a busca, "Filtros" e o "+"; o modo de exibição vai para o fim da folha de "Filtros". **Estados.** - Primeira carga: as colunas com o cabeçalho e dois cards em Skeleton cada uma; o contador mostra "–" (2.11). - Nada no quadro inteiro com os filtros: o estado vazio (2.12) no lugar do quadro, com "Limpar filtros". Coluna sem nada: a caixa "Arraste tarefas para cá". - Erro ao carregar: o estado de erro no lugar do quadro, com "Tentar de novo". - A contagem ("72 tarefas") fica numa linha no pé do card. - A visão em lista ("Lista", na barra) é a tabela de dados (2.10), com paginação; nela a página volta a rolar. Nunca: a página rolando junto com o quadro; o cabeçalho da coluna rolando com os cards; coluna com a altura do conteúdo, esticando a página; a barra horizontal escondida; mover só arrastando. ## 2.27 Segundo menu Quando um item do menu reúne várias **seções que são telas próprias**, cada uma com a sua listagem ou o seu formulário, elas vão num segundo menu *(decisão do usuário, 2026-10-26)*. O caso típico são as configurações da empresa: Tags, Campos personalizados, Respostas rápidas, SLA, API Keys, Webhooks. Trocar de seção é trocar de tela, por isso não são abas (2.23). ```text +------------+-------------------------------------------------------------+ | [Logo] | [≡] | Configurações da empresa › Geral › Tags [?] [✨] [🔔] | | [EE] Emp. ⇕+---------------+---------------------------------------------+ | | ATENDIMENTO | +---------------------------------------+ | | PLATAFORMA | [Tags] | | Tags [+ Nova tag] | | | Item | Campos | | As tags da empresa e quantos contatos | | | CONFIG. | Respostas | | [Buscar por nome] | | | Filas | REGRAS | | Tag ↑ Contatos | | | [Geral] | SLA | | ● VIP 12 …| | | | INTEGRAÇÕES | +---------------------------------------+ | | | API Keys | | | | Webhooks | rodapé | +------------+---------------+---------------------------------------------+ sidebar segundo menu conteúdo ``` - **Onde:** abaixo do header, à esquerda do conteúdo, até o fim da janela. O header fica igual ao das outras telas *(decisão do usuário, 2026-10-26: o segundo menu começa abaixo do header)*. A coluna fica parada enquanto o conteúdo rola (`sticky`) e rola sozinha se as seções não couberem. O rodapé (2.21) fica na coluna do conteúdo, à direita dela. - **Desenho:** 224px (`w-56`), fundo `muted`, borda à direita em `border`, padding 8. Itens de 32px (a altura padrão, 1.5), rótulo 14 e ícone de 16 opcional, raio 8. Com mais de 6 seções, elas entram em **grupos** com rótulo (12/500, `muted-foreground`), como os da sidebar. - **A cor:** no escuro, o `muted` fica um tom mais claro que a sidebar (`#1f1f1f` ao lado de `#171717`), como pediu o usuário ("uma cor mais clara do que a navbar"). No claro, a sidebar já é branca e não há tom mais claro: o `muted` é o cinza leve que separa as duas colunas. - **Ativo:** o item da sidebar (o "Geral") **continua ativo**, em `primary` cheia, em qualquer seção *(decisão do usuário, 2026-10-26)*. No segundo menu, a seção aberta fica **destacada de leve**: fundo `card`, contorno `edge` e texto 500, sem a primária, para ficar claro qual é o nível principal. Hover: fundo `card` a 50%. - **Cada seção é uma tela:** URL própria (`/configuracoes/tags`; o item sem seção abre a primeira), e o breadcrumb ganha o terceiro nível, "Grupo › Item › Seção", com o item como link para a primeira seção (2.17). Voltar do navegador volta para a seção anterior; recarregar mantém a seção. - **Conteúdo da seção: a largura toda que sobra**, na listagem e no formulário *(decisão do usuário, 2026-10-27: com o formulário até 720px sobrava um vazio à direita; a de Tags, na largura toda, ficou melhor)*. Listagem: card com a tabela e o "Novo…" no canto do card (2.1). Formulário: cards na mesma largura, campos em 2 colunas (3.4) e o "Salvar" no rodapé do último card. Dentro de uma seção, abas (2.23) só para visões da mesma coisa. - **Abaixo de 1024px:** o segundo menu vira um **Select** "Seção" no topo do conteúdo, antes da primeira box, com os mesmos grupos; escolher navega para a seção. - **Semântica e teclado:** `nav` com nome ("Seções de Geral") e links, com `aria-current="page"` na seção aberta. O Tab passa pelos links. Não é `tablist` nem tem setas. - **Permissão:** seção sem permissão não aparece; grupo sem seção visível some com o rótulo, como no menu (3.2). O servidor revalida. - Nunca: abas para trocar de seção; segundo menu dentro de um card ou com título próprio (o nome é o breadcrumb); mais de um nível no segundo menu; segundo menu com uma seção só; a primária no item ativo do segundo menu. ## 2.28 Passo a passo da tela e ajuda Cada tela pode ter um **passo a passo**: o sistema escurece a página e destaca uma parte por vez (a busca e os filtros, o "Novo…", uma coluna), com um cartão que diz o que se faz ali. Ele **começa por convite**, nunca sozinho, e se revê pelo **botão de ajuda** do header *(decisões do usuário, 2026-10-26)*. **O botão de ajuda.** Botão só de ícone no header (`circle-question-mark`, `ghost` `icon-sm`, a altura dos outros botões do header, tooltip "Ajuda"), à direita, na ordem **? · ✨ · sino** (2.15). Abre um menu suspenso (2.14) alinhado à direita: 1. **Passo a passo desta tela** (`route`). Na tela sem passo a passo, desabilitado, com "Esta tela não tem passo a passo" embaixo do rótulo (12, `muted-foreground`). 2. Só se o produto tiver, depois de um separador: **Central de ajuda** (`book-open`, abre em outra aba, com `external-link` à direita) e **Falar com o suporte** (`life-buoy`). **O convite.** Na primeira vez que a pessoa abre uma tela com passo a passo, um cartão sai do "?": ```text [?] [✨] [🔔] +-------------------------+ | Conheça esta tela | | Um passo a passo de 4 | | passos pelo que dá para | | fazer aqui. | | [Agora não] [Começar] | +-------------------------+ ``` - **Quando:** depois que a tela carrega (sem skeleton), sem modal nem outro flutuante aberto, e só se sobrarem pelo menos 2 passos visíveis nesta largura de tela. - **Desenho:** o `PopoverContent` do shadcn como vem (288px, fundo `popover`, contorno, sombra média, raio 8; 1.5), ancorado no "?": título "Conheça esta tela" (14/500), uma frase com o número de passos (14, `muted-foreground`) e, à direita, "Agora não" (`ghost` `sm`) e "Começar" (padrão `sm`). - **Não rouba o foco:** quem está digitando continua digitando. Ele é anunciado (`aria-live` educado) e, no HTML, vem logo depois do "?", então o Tab chega nele. - **Fecha** com "Agora não", com Esc ou ao clicar fora, e qualquer um deles vale como "Agora não": **um convite só por tela**, que não volta. O passo a passo continua no "?". O "?" não ganha ponto nem contador. - **Guardado por pessoa, no servidor** (não no navegador, para não repetir em outro computador): por tela e versão do passo a passo, `convidado`, `visto` ou `concluído`. Quando a tela muda a ponto de o passo a passo mudar, sobe a versão e o convite volta uma vez. **O passo a passo.** ```text ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ← véu preto a 50% ░░ +------------------------------------------+ ░░ ░░ | [Buscar] [Status ▾] [Responsável ▾] | ░░ ← a parte destacada, sem véu ░░ +------------------------------------------+ ░░ ░░ +-------------------------------------+ ░░ ░░ | Filtros 2 de 4 | ░░ ░░ | Escolha o que aparece na lista: o | ░░ ░░ | status e quem é o responsável. | ░░ ░░ | [Pular] [Voltar] [Próximo]| ░░ ░░ +-------------------------------------+ ░░ ``` - **De 3 a 5 passos**, cada um numa parte que a pessoa vai usar: a busca e os filtros, o "Novo…", uma coluna que pede explicação, o "…" das ações. Nada do shell (menu, header, sino): o passo a passo é da tela. **O último passo destaca o "?"**: "Para rever este passo a passo, é aqui.", com "Concluir". - **Véu** preto a 50% nos dois temas, sem desfoque, com o recorte na parte destacada (o raio dela e 4px de folga). A parte destacada não responde a clique enquanto o passo a passo está aberto; clicar no véu vale como "Pular". - **Cartão:** o mesmo `PopoverContent` do convite. Em cima, o título (14/500, até 3 palavras: o nome da parte) e, à direita, "2 de 4" (12, `muted-foreground`); a frase (14, `muted-foreground`, até 120 caracteres, com verbo: o que se faz ali); embaixo, **"Pular"** (`ghost` `sm`) à esquerda e, à direita, "Voltar" (`outline` `sm`, não aparece no primeiro passo) e "Próximo" (padrão `sm`; no último, "Concluir", e o "Pular" sai). - **Posição:** abaixo ou acima da parte destacada, onde couber; a página rola até ela, sem animação para quem pede movimento reduzido (1.7). Passo cuja parte não está na tela (escondida no celular, sem permissão, lista vazia) é pulado. No celular, o cartão tem a largura da tela menos 32px. - **Teclado:** o cartão é um `dialog` com `aria-labelledby` e `aria-describedby`; o foco vai para "Próximo" e fica preso no cartão; ← e → trocam de passo; Esc é "Pular". Ao sair, o foco volta para onde estava antes de começar. - **Começar de novo:** pelo "?" › "Passo a passo desta tela", ou pela sugestão do assistente (2.29), sempre do primeiro passo. - **Texto** em i18n, nos três idiomas (3.14). Cada passo se prende à tela por um atributo (`data-tour="filtros"`), nunca por classe CSS. - **Biblioteca:** **driver.js** (MIT, sem dependências; serve em React, Next e nos projetos em Tailwind 3). O shadcn não tem passo a passo, e Shepherd e Intro.js são AGPL. O cartão recebe as classes do `PopoverContent` e os botões as do `Button` (`buttonVariants`), para seguir o tema e as medidas sem valor à mão (exemplo na Parte 4). - Nunca: começar sozinho; mais de um convite por tela, ou ponto e contador no "?"; mais de 5 passos; explicar o menu ou o header; cartão sem "Pular"; passo preso a classe CSS; o mesmo cartão fora do passo a passo (dica é tooltip, 2.8; aviso é alerta, 2.6). ## 2.29 Assistente de IA Os produtos com IA têm um assistente interno: tira dúvidas sobre a tela, resume, busca e, quando o produto permite, faz tarefas no sistema. Ele mora num **painel encaixado à direita**, nunca num balão flutuante *(decisão do usuário, 2026-10-26)*: por cima, cobriria o fim da lista, o canto dos toasts e, no atendimento, o botão de enviar, o mesmo problema que tirou o rodapé de cima do conteúdo. ```text +------------+-------------------------------------+---------------------+ | [Logo] | [≡] | Grupo › Tela [?] [✨] [🔔] | ✨ Assistente [✎][×]| | [EE] Emp. ⇕+-------------------------------------+---------------------+ | | +-------------------------------+ | Nesta tela: | | GRUPO | | box (encolhe; nada fica | | Contatos [×]| | Item | | coberto) | | | | Item | +-------------------------------+ | Pergunte sobre esta | | | +-------------------------------+ | tela ou peça uma | | | | box | | tarefa. | | | +-------------------------------+ | [Como importo…? ] | | | | [Quem está sem tag?]| | | | [Fazer o passo a… ]| | | +---------------------+ | | | Pergunte ou peça… | | | | [↑] | | | rodapé | A IA pode errar… | +------------+-------------------------------------+---------------------+ ``` **Abrir e fechar** - O botão **✨** (`sparkles`, `ghost` `icon-sm`, a altura dos outros botões do header) fica no header, entre o "?" e o sino (2.15), com tooltip "Assistente" (ou o nome da IA do produto), `aria-expanded` e `aria-controls`. Abre e fecha o painel; o "×" do painel e o Esc (com o foco dentro dele) também fecham, e o foco volta ao ✨. Ao abrir, o foco vai para o campo de mensagem. Só no clique: nunca abre sozinho, no hover ou arrastando. - Só existe nos produtos com IA. Aberto ou fechado fica guardado por pessoa; a conversa **continua ao trocar de tela**, porque o painel é do shell, não da tela. **O painel** - À direita, **384px** (`w-96`), do topo ao fim da janela, fundo `sidebar` e borda à esquerda, como o painel da direita (2.15), cujo lugar ele usa: aberto, toma o lugar do painel da tela (os dados do contato, no atendimento), que volta quando ele fecha. **O conteúdo encolhe**, como com a sidebar: nada fica coberto. - **Sem espaço**, abaixo de 1024px ou quando a coluna de conteúdo ficaria com menos de 640px (com o segundo menu, 2.27, por exemplo), ele abre por cima, como **folha da direita** com véu; fecha com o "×", com Esc ou tocando fora. No celular, a folha ocupa a tela toda. - **Cabeçalho** (64px, com a borda de baixo na linha do header): ✨ e o nome (14/500) à esquerda; à direita, **Nova conversa** (`square-pen`) e **Fechar** (`x`), botões só de ícone `ghost` `sm` com tooltip. Produto que guarda o histórico põe **Conversas anteriores** (`history`) antes deles. - **Contexto:** logo abaixo, "Nesta tela: **Contatos**" (12, `muted-foreground`), com o registro aberto quando há ("Atendimento #1234"). O assistente usa a tela e o registro como contexto; o "×" da linha tira o contexto desta conversa. **A conversa** (componentes `message-scroller`, `message` e `bubble` do shadcn) - **Sem conversa:** uma frase do que ele faz ("Pergunte sobre esta tela ou peça uma tarefa.") e de 2 a 4 **sugestões da tela**, botões `outline` `sm`, um por linha, com texto de uma linha. Se a tela tem passo a passo (2.28), uma delas é "Fazer o passo a passo desta tela". - **Mensagem da pessoa:** balão à direita, `Message align="end"` com `Bubble variant="muted"`, até 80% da largura (o `Bubble` como vem). - **Resposta:** sem balão (`Bubble variant="ghost"`), na largura toda, 14/20, com Markdown seguro (negrito, listas, links em `text-link`, código em `font-mono`); nunca HTML vindo do modelo. Embaixo, **Copiar** (`copy`), **Útil** (`thumbs-up`) e **Não útil** (`thumbs-down`), botões só de ícone `ghost` `icon-xs`, com tooltip; o escolhido fica marcado (`aria-pressed`). - **Enquanto responde:** "Pensando…" com o efeito `shimmer` do shadcn (parado para quem pede movimento reduzido), depois o texto chegando. O botão de enviar vira **Parar** (`square`). A lista acompanha a resposta enquanto a pessoa está no fim; se ela rolar para cima, para de acompanhar e aparece o botão de ir ao fim (`MessageScrollerButton`). - **Erro:** dentro da conversa, logo abaixo da pergunta, `Bubble variant="destructive"` com "Não consegui responder agora." e **Tentar de novo** (`outline` `sm`). Nunca toast. **Ações no sistema** (padrão Confirmation do shadcn) - Tudo o que o assistente for **mudar** (criar, editar, enviar, encerrar, excluir) aparece antes como um cartão na conversa: `Item variant="outline" size="sm"`, o ícone da ação, a pergunta ("Criar a tag **VIP**?"), o que muda numa frase e, à direita, **Cancelar** (`outline` `sm`) e **Confirmar** (padrão `sm`). Ação destrutiva: o botão `destructive` e a consequência na frase (3.10). - Depois da escolha, o cartão vira uma linha com o resultado ("Tag VIP criada.", com **Ver** quando há registro, ou "Cancelado."). Ler, buscar e resumir não pedem confirmação. - **Permissões:** o assistente só vê e faz o que a pessoa pode; o servidor revalida, como em toda ação. No "Ver como" (2.16) ele é a pessoa escolhida, e a auditoria registra quem fez ("Marcos Silva, vendo como Ana Ribeiro, pelo assistente"). **O campo de mensagem** (padrão Prompt Input do shadcn) - `InputGroup` com `InputGroupTextarea` e, embaixo (`InputGroupAddon align="block-end"`), o botão de enviar à direita: `InputGroupButton` só de ícone (`arrow-up`, `aria-label` "Enviar"), desabilitado com o campo vazio. Anexo (`paperclip`) só se o produto aceitar. - Placeholder "Pergunte ou peça algo…"; cresce de 1 a 6 linhas e depois rola. **Enter envia, Shift+Enter quebra a linha.** - Embaixo, "A IA pode errar. Confira o que for importante." (12, `muted-foreground`, centralizado). **Quando não resolve:** o assistente oferece **Falar com o suporte**, o mesmo destino da Ajuda (2.28), levando a tela e a conversa. - Responde no idioma da interface; os textos fixos do painel e as sugestões vêm do i18n (3.14). - Nunca: balão flutuante ou janela por cima do conteúdo no desktop; abrir sozinho; mudar algo sem a confirmação; HTML da resposta inserido na página; um assistente montado dentro da tela. ## 2.30 Área de administração O que é da IDeia, e não de uma empresa cliente, mora na área de administração *(decisão do usuário, 2026-10-27)*: - as empresas que usam o sistema; - os produtos, com logo e cores; - as pessoas, com os seus papéis. Só o Sysadmin entra. A área se distingue do resto do sistema pela cor da sidebar, e a identidade do produto não muda. ```text +--------------+-----------------------------------------------------+ | [Logo] | [≡] | Administração › Empresas [?] [✨] [🔔] | | [🛡] Adminis- +-----------------------------------------------------+ | tração | +-----------------------------------------------+ | | Todas as e. | | [Buscar empresa] [Status ▾] [+ Nova empresa] | | | CADASTROS | | Empresa CNPJ Produtos | | | [Empresas] | | [EE] Empresa Ex. 12.345.678/… [◼] [◼] | | | Produtos | +-----------------------------------------------+ | | ACESSO | | | Usuários | | |--------------| | | ↶ Sair da a. | | | [MS] Marcos ⇕| rodapé | +--------------+-----------------------------------------------------+ a sidebar no tom suave da primária; o resto do shell não muda ``` - **Quem entra:** só quem está como **Sysadmin** (2.16). - Com outro papel, ou no "Ver como", a entrada não aparece. - A URL da área leva ao início, sem dizer que a área existe. - O servidor revalida em toda chamada: quem não é Sysadmin recebe 404. - **A entrada:** um item fixo no **pé da sidebar, acima do menu do usuário** *(decisão do usuário, 2026-10-27)*, com o ícone `shield` e o texto "Administração". - É um item como os do menu, separado dos grupos pela borda de cima do rodapé. - Fica à vista em qualquer tela, longe dos itens do dia a dia e perto de quem a pessoa é. - Na sidebar recolhida, só o ícone, com tooltip. - **A cor:** a sidebar inteira (fundo, hover e bordas) ganha **um tom suave da primária do produto** *(decisão do usuário, 2026-10-27: mudar a cor sem mudar a identidade)*. Os tokens: | Token | Claro | Escuro | |---|---|---| | `sidebar-admin` (fundo) | primária a 8% sobre a `sidebar` | 14% | | `sidebar-admin-accent` (hover) | 14% | 22% | | `sidebar-admin-border` | primária a 20% sobre a `border` | 30% | - O item ativo continua na `primary` cheia, e o logo é o do produto: muda o tom, não a marca. - O header, o conteúdo e o rodapé não mudam. - O build confere o contraste em todo produto: texto 4,5:1 e item ativo 3:1. - **O topo:** o logo continua. No lugar da troca de empresa (2.25) fica o bloco fixo **"Administração · Todas as empresas"**, com um quadrado de 32px em `primary` e o ícone `shield`. Não é botão, porque a área vale para todas as empresas. - **O menu:** só os itens da administração, em grupos como os do sistema (3.2): **Cadastros** (Empresas, Produtos) e **Acesso** (Usuários). Nenhum item do sistema da empresa aparece aqui. - **A saída:** dentro da área, a entrada vira **"Sair da administração"** (`undo-2`). Ela volta para a tela em que a pessoa estava antes de entrar; sem tela anterior, para o início. - **URL e breadcrumb:** - Cada tela tem URL própria: `/admin/empresas`, `/admin/produtos/ideia-vault`. - O breadcrumb é "Administração › Empresas". - No detalhe, "Administração › Produtos › Ideia Vault", com o item como link (2.17). - **As telas** usam os mesmos tipos de tela do sistema (3.1): listagem com o "Novo…" no canto do card e formulário em cards. - **Empresas:** listagem; criar e editar em modal. - **Produtos:** listagem com o logo e as cores, primária e secundária, em amostras com o hex. Aqui a cor é dado, como a cor de uma tag. O cadastro é uma tela de formulário com identificação, logos para o tema claro e o escuro, e cores com prévia e aviso de contraste. - **Usuários:** listagem com os papéis e "Editar papéis" em modal. Ninguém tira o próprio papel de Sysadmin. - **Celular:** a sidebar vira gaveta, também no tom da área. - **No código:** o layout da área marca `data-area="admin"`. - O globals.css de cada produto (get_tokens) já traz os `--sidebar-admin*` e a regra `:root:has([data-area="admin"])`, que troca `--sidebar`, `--sidebar-accent` e `--sidebar-border`. - A Sidebar do shadcn não muda, e a gaveta do celular, que abre fora do layout, também pega o tom. - **Nunca:** - outra cor para marcar a área, como vermelho, amarelo de alerta ou o cinza de outro produto; - a entrada no meio dos grupos do menu; - itens do sistema da empresa dentro da área; - a entrada à vista para quem não está como Sysadmin; - o seletor de empresa dentro da área. --- # Parte 3 — Pattern Library ## 3.1 Tipos de tela | Tipo | Estrutura | |---|---| | **Lista** (a maioria) | card com a tabela de dados, com o "Novo…" no canto superior direito do card; criar e editar em modal. Lista que depende de uma escolha (a pessoa, a fila): listagem em duas etapas (3.18) | | **Dashboard** | grade de KPIs › cards com gráficos. **Sem filtro nem período** *(decisão do usuário, 2026-10-26)*: o recorte é fixo e vai na descrição de cada card ("Últimos 30 dias", "Agora"); KPI compara com o recorte anterior do mesmo tamanho. Dashboard com filtro é relatório | | **Relatório** | um card de relatório (2.24) por assunto: abas, filtros e conteúdo dentro do card | | **Detalhe** | cards por assunto (dados, histórico, anexos), com as ações do registro (Editar, "…") no cabeçalho do primeiro card; ou painel da direita quando a lista fica à vista | | **Altura cheia** (atendimento, chat, kanban) | a área vai do header até o rodapé, com a altura da janela: **a página não rola**, só as partes dela (no kanban, o quadro na horizontal e cada coluna na vertical, 2.26). Altura = janela − header − rodapé − respiros (− as faixas do "Ver como" e da empresa, quando ligadas: 2.16 e 2.25); mínimo de 560px, abaixo disso a página rola | | **Configuração** | cards por grupo de opções; switch para o que salva sozinho, formulário com "Salvar" para o resto. **Sem listagem nem tabela** (Perfil, Geral): **centralizada, até 720px** (`mx-auto w-full max-w-180`); com tabela, a largura toda, para a tabela não ficar apertada *(decisão do usuário, 2026-10-26)*. **Com várias seções que são telas próprias** (Tags, Campos personalizados, SLA, Webhooks): segundo menu (2.27), cada seção com a sua URL, **na largura toda que sobra, também as de formulário**; nunca abas *(decisões do usuário, 2026-10-26 e 2026-10-27)* | | **Login e recuperação** | fora do shell, duas colunas: painel da marca e formulário (2.20) | Em todas dentro do shell, o conteúdo começa logo abaixo do header, na primeira box, sem barra de ações (3.3). ## 3.2 Navegação e menu - Cada item do menu é uma tela. Os itens entram em **grupos** pelo que são (o que se usa no dia a dia; o que a empresa configura; o que só a administração alcança), não pela permissão. - Item só aparece para quem tem permissão; grupo sem item visível some com o rótulo. A visibilidade é dado do menu, não condição espalhada pela tela, e o servidor revalida toda permissão. - **O menu é o do papel ativo.** Quem tem mais de um papel troca no menu do usuário (2.16), e o menu, as telas e as permissões passam a ser os do papel novo. - **O que é da IDeia, e não da empresa cliente** (as empresas, os produtos, os papéis das pessoas), não entra nos grupos do menu. Fica na **área de administração** (2.30), com entrada fixa no pé da sidebar e só para quem está como Sysadmin. - Item ativo pela rota atual. Contadores (não lidos, pendências) na pílula do item. - **Topo da sidebar em duas linhas:** o logo do produto, que leva ao início, e logo abaixo o seletor da empresa ativa (componente Troca de empresa) *(decisão do usuário, 2026-10-26)*. O logo diz qual é o produto; o seletor diz em que empresa a pessoa está. O menu e as permissões são os da empresa ativa. - **Trocar de empresa remonta a tela limpa:** a mesma tela, sem filtro, busca, página nem linha aberta; num detalhe, volta para a lista. Um toast avisa "Você está em …". Quem alcança todas as empresas e saiu da principal vê a faixa "Você está em …" acima do header, com o botão de voltar (2.25). - Item com subitens abre em sanfona dentro do grupo, um nível só. Item cujas seções são telas próprias de uma mesma área (as configurações: Tags, Campos, SLA, Webhooks) não usa subitens: abre o **segundo menu** (2.27), e o item fica ativo em qualquer seção. - Nada do usuário (perfil, preferências, idioma, tema, sair) no menu principal: tudo no menu do usuário. - A tela não tem título: o breadcrumb é o nome dela. Tela nova = um item no menu + o nome no breadcrumb. ## 3.3 Topo do conteúdo **Não existe barra de ações** *(decisão do usuário, 2026-10-26)*. O topo da tela é o header e, logo abaixo, a primeira box. O que ficava entre os dois passou para dentro do card a que pertence. - **Header:** só recolher, breadcrumb, indicadores de estado, ajuda (?), assistente (✨) e sino (2.15). Nenhuma ação da tela. - **Primeira box logo abaixo do header**, sem título de tela nem frase solta: o nome da tela é o breadcrumb, e o que explica o conteúdo vai na descrição da box. | O que ficava na barra de ações | Onde fica agora | |---|---| | "Novo…" | no card da lista que ele alimenta, no canto superior direito, na altura padrão, como o resto da barra (2.1) | | "Atualizar" e "Atualizado às" | não existem: os dados se atualizam sozinhos (3.8) | | visão e período de um relatório | nas abas e na linha de filtros do card de relatório (2.24) | | período de um dashboard | não existe: dashboard mostra um recorte fixo, dito em cada card (3.1) | | a escolha de que a tela depende (a pessoa, a fila) | na primeira etapa da listagem em duas etapas (3.18) | | "Salvar" de formulário em tela própria | no rodapé do último card (3.4) | | ações de um registro (Editar, "…") | no cabeçalho do card do detalhe, à direita | - Ação que depende de permissão entra só com ela; sem permissão, o card fica sem o botão. - Ação que parece valer para a tela inteira pertence a algum card. Se não pertence a nenhum, falta o card. ## 3.4 Formulários - **Criar e editar são o mesmo formulário**, com os campos preenchidos na edição. - **Em modal, por padrão, até 8 campos** (recomendação, não limite). Formulário maior pode ser tela própria, com os cards por grupo de campos e "Cancelar" e "Salvar" no rodapé do último card (não há barra de ações, 3.3). - Layout: 2 colunas no desktop, 1 no celular; campo longo (observações, endereço) ocupa a linha. Rótulo acima do campo. 20px entre campos (o `FieldGroup` do shadcn). - Ordem dos campos: do mais importante ao menos; o que identifica (nome) primeiro; o opcional por último. - **Validação:** no envio, e a partir daí a cada mudança do campo que falhou. Erro embaixo do campo, com o que fazer. O foco vai ao primeiro campo com erro. - **Recusa do servidor:** alerta `danger` acima do rodapé, uma frase, sem fechar o modal. - **Rodapé:** "Cancelar" (`ghost`) e "Salvar" (`primary`), à direita. Salvando: botão desabilitado com Spinner. Sucesso: fecha e mostra toast ("Fila criada"). - Fechar com alteração não salva pergunta antes ("Descartar alterações?"). - Formato de data, número e moeda segue o idioma do usuário (3.14). - Nunca campos sem rótulo, formulário sem feedback de erro, ou "Salvar" sem estado de carregamento. ## 3.5 Filtros: escopo, modo de exibição e filtro do conjunto Cada filtro tem um nível, e cada nível tem um lugar *(decisão do usuário, 2026-10-22; a validar com os analistas na página de testes)*. | Nível | O que é | Onde fica | Exemplos | |---|---|---|---| | **Escopo do card** | muda do que o card trata | topo do próprio card: abas e, logo abaixo, a linha de filtros (2.24); ou a primeira etapa da listagem em duas etapas (3.18) | as visões de um relatório (Produtos, Pessoas, Equipe); o período e o produto do relatório; a pessoa cujo acesso se edita | | **Modo de exibição** | muda como o mesmo conjunto aparece | barra de ferramentas da box, lado direito; no kanban, só Ordenar fica à direita (barra do kanban, abaixo) | Lista ou Kanban; Ordenar; Compacto; Colunas | | **Filtro do conjunto** | reduz o que a box mostra | barra de ferramentas da box, lado esquerdo | busca; Status; Produto; Pessoa; "Só o que é meu" | **Escopo dentro do card, não numa barra da tela** *(decisão do usuário, 2026-10-26)*. Não há barra de ações (3.3). Num relatório, as visões são abas (2.23) no topo do card, e o período e os filtros que mudam o recorte ficam logo abaixo delas (2.24). Tela que depende de uma escolha (a pessoa, a fila) começa listando as opções: é a listagem em duas etapas (3.18). Nada de título na tela nem frase explicativa no header: isso vai para a descrição da box. **Dashboard não tem filtro nem período** (3.1): quem precisa filtrar abre um relatório. **Barra de ferramentas da box, em até duas linhas.** 1. Primeira linha: a busca e **no máximo três filtros à vista**, os mais usados daquela tela. Os outros ficam no botão "Filtros", no fim da linha. Card sem título: o "Novo…" fecha a linha, no canto superior direito (2.1). 2. Segunda linha, só quando existe: à esquerda os chips de alternância, no máximo três; à direita o modo de exibição (Ordenar, Lista ou Kanban, Compacto, Colunas). 3. Linha de chips removíveis: só aparece quando há filtro ativo que não está à vista, com "Limpar (n)" no fim. 4. A contagem ("23 tickets") vai para o rodapé da tabela ou para a descrição da box, nunca para o título. **Barra do kanban** (2.26) *(decisão do usuário, 2026-10-26)*: à esquerda, a busca, até três filtros, "Filtros", os chips de alternância, "Lista/Kanban" e "Compacto"; à direita, só "Ordenar" e "Nova tarefa". Na primeira linha, "Filtros" vem logo depois dos filtros à vista e "Nova tarefa" fecha a linha; na segunda, "Lista/Kanban" e "Compacto" vêm depois dos chips, e "Ordenar" fica sozinho à direita. Vale também para a visão em lista da mesma tela, que usa a mesma barra. **Comportamento.** - Filtro, busca, ordenação, aba e período **vivem na URL**: sobrevivem a recarregar a página, dão para compartilhar e voltam no histórico do navegador. Trocar de empresa zera tudo. - Aplica ao mudar. O que vai ao servidor espera 300ms. Enquanto recarrega, os dados ficam e o Spinner aparece na barra (2.11). - Filtro ativo sempre pode ser visto e removido sem abrir o painel: pelo gatilho, pela linha de chips ou por "Limpar". - Padrão da tela ("Só o que é meu" ligado para quem atende) não é filtro escondido: aparece ligado no chip. - **Celular:** a barra de ferramentas recolhe para a busca e o botão "Filtros", que abre uma folha de baixo com todos os campos, inclusive os que estavam à vista e os chips de alternância; o modo de exibição vai para o fim da folha. - Os controles estão no componente 2.22. Exemplo vivo do card de relatório e do kanban na página do guia, seção 3.5; o da listagem em duas etapas, na seção 3.18. ## 3.6 Tabelas e listas - Toda listagem é tabela de dados (2.10): mesma barra de ferramentas, mesmo "…", mesmo rodapé. - **Toda listagem tem o padrão completo** *(decisão do usuário, 2026-10-26)*: busca, colunas ordenáveis, paginação, "…" de ações e os estados (skeleton, vazio, erro). - **Toda coluna de valor único é ordenável**, inclusive telefone, e-mail e código. Não ordenam a coluna de ações, a de seleção, coluna com vários valores na célula (etiquetas, avatares) e texto livre longo (descrição, observações). - **Coluna cujo valor vem de um conjunto fechado** (Status, Setor, Origem, Prioridade, Tipo…) **vira filtro** na barra de ferramentas: escolha única até 5 valores; múltipla acima disso ou quando o normal é combinar valores, como Pessoa. Com mais de três, os excedentes vão para "Filtros" (3.5). - Tela que depende de uma escolha (a pessoa, a fila) começa listando as opções: é a listagem em duas etapas (3.18). - Cards no lugar de tabela só quando o item é visual (um modelo, um anúncio) e tem no máximo 4 informações; a grade de cards segue os mesmos estados (skeleton, vazio, erro) e tem a mesma busca, ordem e filtros na barra. - Busca, filtro e ordenação ficam na URL ou no estado da tela e sobrevivem a recarregar a página. - Trocar de empresa ou de contexto remonta a tela limpa (busca, página e linha aberta zeradas). ## 3.7 Feedback: qual componente para cada situação | Situação | Componente | Onde | |---|---|---| | Erro de um campo | mensagem do campo | abaixo do campo | | Recusa do servidor num formulário | alerta `danger` | acima do rodapé do formulário | | Erro de uma ação fora de formulário | alerta `danger` | no card da ação; se não há onde, toast de erro | | Erro ao carregar uma lista | estado vazio com "Tentar de novo" | dentro da box | | Deu certo, passageiro | toast `success` | canto inferior direito | | Deu certo, precisa ficar | alerta `success` | no card | | Consequência que a pessoa precisa saber | alerta `warning` | onde a ação acontece | | Contexto neutro | alerta `info` | no topo do card | | Condição que vale enquanto dura | indicador de estado (neutro, `role="status"`) | junto do que descreve | | Processo longo | Spinner com texto curto ("Enviando 3 de 10") | no lugar da ação | | Notificação do sistema | o sino (3.11) e, se acabou de chegar, toast do tipo dela com "Ver" | header | | Dado novo no servidor | nada: a tela relê sozinha, sem Spinner e sem mudar de lugar (3.8) | — | ## 3.8 Carregamento - Primeira carga de qualquer coisa: Skeleton no formato. Recarga porque a pessoa mudou o que pediu (busca, filtro, ordenação, aba): Spinner, dados mantidos. Releitura em segundo plano: nada. - **Os dados se atualizam sozinhos** *(decisão do usuário, 2026-10-26)*: por tempo real quando o servidor avisa, ou relendo ao voltar o foco da janela e em intervalo (o `refetchOnWindowFocus` e o `refetchInterval` do TanStack Query). Sem Skeleton, sem Spinner e **sem mudar a página de lugar**: rolagem, foco, linha aberta e página da tabela ficam onde estão. - **Não existe botão "Atualizar"** em nenhuma tela, nem o "Atualizado às". "Tentar de novo" continua nos estados de erro (2.12), porque é outra coisa: a leitura falhou. - Releitura em segundo plano que falha não avisa nada: os dados ficam e a próxima tenta de novo. - Botão que dispara chamada: desabilitado com Spinner até a resposta. - Nenhuma tela sem estado de carregamento quando há chamada ao servidor. ## 3.9 Estados vazios e de erro Sempre dentro da box, no formato do 2.12. Primeiro uso convida a agir; filtro sem resultado oferece limpar; erro oferece tentar de novo. Nunca a box some: o estado toma o lugar do conteúdo. ## 3.10 Ações destrutivas - Excluir, desativar, encerrar, remover acesso: item por último no "…", depois do separador, em `danger`. - Confirmação em diálogo (2.13): pergunta no título, consequência em uma frase, botão `danger` com o verbo e o objeto ("Excluir fila"). O botão principal de uma confirmação destrutiva nunca é o foco inicial. - O que dá para desfazer não confirma: faz e oferece "Desfazer" no toast por 6 segundos. - Depois: toast de sucesso, a lista atualiza, o foco volta a um lugar razoável (a linha seguinte ou a barra de ferramentas). ## 3.11 Notificações (o sino) - Botão só de ícone no header, com o contador de não lidas na pílula `primary`. - Abre um popover de 360px (`w-90`; no celular, uma folha) com a lista: ícone do tipo (`success`, `info`, `warning`, `danger`), título de uma linha, hora relativa ("há 5 min"), e a ação "Ver" quando há destino. Não lida tem fundo `muted` leve. Vazio: "Nenhuma notificação." - **Cabeçalho do popover:** o título "Notificações" à esquerda e, à direita, duas ações: **"Marcar todas como lidas"** (`ghost` `sm`, desabilitada quando não há não lidas) e o **botão de configurações** (`settings`, `ghost` `icon-sm`, tooltip "Configurar notificações"). Ele fecha o popover e abre o modal de preferências; ao fechar o modal, o foco volta ao sino. - A que acabou de chegar também aparece como toast do tipo dela, com "Ver". **Preferências de notificação.** São **de cada pessoa, não da empresa**, e o modal do sino cuida **só das notificações do app** *(decisão do usuário, 2026-10-26)*: ```text Preferências de notificação [×] São suas: valem só para você, em qualquer dispositivo. Como avisar No sino a lista, o contador e o aviso na tela [■ ] Som um toque curto quando chega [■ ] Notificação do navegador aparece com a aba em segundo plano [ □] Avisar sobre Novas conversas · Tarefas atribuídas a mim · Menções [■ ] Notificações por e-mail ficam no seu perfil. ------------------------------------------------------------------ [Fechar] ``` - Modal padrão (384px, 2.13) em dois grupos com legenda: os **canais** (no sino, som, notificação do navegador) e os **tipos de aviso** (novas conversas, tarefas atribuídas a mim, menções). Cada linha tem rótulo, uma frase de descrição e um **switch que salva na hora** (2.4), com o toast "Preferência salva"; se falhar, o switch volta e o toast é de erro. - O tipo decide se a notificação existe; o canal, onde ela aparece. Desligar "No sino" tira a lista e o toast; o som e o navegador seguem os switches deles. - **Notificação do navegador:** ligar o switch pede a permissão do navegador, e o pedido sai do próprio clique (o navegador recusa o pedido que não nasce de um gesto). Recusada ou bloqueada, o switch fica desligado e desabilitado, com um alerta `warning` dizendo como liberar nas configurações do navegador. Sem suporte, o switch fica desabilitado e a descrição diz por quê. - **Rodapé só com "Fechar"**: não há "Salvar", porque cada switch já salvou. Acima do rodapé, a linha "Notificações por e-mail ficam no **seu perfil**", com o link para o Perfil. - **O e-mail fica só no Perfil**: um switch "Notificações por e-mail", ligado por padrão, e nada mais sobre notificação ali. O card "Notificações" de Configurações › Geral deixa de existir: era da empresa e passou a ser de cada pessoa. ## 3.12 Responsividade - **Mobile-first.** Pontos de quebra da janela: 640, 768 e 1024px. Abaixo de 768 a sidebar vira gaveta; o painel da direita só existe a partir de 1024. O segundo menu (2.27) vira um Select abaixo de 1024; o assistente de IA (2.29) vira folha abaixo de 1024 ou quando o conteúdo ficaria com menos de 640px. - **Dentro do shell, o componente mede o contêiner**, não a janela: a sidebar e o painel tiram 512px da largura, e um arranjo pela janela fica errado. Grades, tabelas, paginação e barras mudam de arranjo pela largura do elemento em volta. - A página nunca rola para o lado. Tabela que não cabe rola dentro do card; antes disso, esconde colunas secundárias. - Modal ocupa a largura no celular; formulário longo vira folha de baixo. - Toque: alvo mínimo de 24 × 24px (WCAG 2.5.8). Todo controle do shadcn cumpre (o menor, `xs`, tem 24px; o checkbox tem área de clique maior que o desenho). Controle não cresce no celular: só o texto do campo passa a 16px, para o iPhone não ampliar a tela. ## 3.13 Tema Ver 1.6. Resumo: os dois temas, escolha no menu do usuário, guardada por usuário, inicial claro ou do sistema, sem cor fixa em tela nenhuma. ## 3.14 Internacionalização (i18n) - **Todo sistema nasce preparado para três idiomas: pt-BR (padrão), en e es.** Nenhum texto fixo no código: rótulo, mensagem, placeholder, tooltip, título e texto de e-mail vêm de dicionário por idioma, com chave por contexto. Ícone e cor não substituem texto. - A troca fica no **menu do usuário** (2.16), em sigla, e vale na hora, sem recarregar. A escolha é guardada por usuário; sem escolha, o idioma do navegador se for um dos três, senão pt-BR. - Data, hora, número e moeda são formatados pela localidade escolhida: `22/10/2026` e `R$ 1.234,56` em pt-BR; `10/22/2026` e `R$1,234.56` em en; `22/10/2026` e `R$ 1.234,56` em es. A moeda é a do dado, não a do idioma. - Texto cresce até 30% de um idioma para outro: nenhum rótulo com largura fixa, nenhum botão que corte texto. Plural e gênero vêm do dicionário, nunca de concatenação. - Texto que vem do banco (nome de fila, mensagem de cliente) fica como está; só a interface traduz. ## 3.15 Acessibilidade - Contraste mínimo (1.2). Foco visível em todo controle: a borda vira `ring` e ganha um anel de 3px em `ring` a 50%, colado ao controle (o foco do shadcn, sem edição). - Tudo opera por teclado: Tab na ordem visual, Enter e Espaço ativam, Esc fecha, setas navegam em menu e tabela. - Todo controle tem nome acessível: rótulo ligado ao campo; `aria-label` em botão só de ícone; tabela com cabeçalho; imagem informativa com `alt`, decorativa com `alt` vazio. - Alerta tem `role="alert"`; indicador de estado, `role="status"`; toast é anunciado. - Cor nunca é o único sinal: status tem ícone e texto; erro tem mensagem; gráfico tem rótulo. - Movimento respeita a preferência do sistema. Nada pisca. - Nunca o atributo `title`. Nunca `tabindex` positivo. Nunca foco preso fora de um modal. ## 3.16 Texto da interface As IAs tendem a escrever demais. Aqui, não. - **Uma frase por aviso, alerta, toast, descrição ou estado vazio.** Se precisa de duas, a segunda é um link "Saiba mais". - **Título de toast: até 5 palavras.** Título de modal: até 6. Rótulo de botão: 1 a 3. - **O texto é o que a pessoa precisa para decidir**, não tudo o que é verdade sobre o assunto. - **Botão é verbo + objeto:** "Salvar", "Excluir fila", "Nova fila", "Tentar de novo". Nunca "OK", "Sim", "Confirmar", "Clique aqui", "Prosseguir". - **Erro diz o que aconteceu e o que fazer:** "Não foi possível salvar. Tente de novo." Nunca código, stack, nome de tabela ou "erro inesperado". - Sem "Por favor", sem "Ops", sem exclamação, sem ponto de interrogação fora de confirmação. Sem "Você" quando dá para omitir. Sem ponto final em rótulo e título; com ponto final em frase. - Voz ativa, presente, direta: "A fila recebe conversas" e não "As conversas serão recebidas". - Nome das coisas pelo que a pessoa reconhece ("Notificações"), não pelo que o sistema faz ("Configuração de webhook"). - Maiúscula só na primeira palavra ("Nova fila", não "Nova Fila"). Glossário dos rótulos padrão (usar sempre estes, nos três idiomas): | pt-BR | en | es | |---|---|---| | Salvar | Save | Guardar | | Cancelar | Cancel | Cancelar | | Fechar | Close | Cerrar | | Excluir | Delete | Eliminar | | Editar | Edit | Editar | | Novo / Nova … | New … | Nuevo / Nueva … | | Atualizar (nunca botão de tela: 3.8) | Refresh | Actualizar | | Buscar | Search | Buscar | | Filtrar | Filter | Filtrar | | Limpar filtros | Clear filters | Limpiar filtros | | Carregar mais | Load more | Cargar más | | Tentar de novo | Try again | Intentar de nuevo | | Desfazer | Undo | Deshacer | | Ver | View | Ver | | Entrar | Sign in | Iniciar sesión | | Sair | Sign out | Cerrar sesión | | Perfil | Profile | Perfil | | Configurações | Settings | Configuración | | Idioma | Language | Idioma | | Tema · Claro · Escuro | Theme · Light · Dark | Tema · Claro · Oscuro | | Ações | Actions | Acciones | | Opcional | Optional | Opcional | | Nenhum resultado | No results | Sin resultados | | Carregando | Loading | Cargando | | Bem-vindo de volta | Welcome back | Bienvenido de nuevo | | Esqueci minha senha | Forgot my password | Olvidé mi contraseña | | Filtros · Limpar tudo | Filters · Clear all | Filtros · Limpiar todo | | Todos · Período · Ordenar · Compacto | All · Period · Sort · Compact | Todos · Período · Ordenar · Compacto | | Problemas para entrar? | Trouble signing in? | ¿Problemas para entrar? | ## 3.17 O que nunca fazer - Cor fixa na tela (hexadecimal, nome de cor ou paleta da biblioteca): só tokens. - Título ou subtítulo na tela: o nome dela é o breadcrumb. - Menu, header ou painel lateral montado dentro da tela: são do shell. - Ação ou filtro no header, ou numa barra solta entre o header e a primeira box: vai no card a que pertence (3.3). - Mais de um botão `primary` no mesmo contexto. - Botão só de ícone sem `aria-label` e tooltip. Atributo `title` em qualquer lugar. - Texto "Carregando…" ou spinner no lugar de um componente que ainda não carregou: é Skeleton. - Aviso escrito à mão com texto colorido: é alerta. Ícone ou cor escolhidos pela tela num alerta com tipo. Quinto tipo de alerta. - Tabela solta sem barra de ferramentas, estados e "…" de ações. Botões soltos na linha. - Formulário sem feedback de erro, chamada ao servidor sem estado de carregamento. - Opacidade em texto de descrição ou de erro. - Fonte, ícone ou tamanho fora do guia. Emoji como ícone. - Texto fixo no código: tudo vem de dicionário. Bandeira como idioma. - Sombra em box. Raio diferente de 8, 12 ou redondo. - "Sim" e "Não" numa confirmação. "OK" num botão. Frase explicando o sistema inteiro num toast. - Valor aleatório ou hora atual no primeiro render (quebra a hidratação e o Skeleton). - Decidir o que renderizar pelo tema: tudo é token. - Botão "Atualizar" ou "Atualizado às": os dados se atualizam sozinhos (3.8). - Filtro ou período num dashboard: dashboard com filtro é relatório (3.1). - Coluna de valor único que não ordena, ou coluna de conjunto fechado sem o seu filtro (2.10). - Balão flutuante (de IA, ajuda ou chat) ou janela por cima do conteúdo: o assistente é um painel encaixado à direita (2.29), e a ajuda é o "?" do header (2.28). - Passo a passo que começa sozinho, ou convite que volta depois de "Agora não" (2.28). - Abas para trocar de seção das configurações: é o segundo menu (2.27). - Assistente que muda algo no sistema sem o cartão de confirmação (2.29). - Itens da administração (empresas clientes, produtos, papéis) no meio dos grupos do menu, ou a área de administração com outra cor que não o tom da primária (2.30). - Trocar de papel pelo "Ver como", ou o "Ver como" para quem não está como Sysadmin (2.16). - Tela que depende de uma escolha abrindo num seletor vazio ("Escolha uma pessoa"): ela começa listando as opções (3.18). Esc para voltar da segunda etapa. ## 3.18 Listagem em duas etapas Tela que depende de uma escolha (a pessoa, a fila, a empresa…) **começa listando as opções**, com o padrão completo de listagem, e não com um seletor vazio no topo *(decisão do usuário, 2026-10-26)*. É o que substitui o antigo "filtro inicial": não há barra de ações (3.3), e o escopo mora no card (3.5). Ao escolher, **o mesmo card desliza** para a segunda etapa, já filtrada pela escolha. ```text Etapa 1: as opções Etapa 2: a escolha (?pessoa=u2) +--------------------------------------------+ +--------------------------------------------+ | [Buscar pessoa] [Cargo ▾] | | ← Pessoas | | | | Repositórios de Ana Ribeiro | | Nome ↑ Cargo Repos. | | 8 com acesso · 24 no catálogo | | Ana Ribeiro Atendente 8 Ver acessos › | → | [Filtrar por nome] [Origem ▾] [Acesso ▾] | | Bruno Cardoso Desenv. 5 Ver acessos › | | Repositório ↑ Origem Acesso | | 1-10 de 24 ‹ Página 1 de 3 › | | ideia-chat GitHub [●] | +--------------------------------------------+ +--------------------------------------------+ ``` **Etapa 1, as opções.** Uma tabela de dados completa (2.10): busca, colunas ordenáveis, um filtro por coluna de conjunto fechado, paginação e estados. A última coluna é a ação que leva à etapa 2: um botão `ghost` `sm` com o verbo e `chevron-right` ("Ver acessos ›"), o único botão da linha, com o nome da opção no nome acessível ("Ver acessos de Ana Ribeiro"). A linha inteira também abre a etapa 2. Se a linha tiver outras ações, elas ficam no "…" ao lado. **Etapa 2, a escolha.** O mesmo card, de cima para baixo: 1. O botão **"← Pessoas"**: `ghost` `sm`, `arrow-left`, o nome da etapa 1 no plural. 2. O título (16/500) **com o nome escolhido** ("Repositórios de Ana Ribeiro") e a descrição com a contagem. 3. A barra de ferramentas e a tabela da etapa, também no padrão completo (2.10). **Ida e volta.** - **Desliza em 200ms**, como a folha lateral (1.7): ao ir, a etapa 1 sai pela esquerda e a 2 entra pela direita; ao voltar, o contrário. Com movimento reduzido, troca sem animação. - **Volta só pelo botão "← Pessoas" e pelo voltar do navegador** *(decisão do usuário, 2026-10-26)*. O Esc não volta: ele fecha camadas (modal, menu, popover), e a etapa 2 não é camada. - **Estado na URL:** a escolha entra como parâmetro (`?pessoa=u2`) com uma entrada nova no histórico; o link abre direto na etapa 2. A busca, os filtros, a ordem e a página da etapa 1 continuam na URL enquanto a etapa 2 está aberta, e os da etapa 2 têm nomes próprios (`rq`, `rpage`, `rsort`), para uma etapa não apagar a outra. Voltar tira só os da etapa 2. - **A volta encontra a etapa 1 como ficou:** a mesma busca, os mesmos filtros, a mesma ordem e a mesma página, sem Skeleton de novo (a etapa 1 continua montada, escondida). - **Foco:** ao ir, vai ao título da etapa 2 (`tabIndex={-1}`); ao voltar, à ação da linha de origem. Se a linha não está mais na página, à busca da etapa 1. - **Breadcrumb com o terceiro nível** *(decisão do usuário, 2026-10-26)*: "Configurações da empresa › Acesso a projetos › Ana Ribeiro". O nível do meio é link para a etapa 1, como na tela de detalhe (2.17), e o título da aba do navegador acompanha o último nível. - Escolha que não existe mais (o link de alguém que saiu): a etapa 2 diz isso no título ("Pessoa não encontrada"), sem tabela, com o "← Pessoas". - Trocar de empresa zera tudo e volta à etapa 1 (3.2). **Quando não usar.** Escolha que só recorta um conjunto que faz sentido inteiro (Status, Produto) é filtro (3.5). Visão de um relatório é aba (2.23). Um registro com várias partes é tela de detalhe (3.1). --- # Parte 4 — Implementação de referência: shadcn/ui + Tailwind A equipe padronizou também a stack do front *(decisão do usuário, 2026-10-22)*. Projeto novo nasce nela; projeto antigo migra quando tocar na tela. As partes 1 a 3 continuam valendo para qualquer stack; esta parte diz como elas viram código nesta. O `docs/DESIGN.md` local de um projeto nesta stack só descreve o que é específico dele (rotas, NAV, nomes de arquivos); o mapa é este. ## 4.1 A stack | Camada | Padrão | |---|---| | Framework | Next.js (App Router) com React 19 e TypeScript. App sem servidor próprio: Vite + React | | CSS | Tailwind CSS 4 | | Componentes | shadcn/ui pelo CLI atual, **estilo `base-nova`** (primitivos **Base UI**), copiados para `components/ui/`, com as medidas que vêm (1.5). Composição pelo prop `render`, nunca `asChild`. O `radix-nova` (ideia-renewal, primitivos Radix) tem as mesmas medidas e compõe pelo `asChild`, que é o do Radix (4.5) | | Ícones | `lucide-react` (importar sem o sufixo `Icon`) | | Tema | `next-themes`, classe `dark` no `` | | Formulários | `react-hook-form` + `zod` + a família `Field` do shadcn, por `Controller` | | Tabelas | `@tanstack/react-table` v9 pelo guia Data Table do shadcn | | Fontes | Roboto e Roboto Mono por `next/font/google` (no Vite, `@fontsource-variable/roboto` e `@fontsource-variable/roboto-mono`) | | Classes | `cn()` de `@/lib/utils` | | i18n | `next-intl` no Next; `i18next` + `react-i18next` no Vite *(proposta, a confirmar)* | | Documentação | sempre a da versão Base UI: `https://ui.shadcn.com/docs/components/base/` | Instalar um componente: `pnpm dlx shadcn@latest add `. Procurar antes de criar: `pnpm dlx shadcn@latest search @shadcn -q `. Ver um bloco: `pnpm dlx shadcn@latest view sidebar-07`. ## 4.2 Tokens: `app/globals.css` Os valores são os da Parte 1. **Por produto mudam só `--primary`, `--primary-foreground`, `--ring`, `--sidebar-primary` e `--brand`**; todo o resto é copiado igual. O `danger` do guia é o `destructive` do shadcn: não renomear. ```css /* Rede Ideia — gerado pelo ideia-design-system a partir de tokens/. Não edite à mão: mude o token e gere de novo. */ @import "tailwindcss"; @import "shadcn/tailwind.css"; @custom-variant dark (&:is(.dark *)); :root { --background: #f8fafc; --foreground: #0f172a; --card: #ffffff; --card-foreground: #0f172a; --popover: #ffffff; --popover-foreground: #0f172a; --primary: #153261; --primary-foreground: #ffffff; /* só isto muda por produto (com --ring, --sidebar-primary e --brand) */ --link: #153261; /* texto de link (variante link do Button): a primária; no escuro, mais clara, com 4,5:1 (D18) */ --brand: #153261; /* a primária clara, fixa nos dois temas: só o painel do login */ --secondary: #f1f5f9; --secondary-foreground: #0f172a; --muted: #f1f5f9; --muted-foreground: #475569; --accent: #f1f5f9; --accent-foreground: #0f172a; --destructive: #b91c1c; --destructive-foreground: #ffffff; --destructive-strong: #991b1b; /* = danger do guia; -strong = texto e ícone sobre superfície tingida */ --success: #15803d; --success-foreground: #ffffff; --success-strong: #166534; /* -strong: texto e ícone sobre a tinta (badge, alerta) */ --warning: #b45309; --warning-foreground: #ffffff; --warning-strong: #92400e; --info: #0369a1; --info-foreground: #ffffff; --info-strong: #075985; --border: #e2e8f0; --input: #8592a6; --ring: #153261; --sidebar: #ffffff; --sidebar-foreground: #0f172a; --sidebar-primary: #153261; --sidebar-primary-foreground: #ffffff; /* item ativo, cheio */ --sidebar-accent: #f1f5f9; --sidebar-accent-foreground: #0f172a; /* hover */ --sidebar-border: #e2e8f0; --sidebar-ring: #153261; --sidebar-admin: #eceff2; --sidebar-admin-accent: #dee2e9; --sidebar-admin-border: #b9c4d3; /* área de administração (2.30): a sidebar no tom suave da primária */ --radius: 0.5rem; /* 8px = rounded-lg (controles); a escala do shadcn multiplica: md ×0,8 (itens de menu), xl ×1,4 (card, modal) */ } .dark { --background: #171717; --foreground: #e5e5e5; --card: #262626; --card-foreground: #e5e5e5; --popover: #262626; --popover-foreground: #e5e5e5; --primary: #2e6cd2; --primary-foreground: #ffffff; /* só isto muda por produto (com --ring, --sidebar-primary e --brand) */ --link: #608fdd; /* texto de link (variante link do Button): a primária; no escuro, mais clara, com 4,5:1 (D18) */ --brand: #153261; /* a primária clara, fixa nos dois temas: só o painel do login */ --secondary: #404040; --secondary-foreground: #e5e5e5; --muted: #1f1f1f; --muted-foreground: #a8a8a8; --accent: #1f1f1f; --accent-foreground: #e5e5e5; --destructive: #f87171; --destructive-foreground: #171717; --destructive-strong: #fca5a5; /* = danger do guia; -strong = texto e ícone sobre superfície tingida */ --success: #4ade80; --success-foreground: #171717; --success-strong: #86efac; /* -strong: texto e ícone sobre a tinta (badge, alerta) */ --warning: #fbbf24; --warning-foreground: #171717; --warning-strong: #fcd34d; --info: #38bdf8; --info-foreground: #171717; --info-strong: #7dd3fc; --border: #404040; --input: #737373; --ring: #2e6cd2; --sidebar: #171717; --sidebar-foreground: #e5e5e5; --sidebar-primary: #2e6cd2; --sidebar-primary-foreground: #ffffff; /* item ativo, cheio */ --sidebar-accent: #1f1f1f; --sidebar-accent-foreground: #e5e5e5; /* hover */ --sidebar-border: #404040; --sidebar-ring: #2e6cd2; --sidebar-admin: #1a2331; --sidebar-admin-accent: #1c2a40; --sidebar-admin-border: #3b4d6c; /* área de administração (2.30): a sidebar no tom suave da primária */ } /* Área de administração (2.30): com ela na tela, a sidebar inteira (também a do celular, que abre fora do layout) fica no tom suave da primária. Marque o layout da área com data-area="admin"; a Sidebar do shadcn só lê --sidebar-*. */ :root:has([data-area="admin"]) { --sidebar: var(--sidebar-admin); --sidebar-accent: var(--sidebar-admin-accent); --sidebar-border: var(--sidebar-admin-border); } /* O shadcn/tailwind.css só traz animações e variantes data-*: as cores, a fonte e a escala de raio se registram aqui. */ @theme inline { --font-sans: var(--font-roboto), system-ui, sans-serif; --font-mono: var(--font-roboto-mono), ui-monospace, monospace; --font-heading: var(--font-sans); --color-background: var(--background); --color-foreground: var(--foreground); --color-card: var(--card); --color-card-foreground: var(--card-foreground); --color-popover: var(--popover); --color-popover-foreground: var(--popover-foreground); --color-primary: var(--primary); --color-primary-foreground: var(--primary-foreground); --color-link: var(--link); --color-brand: var(--brand); --color-secondary: var(--secondary); --color-secondary-foreground: var(--secondary-foreground); --color-muted: var(--muted); --color-muted-foreground: var(--muted-foreground); --color-accent: var(--accent); --color-accent-foreground: var(--accent-foreground); --color-destructive: var(--destructive); --color-destructive-foreground: var(--destructive-foreground); --color-destructive-strong: var(--destructive-strong); --color-success: var(--success); --color-success-foreground: var(--success-foreground); --color-success-strong: var(--success-strong); --color-warning: var(--warning); --color-warning-foreground: var(--warning-foreground); --color-warning-strong: var(--warning-strong); --color-info: var(--info); --color-info-foreground: var(--info-foreground); --color-info-strong: var(--info-strong); --color-border: var(--border); --color-input: var(--input); --color-ring: var(--ring); --color-sidebar: var(--sidebar); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-admin: var(--sidebar-admin); --color-sidebar-admin-accent: var(--sidebar-admin-accent); --color-sidebar-admin-border: var(--sidebar-admin-border); --radius-sm: calc(var(--radius) * 0.6); --radius-md: calc(var(--radius) * 0.8); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) * 1.4); --radius-2xl: calc(var(--radius) * 1.8); --radius-3xl: calc(var(--radius) * 2.2); --radius-4xl: calc(var(--radius) * 2.6); } /* O que o shadcn init põe no globals.css: sem isto, a borda sai na cor do texto e o body fica sem o fundo do tema. */ @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } button:not(:disabled), [role="button"]:not(:disabled) { cursor: pointer; } } ``` O arquivo acima é gerado pelo build a partir de `packages/tokens/base.json` e das cores da Rede Ideia no cadastro do painel, lidas da API dos produtos; cada produto tem o seu em `apps/site/dist/css/.css`. - O `@theme inline` registra as cores, a fonte e a escala de raio. **O `shadcn/tailwind.css` não registra nenhuma delas** (só as animações e as variantes `data-*`): sem esse bloco, `bg-primary` e `rounded-xl` não existem. - `--radius: 0.5rem` é o raio dos controles (`rounded-lg`, 8px); a escala do shadcn multiplica: `rounded-md` 6px (itens de menu), `rounded-xl` 11px (card, modal). Não use outro `rounded-*` em superfície. - `--chart-1` a `--chart-5` ainda não saem do build: até lá, gráfico usa a primária e as semânticas (no TODO). - O `layout.tsx` carrega as fontes e monta os providers: ```tsx // app/layout.tsx import { Roboto, Roboto_Mono } from "next/font/google"; const roboto = Roboto({ subsets: ["latin"], variable: "--font-roboto" }); const robotoMono = Roboto_Mono({ subsets: ["latin"], variable: "--font-roboto-mono" }); {children} {/* dentro do TooltipProvider: o X de fechar tem dica */} ``` ## 4.3 Mapa: componente do guia → shadcn | Guia | shadcn | Como | |---|---|---| | 2.1 Botão | `Button` | `variant`: `default` (= primary), `secondary`, `outline`, `ghost`, `link`, `destructive` (= danger). `size`: `xs` (24), `sm` (28), `default` (32), `lg` (36), `icon-xs`, `icon-sm`, `icon`, `icon-lg`; não existe `xl`. `destructive` com `text-destructive-strong` e `link` com `text-link` no `cva` (nível 3, contraste). Ícone com `data-icon="inline-start"` | | 2.2 Campo | `Field`, `FieldLabel`, `Input` / `Textarea`, `FieldDescription`, `FieldError`; ícone por `InputGroup` + `InputGroupAddon` | sempre por `Controller`; nunca o conjunto antigo `Form`/`FormField` | | 2.3 Select, combobox, data | `Select`; `Combobox` acima de 8 opções; data = campo digitável + `Calendar` (`captionLayout="dropdown"`) num `Popover` | | | 2.4 Checkbox, rádio, switch | `Checkbox`, `RadioGroup`, `Switch` | | | 2.5 Badge | `Badge` | variantes `success`, `warning`, `info` e `danger` (tingidas a 12%, texto em `text-*-strong`) adicionadas no `cva` do `badge.tsx` (nível 3) | | 2.6 Alerta | `Alert` + `AlertDescription` | variantes `success`, `warning`, `info` no `cva` do `alert.tsx`, com o ícone fixo por variante e texto em `text-*-strong`; a tela não passa ícone | | 2.7 Toast | `Toast` (Base UI) por `@/lib/toast` | `toast.success/error/info/warning(título, { description, action })`; `` no layout, dentro do `TooltipProvider`; 6 s | | 2.8 Tooltip | `Tooltip`, `TooltipTrigger render={…}`, `TooltipContent` | `TooltipProvider` na raiz; atalho em `Kbd` | | 2.9 Card, KPI | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | KPI: `CardTitle className="text-3xl font-bold tabular-nums"` | | 2.10 Tabela | `Table` + TanStack v9, com os reutilizáveis do guia (`data-table-column-header`, `-pagination`, `-view-options`, `-row-actions`) e o `filtro-de-coluna` (`Select` até 5 valores; `Popover` + `Command` acima) | um `DataTable` por tela, dentro de `Card`; `DataTableColumnHeader` em toda coluna de valor único, `enableSorting: false` nas outras; `aria-sort` no `TableHead`; ordem e filtros por `useSearchParams` | | 2.11 Skeleton, Spinner | `Skeleton`, `Spinner` | Spinner com `data-icon="inline-start"` em botão | | 2.12 Vazio | `Empty`, `EmptyHeader`, `EmptyMedia variant="icon"`, `EmptyTitle`, `EmptyDescription`, `EmptyContent` | | | 2.13 Modal | `Dialog` (formulário), `AlertDialog` (confirmação) | largura pelo `DialogContent`: padrão (384), `className="sm:max-w-lg"` (512) ou `"sm:max-w-2xl"` (672); `DialogClose render={ }> {rotulo} ); } // salvando: desabilitado, Spinner no lugar do ícone, rótulo mantido // link com cara de botão: nunca dentro de ``` ### Campo ```tsx import { Controller } from "react-hook-form"; import { Field, FieldDescription, FieldError, FieldLabel } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group"; // sempre por Controller + família Field; nunca o conjunto antigo Form/FormField/FormItem ( Nome Como aparece para o cliente. {fieldState.invalid && } )} /> // campo com ícone E-mail // data: digitável dd/mm/aaaa + Calendar num Popover, mês e ano em dropdown (captionLayout="dropdown") ``` ### Badge ```tsx import { CircleCheck, CircleX } from "lucide-react"; import { Badge } from "@/components/ui/badge"; // variantes success, warning, info e danger adicionadas no cva do badge.tsx — nível 3: // success: "bg-success/12 text-success-strong" · danger: "bg-destructive/12 text-destructive-strong" (texto no tom -strong, guia 1.2) Ativa Inativa Neutro Primária ``` ### Alerta ```tsx import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; // variantes success, warning e info no cva do alert.tsx, ícone fixo por variante; a tela não passa ícone // ex.: warning: "border-warning/30 bg-warning/8 text-warning-strong [&>svg]:text-warning-strong" {excluir.error.message} Desativar a fila devolve as conversas para a triagem. Os contadores atualizam a cada 5 minutos. // manchete só quando precisa Conexão perdida Reconectando. As mensagens enviadas agora ficam na fila. // indicador de estado (não é alerta): variante default, role="status", ícone próprio Nota interna: o cliente não vê. ``` ### Toast ```ts import { toast } from "@/lib/toast"; // o nosso, nunca o componente: tipo como método, action, prioridade toast.success("Fila criada", { description: "Retenção já recebe conversas." }); toast.error("Não foi possível salvar", { description: "Tente de novo." }); toast.info("Nova conversa atribuída", { action: { label: "Ver", onClick: () => router.push(href) } }); toast.success("Contato excluído", { action: { label: "Desfazer", onClick: desfazer } }); // no app/layout.tsx, dentro do TooltipProvider; timeout padrão 6 s (0 = até fechar) ``` ### Card e KPI ```tsx import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; // a box, como o shadcn entrega: bg-card, contorno ring-foreground/10, rounded-xl, padding 16, sem sombra. // Nada fica solto fora de uma. Não passe padding, raio nem borda por className. Horário de atendimento Fora do horário, as conversas ficam na triagem. … // KPI Conversas hoje 148 // KPI de dashboard: o recorte é fixo e dito no card, sem filtro de período (3.1); a variação compara // com o recorte anterior do mesmo tamanho Conversas 4.812 +12%

Últimos 30 dias, contra os 30 anteriores

``` ### Tabela de dados ```tsx // app/(painel)/filas/ // page.tsx "use client": busca os dados e renderiza dentro do Card // columns.tsx as colunas: toda coluna de valor único ordena; a de conjunto fechado diz o seu filtro // data-table.tsx o desta tela: barra de ferramentas, Table, paginação // data-table-features.ts tableFeatures(): só os recursos que esta tabela usa (TanStack v9) // components/ (compartilhados, do guia Data Table do shadcn): // data-table-column-header.tsx · data-table-pagination.tsx · data-table-view-options.tsx · data-table-row-actions.tsx // filtro-de-coluna.tsx o filtro de uma coluna de conjunto fechado: Select até 5 valores, seletor múltiplo acima setEditando("novo")} />} /> // ---- columns.tsx: toda coluna de valor único ordena (2.10), inclusive telefone, e-mail e código. // Não ordenam: ações, seleção, vários valores na célula sem um número natural (etiquetas), texto livre longo. const SETORES = [{ value: "suporte", label: "Suporte" }, { value: "vendas", label: "Vendas" }, { value: "financeiro", label: "Financeiro" }, { value: "retencao", label: "Retenção" }]; const STATUS = [{ value: "ativa", label: "Ativa" }, { value: "inativa", label: "Inativa" }]; const ORDEM_DO_STATUS: Record = { ativa: 0, inativa: 1 }; // pela ordem do conjunto, não pelo alfabeto export const columns = columnHelper.columns([ columnHelper.accessor("nome", { header: ({ column }) => , sortFn: "textoPtBr", enableHiding: false, cell: ({ row }) => , }), // conjunto fechado: a coluna declara o filtro; 4 valores = escolha única (2.22) columnHelper.accessor("setor", { header: ({ column }) => , sortFn: "textoPtBr", cell: ({ getValue }) => NOME_DO_SETOR[getValue()], meta: { label: "Setor", className: "hidden @md/data-table:table-cell", filtro: { param: "setor", opcoes: SETORES } }, }), // vários valores na célula (avatares) com um número natural: ordena pela quantidade. // Pessoa é conjunto fechado em que o normal é combinar: seletor múltiplo, com as opções do servidor columnHelper.accessor((fila) => fila.atendentes.length, { id: "atendentes", header: ({ column }) => , sortFn: "basic", cell: ({ row }) => , meta: { label: "Atendentes", className: "hidden @lg/data-table:table-cell", filtro: { param: "atendente", multiplo: true } }, }), columnHelper.accessor("emEspera", { header: ({ column }) => , sortFn: "basic", cell: ({ getValue }) =>
{getValue()}
, }), columnHelper.accessor((fila) => ORDEM_DO_STATUS[fila.status], { id: "status", header: ({ column }) => , sortFn: "basic", cell: ({ row }) => , // Badge com ícone (2.5) meta: { label: "Status", filtro: { param: "status", opcoes: STATUS } }, }), columnHelper.display({ id: "acoes", enableSorting: false, enableHiding: false, cell: ({ row }) => }), ]); // ---- data-table.tsx: ordem e filtros vivem na URL (3.5); a barra nasce das colunas export function DataTable({ columns, data, isLoading, isFetching, opcoes = {}, toolbarEnd }: DataTableProps) { const url = useUrlDaTela(); // ler(k) e mudar(patch) sobre useSearchParams + router.push const [id = "nome", sentido = "asc"] = (url.ler("ordem") ?? "").split(".").filter(Boolean); const table = useTable({ features, data, columns, getRowId: (row) => row.id, state: { sorting: [{ id, desc: sentido === "desc" }], globalFilter: url.ler("q") ?? "" }, // um clique ordena crescente, outro decrescente; vazio = a ordem de abertura; mudar a ordem volta à página 1 onSortingChange: (u) => { const [s] = functionalUpdate(u, [{ id, desc: sentido === "desc" }]); url.mudar({ ordem: !s || (s.id === "nome" && !s.desc) ? undefined : `${s.id}.${s.desc ? "desc" : "asc"}`, pagina: undefined }); }, }); // um filtro por coluna de conjunto fechado; até três à vista, o resto em "Filtros" (3.5) const filtros = table.getAllLeafColumns().map((c) => c.columnDef.meta?.filtro).filter(Boolean); return ( // o container da tabela mede a si mesmo: colunas secundárias por @container, nunca por sm:/md:
url.mudar({ q, pagina: undefined }, { replace: true })} /> {filtros.slice(0, 3).map((f) => ( url.mudar({ [f.param]: v, pagina: undefined })} /> ))} {filtros.length > 3 && } {isFetching && }
{toolbarEnd}
{table.getHeaderGroups().map((grupo) => ( {grupo.headers.map((h) => { const ordem = h.column.getIsSorted(); // aria-sort acompanha: ascending/descending na ordenada, none nas outras ordenáveis, nada nas demais const ariaSort = !h.column.getCanSort() ? undefined : ordem === "asc" ? "ascending" : ordem === "desc" ? "descending" : "none"; return {flexRender(h.column.columnDef.header, h.getContext())}; })} ))} {/* corpo: Skeleton na primeira carga, linhas, ou o Empty dentro do card (2.12) */}
); } // ---- components/filtro-de-coluna.tsx: até 5 valores, escolha única; acima disso, ou quando o normal é // combinar valores (Pessoa, Etiquetas), o seletor múltiplo com busca do 2.22 (Popover + Command) export function FiltroDeColuna({ titulo, opcoes, multiplo, valor, onChange }: FiltroDeColunaProps) { if (!multiplo && opcoes.length <= 5) { return ( ); } return onChange(v.join(",") || undefined)} />; } ``` ### Skeleton e Spinner ```tsx import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; // primeira carga: o formato do conteúdo final, larguras fixas (nunca Math.random()) {Array.from({ length: 5 }).map((_, i) => (
))} // ação em andamento ``` ### Estado vazio ```tsx import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@/components/ui/empty"; Nenhum contato ainda Cadastre o primeiro para começar. // erro ao carregar: o mesmo Empty com CircleAlert e "Tentar de novo" (refetch) ``` ### Modal e confirmação ```tsx import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; // o gatilho é o "Novo…" no canto superior direito do card da lista (2.1); o foco volta a ele ao fechar setOpen(true)} /> {/* 512px: formulário em duas colunas; o padrão do shadcn tem 384 */} Nova fila Defina quem atende e como as conversas são distribuídas. {/* o mesmo componente cria e edita; a key remonta o form limpo a cada abertura */}
…Controllers…
{form.formState.errors.root && ( {form.formState.errors.root.message} )} }>Cancelar
// confirmação destrutiva: AlertDialog, pergunta no título, consequência de uma frase, verbo + objeto Cancelar } onClick={excluir}>Excluir fila ``` ### Menu suspenso (ações da linha) ```tsx import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { DataTableRowActions } from "@/components/data-table-row-actions"; // o "…": ghost icon-sm, aria-label e Tooltip } /> editar(row)}>Editar desativar(row)}>Desativar setConfirmar(row)}>Excluir… ``` ### Shell e item do menu ```tsx // components/shell/shell.tsx — bloco sidebar-07 (menu) + sidebar-15 (painel da direita) {/* Sidebar collapsible="icon": logo, grupos do NAV, NavUser no footer */} {/* o
da tela */}
{/* com Tooltip "Recolher menu" + Ctrl B */}

{tela.label}

…Grupo › Tela…
{/* indicadores de estado, depois ? · ✨ · sino (2.28, 2.29, 3.11); nada mais */}
{/* sem barra de ações (3.3): a primeira box vem logo abaixo do header; o "Novo…" mora no card */} {/* com segundo menu (2.27), o layout da área encosta no header e na sidebar: sem o padding daqui */}
{children}