Anatomia de um componente
Guia para quem cria ou altera um componente no pacote @govbr-ds/webcomponents.
As regras completas estão em AGENTS.md, AI-CONTEXT.md e nas instruções em
.github/instructions/; aqui está o mapa prático.
Estrutura de arquivos
Um componente vive em packages/webcomponents/src/components/<nome>/:
| Arquivo | Papel |
|---|---|
<nome>.tsx | Classe Stencil: ciclo de vida, estado, DOM e render. Nada de lógica reutilizável pesada aqui. |
<nome>.scss | Estilos do Shadow DOM. Use tokens do GovBR-DS, baixa especificidade, :focus-visible. |
<nome>.types.ts | Tipos específicos do componente. Tipos compartilhados vão em components/global.types.ts. |
<nome>.figma.ts | Figma Code Connect (preenchido quando há equivalente visual no DS). |
_tests/<nome>.e2e.tsx | Testes E2E (Vitest browser mode + @stencil/vitest). |
sections/*.md | Blocos editoriais incorporados à página do site: accessibility.md, migrate.md, native-events.md, native-attributes.md, validation.md. |
readme.md | Gerado a partir do JSDoc — não edite à mão. |
<nome>.utils.ts | Opcional: helpers locais não triviais. Lógica compartilhável vai para src/shared/utils. |
Mantenha a ordem dentro do .tsx: imports/constantes → JSDoc → campos e
decorators → ciclo de vida/listeners → métodos públicos → helpers privados →
render. Inclua apenas as seções que existirem.
Fluxo
# 1. Scaffold (cria .tsx/.scss/.types/.figma/_tests, sections/accessibility.md e
# sections/migrate.md, o exemplo default.html, presets.json e a página MDX).
pnpm plop component
# 2. Registrar no manifesto/menu e ver na bancada
pnpm generate:cem
pnpm dev # http://localhost:8001
# 3. Loop rápido de teste (com `pnpm dev` rodando noutro terminal)
pnpm test:watch <nome>
O pnpm plop marca o componente como @status experimental. Promova para
beta/stable só quando a API estabilizar.
Convenções
- Nomes: tag pública
br-<nome>; classe/interface/alias emPascalCase; props e métodos emcamelCase. - API canônica: siga o vocabulário HTML/IDL (
readOnly,tabIndex,disabled). Renomeações mantêm o alias antigo com@deprecated, mesma implementação, mesmo default/reflexão. - IDs: sempre
generateUniqueIddesrc/shared/utils/id. NuncaMath.random,Date.nowou contador manual. reflect: true: só quando um atributo, CSS ou consumidor externo exigir.formAssociated+@AttachInternals(): só para controle de formulário nativo (input, select, checkbox…).- Estado: cada valor renderizado ou exposto por ARIA/formulário tem um único
dono. Derive valores em vez de duplicar props em
@State. Pai↔filho por props/eventos; slots porslotchange. Nada dedocumentcomo barramento.
Eventos
Antes de criar um evento customizado, confirme que o evento nativo não resolve.
Eventos públicos novos: br<ComponentName><Action>, detail tipado, flags
(bubbles/composed/cancelable) explícitos. Eventos legados — inclusive os
sem esse padrão — não mudam de nome, payload, flags nem timing.
Controles internos em Shadow DOM preservam os eventos nativos aplicáveis
(input, change, focus, blur, invalid) e reemitem no host no máximo
uma vez por interação. Escrita programática, init e reset não são interação —
não emita eventos neles. Documente o evento sintético sem prometer isTrusted.
Acessibilidade (mínimo)
- HTML semântico antes de ARIA; todo controle com nome, papel, estado e valor.
- Operação completa por teclado,
:focus-visible, ordem previsível, nenhumtabindexpositivo. - Em Shadow DOM, teste as relações reais de
label/aria-describedby/aria-controlsentre host, controle interno e conteúdo projetado. - Movimento estático por padrão; transições só em
prefers-reduced-motion: no-preference, com tokens existentes. - Preencha
sections/accessibility.mdcom o contrato real (teclas, foco, estados). Vejabutton/sections/accessibility.mdcomo referência.
Documentação
- Documente imediatamente cada
@Prop,@State,@Event,@Method(um bloco JSDoc cada) e@slot/@partquando forem contrato público. - O JSDoc principal da classe (Visão Geral) define função, papel, quando usar e quando evitar, e a composição com subcomponentes.
- Valores estruturados, callbacks,
Dateou property binding não óbvios pedem@exampleexecutável. - A página do site é
apps/site/docs/components/<nome>/index.mdx— importe só as seções que o gerador emitir (ele só cria as que têm conteúdo).
Testes
- Vitest +
@stencil/vitestem browser mode (provider Playwright, headless). - Use
render,userEvent,waitForStablee os helpers desrc/shared/test-utils;queryPiercingquando a composição atravessar Shadow DOMs.expectNoAxeViolationsé evidência complementar, não substitui teclado e foco. - Loop rápido:
pnpm test:component <nome>/pnpm test:watch <nome>/pnpm test:changed. Esses atalhos usamwww; mudanças compartilhadas selecionam toda a suíte do core. A suíte completa (pnpm tests) usadist. Nenhum desses comandos inicia build: aguarde o watch da saída escolhida ou compile explicitamente antes de testar.WBC_TEST_BUNDLE=dist|wwwseleciona outra saída sem fallback. - Matriz de navegadores:
pnpm exec nx run webcomponents:test:platform.
Antes do MR
- Checagem rápida:
pnpm lint,pnpm typecheck,pnpm test:contract. - Mudança de API pública →
.github/review-checklists/component-api.md. - Mudança de estado/eventos →
.github/review-checklists/state-events.md. pnpm figma:validate:structureepnpm figma:parsequando houver Code Connect.- Não rode
pnpm buildoupnpm generatepor rotina — só quando o tipo de mudança exigir. Nenhumgit commitautomático.