@rendra-ui/web 2.1.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +11 -9
  2. package/bin/rendra.mjs +0 -0
  3. package/dist/base.css +1 -1
  4. package/dist/{button-DIcpJwK9.js → button-CO0x1yjg.js} +4 -4
  5. package/dist/{button-DIcpJwK9.js.map → button-CO0x1yjg.js.map} +1 -1
  6. package/dist/chart.js +5 -5
  7. package/dist/cli/auditar.js +1 -1
  8. package/dist/cli/codigos.js +2 -2
  9. package/dist/cli/help.js +1 -1
  10. package/dist/cli/trocar.js +2 -2
  11. package/dist/cli/typescript-loader.js +1 -1
  12. package/dist/{cn-DVPheE99.js → cn-BM6MSZpE.js} +2 -2
  13. package/dist/{cn-DVPheE99.js.map → cn-BM6MSZpE.js.map} +1 -1
  14. package/dist/{components-Cm2ezlPT.js → components-2fCoBfBU.js} +2 -2
  15. package/dist/{components-Cm2ezlPT.js.map → components-2fCoBfBU.js.map} +1 -1
  16. package/dist/components.css +1 -1
  17. package/dist/document-viewer.js +4 -4
  18. package/dist/{empty-state-DtyZAfYb.js → empty-state-8rIk7QlP.js} +3 -3
  19. package/dist/{empty-state-DtyZAfYb.js.map → empty-state-8rIk7QlP.js.map} +1 -1
  20. package/dist/index.js +34 -24
  21. package/dist/index.js.map +1 -1
  22. package/dist/{rendra-provider-cCnquOZy.js → rendra-provider-BC4KBTfU.js} +2 -2
  23. package/dist/{rendra-provider-cCnquOZy.js.map → rendra-provider-BC4KBTfU.js.map} +1 -1
  24. package/dist/rich-text-editor.js +3 -3
  25. package/dist/router-bridge.js +2 -2
  26. package/dist/tokens.css +1 -1
  27. package/dist/{tooltip-DyeuGPCc.js → tooltip-BLc_n2IB.js} +3 -3
  28. package/dist/{tooltip-DyeuGPCc.js.map → tooltip-BLc_n2IB.js.map} +1 -1
  29. package/dist/{use-breakpoint-BmXOZtF9.js → use-breakpoint-BqhYKT3L.js} +2 -2
  30. package/dist/{use-breakpoint-BmXOZtF9.js.map → use-breakpoint-BqhYKT3L.js.map} +1 -1
  31. package/dist/widget-grid.js +2 -2
  32. package/package.json +2 -2
package/README.md CHANGED
@@ -218,7 +218,7 @@ React 19, TypeScript 5.9 (estrito), Vite 8, Tailwind CSS 4 (tema via `@theme`),
218
218
 
219
219
  ## Começar com IA
220
220
 
221
- O repositório vem preparado para agentes de IA de desenvolvimento. Ao abrir o projeto, a IA lê as instruções sozinha, pergunta se é **projeto novo** ou **migração de layout**, conduz um **briefing** (negócio, usuários, menu, tema, cores, telas e dados; se você já tiver um **código de modelo** da galeria, ele pula essas escolhas), registra tudo em `docs/BRIEFING.md` e só então constrói, em etapas e com as verificações passando.
221
+ O repositório vem preparado para agentes de IA de desenvolvimento. Ao abrir o projeto, a IA lê as instruções sozinha, pergunta se é **projeto novo**, **migração de layout** ou **contribuição**, conduz um **briefing** (negócio, usuários, menu, tema, cores, telas e dados; se você já tiver um **código de modelo** da galeria, ele pula essas escolhas), registra tudo em `docs/BRIEFING.md` e só então constrói, em etapas e com as verificações passando.
222
222
 
223
223
  | Ferramenta | Arquivo lido automaticamente |
224
224
  | -------------------------------------------- | -------------------------------------------------------------------- |
@@ -240,10 +240,10 @@ Clone https://github.com/bsmagalhaes/rendra-design-system e use como base do meu
240
240
  **Para migrar o layout de um sistema existente**, abra a IA na pasta do seu sistema e cole:
241
241
 
242
242
  ```text
243
- Aplique neste projeto o design system https://github.com/bsmagalhaes/rendra-design-system. Leia o AGENTS.md e o DESIGN_RULES.md dele e siga o fluxo de migração, começando pelo briefing.
243
+ Aplique neste projeto o design system https://github.com/bsmagalhaes/rendra-design-system. Leia, nesta ordem: https://github.com/bsmagalhaes/rendra-design-system/blob/main/AGENTS.md, https://github.com/bsmagalhaes/rendra-design-system/blob/main/DESIGN_RULES.md, https://github.com/bsmagalhaes/rendra-design-system/blob/main/docs/PROMPT_MIGRACAO.md e https://github.com/bsmagalhaes/rendra-design-system/blob/main/docs/COMO_APLICAR.md, e siga o fluxo de migração, começando pelo briefing.
244
244
  ```
245
245
 
246
- A versão completa do prompt de migração, com todas as regras, está em [docs/PROMPT_MIGRACAO.md](docs/PROMPT_MIGRACAO.md). O roteiro do briefing está em [docs/BRIEFING_MODELO.md](docs/BRIEFING_MODELO.md).
246
+ A versão completa do prompt de migração, que escolhe entre os três caminhos (pacote npm, cópia de arquivos-fonte ou clone e refazer) e aponta para as regras no repositório de referência, está em [docs/PROMPT_MIGRACAO.md](docs/PROMPT_MIGRACAO.md). O roteiro do briefing está em [docs/BRIEFING_MODELO.md](docs/BRIEFING_MODELO.md).
247
247
 
248
248
  ---
249
249
 
@@ -307,7 +307,7 @@ docs/ guia de aplicação, prompt de migração e imagens
307
307
  2. **Modelo e identidade:** fonte e raio em `src/styles/theme.css` (se o modelo mudar); nome, empresa, frase, formato (`square`, `rounded` ou `pill`) e logotipo na sidebar em `src/brand/brand.config.ts`.
308
308
  3. **Arte:** substitua os SVGs de `src/brand/assets`. O símbolo usa `fill="currentColor"`, porque os ícones de feedback o tingem.
309
309
  4. **Contraste:** abra `/tokens` nos modos claro e escuro. Nenhum selo pode marcar "falha".
310
- 5. **Verificação:** rode `npm run check:rules && npm test && npm run test:layout`.
310
+ 5. **Verificação:** rode `npm run typecheck`, `npm run lint`, `npm run check:rules`, `npm test`, `npm run test:layout` e `npm run test:a11y`.
311
311
 
312
312
  Marca de cada cliente em tempo de execução (white label, sem build): `applyPalette(sementes)`. Exemplo em [docs/COMO_APLICAR.md](docs/COMO_APLICAR.md).
313
313
 
@@ -321,7 +321,7 @@ Um projeto criado a partir do Rendra não fica parado no tempo, e há mais de um
321
321
 
322
322
  ### Boilerplate
323
323
 
324
- Comece por este repositório (`degit`, `npx create-rendra` quando existir, ou clone e apague o `.git`) quando o projeto é novo: você recebe os três templates, as quatro paletas, o AppShell, as telas base e o Storybook, e edita a marca em `src/brand` como descrito em "Como trocar a marca em 5 passos", acima.
324
+ Comece por este repositório (`degit`, ou clone e apague o `.git`) quando o projeto é novo: você recebe os três templates, as quatro paletas, o AppShell, as telas base e o Storybook, e edita a marca em `src/brand` como descrito em "Como trocar a marca em 5 passos", acima.
325
325
 
326
326
  ### Registry (shadcn/ui)
327
327
 
@@ -360,14 +360,16 @@ O CSS já sai compilado (estratégia A: o host recebe `tokens.css`, `base.css` e
360
360
 
361
361
  ### CLI (`rendra`)
362
362
 
363
- Três comandos. Com o pacote instalado: `npx rendra <comando>` (ou `rendra` direto, se instalado global). Sem instalar o pacote no projeto: `npx @rendra-ui/web <comando>` roda a mesma CLI direto do registry:
363
+ Três comandos. Forma segura, que não exige instalar nada: `npx @rendra-ui/web <comando>`, direto do registry:
364
364
 
365
365
  ```bash
366
- rendra codigos # lista o catálogo de códigos de componente
367
- rendra auditar [pasta] # regras genéricas de DESIGN_RULES.md no projeto de destino
368
- rendra trocar <DE> <PARA> --dry-run # simula a troca de uma variante do catálogo pela outra
366
+ npx @rendra-ui/web codigos # lista o catálogo de códigos de componente
367
+ npx @rendra-ui/web auditar [pasta] # regras genéricas de DESIGN_RULES.md no projeto de destino
368
+ npx @rendra-ui/web trocar <DE> <PARA> --dry-run # simula a troca de uma variante do catálogo pela outra
369
369
  ```
370
370
 
371
+ Nota: `npx rendra <comando>` só funciona com o pacote instalado neste projeto (ou instalado global, como `rendra <comando>` direto). Sem instalar, não use `npx rendra`: o nome `rendra` no npm pertence a outro pacote, não a este.
372
+
371
373
  `rendra auditar` aplica sete regras estáticas, sem IA (cor fixa, valor arbitrário, estilo inline, fonte fixa, `100vh`, degrau fora da escala, raio fixo), o mesmo tipo de checagem do `check:rules` deste repositório, aplicado a qualquer projeto. `rendra trocar` reescreve a prop literal que distingue duas variantes do catálogo (`ABA-001` para `ABA-002`, por exemplo), sempre com `--dry-run` primeiro: um elemento sem a prop conta como a variante padrão do componente (troca também esse caso, inserindo ou removendo a prop conforme o lado); prop dinâmica (`variant={x}`) nunca é reescrita, só listada para revisão manual, e troca entre componentes diferentes nunca edita, só aponta onde o componente aparece. Só `rendra trocar` precisa do `typescript` instalado no projeto de destino (`peerDependency` opcional do pacote), para ler e reescrever o JSX; sem ele, avisa e sai com erro.
372
374
 
373
375
  ### Skill de migração parcial
package/bin/rendra.mjs CHANGED
File without changes
package/dist/base.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
 
3
3
  @layer base {
4
4
  *,
@@ -1,6 +1,6 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DVPheE99.js";
3
- import { p as resolveCatalogCode } from "./components-Cm2ezlPT.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-BM6MSZpE.js";
3
+ import { p as resolveCatalogCode } from "./components-2fCoBfBU.js";
4
4
  import { Loader2 } from "lucide-react";
5
5
  import { Slot } from "radix-ui";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -140,4 +140,4 @@ var Button = forwardRef(function Button({ className, variant, size, icon, iconRi
140
140
  //#endregion
141
141
  export { usePrefersReducedMotion as i, buttonVariants as n, Spinner as r, Button as t };
142
142
 
143
- //# sourceMappingURL=button-DIcpJwK9.js.map
143
+ //# sourceMappingURL=button-CO0x1yjg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"button-DIcpJwK9.js","names":[],"sources":["../src/hooks/use-reduced-motion.ts","../src/components/ui/spinner.tsx","../src/components/ui/button.tsx"],"sourcesContent":["import { useSyncExternalStore } from 'react'\n\n/*\n * Único hook de leitura de \"menos movimento\" do sistema operacional (prefers-reduced-motion).\n * Usado pelo Spinner e pelo Progress indeterminado para parar a animação sem depender só do\n * CSS, e assim ficar testável (mocke matchMedia para simular a preferência).\n */\n\nfunction subscribe(cb: () => void) {\n const mq = matchMedia('(prefers-reduced-motion: reduce)')\n mq.addEventListener('change', cb)\n return () => mq.removeEventListener('change', cb)\n}\n\nfunction getCurrent() {\n return matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\nexport function usePrefersReducedMotion() {\n return useSyncExternalStore(subscribe, getCurrent, () => false)\n}\n","import { Loader2 } from 'lucide-react'\nimport { usePrefersReducedMotion } from '@/hooks/use-reduced-motion'\nimport { cn } from '@/lib/cn'\n\n/*\n * Indicador de carregamento único do sistema (A17 do plano da v2). Substitui qualquer\n * `Loader2 animate-spin` solto em Button, Input e Select. Sem label é decorativo\n * (aria-hidden); com label, anuncia o carregamento a quem usa leitor de tela. Para de girar\n * quando o sistema pede menos movimento.\n */\n\nexport interface SpinnerProps {\n size?: 'sm' | 'md' | 'lg'\n /** Nome do que está carregando. Vazio (padrão): decorativo, sem anúncio de leitor de tela. */\n label?: string\n className?: string\n}\n\nconst sizeClass: Record<NonNullable<SpinnerProps['size']>, string> = {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n}\n\nexport function Spinner({ size = 'md', label, className }: SpinnerProps) {\n const reduced = usePrefersReducedMotion()\n const icon = (\n <Loader2 aria-hidden className={cn(sizeClass[size], reduced ? '' : 'animate-spin')} />\n )\n\n if (!label) {\n return (\n <span data-rendra=\"SPIN-001\" aria-hidden className={cn('inline-flex', className)}>\n {icon}\n </span>\n )\n }\n\n return (\n <span\n data-rendra=\"SPIN-001\"\n role=\"status\"\n className={cn('inline-flex items-center gap-2', className)}\n >\n {icon}\n <span className=\"sr-only\">{label}</span>\n </span>\n )\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport { Slot } from 'radix-ui'\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'\nimport { resolveCatalogCode } from '@/catalog/components'\nimport { Spinner } from '@/components/ui/spinner'\nimport { cn } from '@/lib/cn'\n\n/**\n * Botão único do sistema. Variantes, tamanho, ícone, só ícone, carregamento e\n * largura total são props: nunca crie outro arquivo de botão.\n * Altura vem de h-control-*: 44px ou mais no mobile, compacta a partir de md.\n */\nexport const buttonVariants = cva(\n 'inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-control font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-out select-none active:scale-98 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n // Hover troca para a cor de hover da marca e eleva um nível; pressionar comprime.\n primary:\n 'bg-primary text-primary-foreground shadow-sm hover:bg-primary-hover hover:text-primary-hover-foreground hover:shadow-md active:bg-primary-hover active:text-primary-hover-foreground active:shadow-sm',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary-hover hover:text-secondary-hover-foreground hover:shadow-md active:bg-secondary-hover active:text-secondary-hover-foreground active:shadow-sm',\n outline:\n 'border border-input bg-card text-foreground hover:border-primary hover:bg-primary-soft hover:text-primary-soft-foreground active:bg-primary-soft',\n // Transparentes: no hover, assumem a cor primária do template.\n ghost:\n 'text-foreground hover:bg-primary-soft hover:text-primary-soft-foreground active:bg-primary-soft',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive-hover hover:shadow-md active:bg-destructive-hover active:shadow-sm',\n link: 'min-h-touch min-w-touch text-primary-text underline-offset-4 hover:text-primary-hover hover:underline md:min-h-0 md:min-w-0',\n },\n size: {\n sm: 'h-control-sm px-3 text-sm [&_svg]:size-icon-sm',\n md: 'h-control-md px-4 text-sm [&_svg]:size-icon-sm',\n lg: 'h-control-lg px-6 text-base [&_svg]:size-icon-md',\n },\n iconOnly: { true: 'px-0', false: '' },\n fullWidth: { true: 'w-full', false: '' },\n },\n compoundVariants: [\n { iconOnly: true, size: 'sm', className: 'w-control-sm' },\n { iconOnly: true, size: 'md', className: 'w-control-md' },\n { iconOnly: true, size: 'lg', className: 'w-control-lg' },\n { variant: 'link', className: 'h-auto px-0' },\n ],\n defaultVariants: { variant: 'primary', size: 'md', iconOnly: false, fullWidth: false },\n },\n)\n\ntype Variants = VariantProps<typeof buttonVariants>\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, Omit<Variants, 'iconOnly' | 'fullWidth'> {\n /** Ícone à esquerda do texto (ou o próprio conteúdo, com iconOnly). */\n icon?: ReactNode\n /** Ícone à direita do texto. */\n iconRight?: ReactNode\n /** Botão só com ícone: quadrado, e aria-label passa a ser obrigatório. */\n iconOnly?: boolean\n /** Mostra indicador, bloqueia cliques e marca aria-busy. */\n loading?: boolean\n /** Ocupa toda a largura do contêiner. */\n fullWidth?: boolean\n /** Renderiza o filho (ex.: <Link>) com a aparência de botão. */\n asChild?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n className,\n variant,\n size,\n icon,\n iconRight,\n iconOnly = false,\n loading = false,\n fullWidth = false,\n asChild = false,\n disabled,\n type = 'button',\n children,\n ...props\n },\n ref,\n) {\n if (import.meta.env.DEV && iconOnly && !props['aria-label']) {\n console.warn('Button com iconOnly precisa de aria-label.')\n }\n const classes = cn(buttonVariants({ variant, size, iconOnly, fullWidth }), className)\n const code = resolveCatalogCode('Button', { variant: variant ?? 'primary' })\n\n if (asChild) {\n return (\n <Slot.Root ref={ref} data-rendra={code} className={classes} {...props}>\n {children}\n </Slot.Root>\n )\n }\n\n // Ícone dentro do botão segue o tamanho do próprio botão (sm/md -> sm, lg -> md, como o\n // [&_svg]:size-icon-* de buttonVariants já fazia com o Loader2 solto).\n const leading = loading ? <Spinner size={size === 'lg' ? 'md' : 'sm'} /> : icon\n return (\n <button\n ref={ref}\n type={type}\n data-rendra={code}\n className={classes}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...props}\n >\n {leading}\n {iconOnly ? (loading ? null : children) : children}\n {!iconOnly && iconRight}\n </button>\n )\n})\n"],"mappings":";;;;;;;;;AAQA,SAAS,UAAU,IAAgB;CACjC,MAAM,KAAK,WAAW,kCAAkC;CACxD,GAAG,iBAAiB,UAAU,EAAE;CAChC,aAAa,GAAG,oBAAoB,UAAU,EAAE;AAClD;AAEA,SAAS,aAAa;CACpB,OAAO,WAAW,kCAAkC,CAAC,CAAC;AACxD;AAEA,SAAgB,0BAA0B;CACxC,OAAO,qBAAqB,WAAW,kBAAkB,KAAK;AAChE;;;ACFA,IAAM,YAA+D;CACnE,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,SAAgB,QAAQ,EAAE,OAAO,MAAM,OAAO,aAA2B;CACvE,MAAM,UAAU,wBAAwB;CACxC,MAAM,OACJ,oBAAC,SAAD;EAAS,eAAA;EAAY,WAAW,GAAG,UAAU,OAAO,UAAU,KAAK,cAAc;CAAI,CAAA;CAGvF,IAAI,CAAC,OACH,OACE,oBAAC,QAAD;EAAM,eAAY;EAAW,eAAA;EAAY,WAAW,GAAG,eAAe,SAAS;EAC5E,UAAA;CACG,CAAA;CAIV,OACE,qBAAC,QAAD;EACE,eAAY;EACZ,MAAK;EACL,WAAW,GAAG,kCAAkC,SAAS;EAH3D,UAAA,CAKG,MACD,oBAAC,QAAD;GAAM,WAAU;GAAW,UAAA;EAAY,CAAA,CACnC;;AAEV;;;;;;;;ACpCA,IAAa,iBAAiB,IAC5B,uaACA;CACE,UAAU;EACR,SAAS;GAEP,SACE;GACF,WACE;GACF,SACE;GAEF,OACE;GACF,aACE;GACF,MAAM;EACR;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,UAAU;GAAE,MAAM;GAAQ,OAAO;EAAG;EACpC,WAAW;GAAE,MAAM;GAAU,OAAO;EAAG;CACzC;CACA,kBAAkB;EAChB;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,SAAS;GAAQ,WAAW;EAAc;CAC9C;CACA,iBAAiB;EAAE,SAAS;EAAW,MAAM;EAAM,UAAU;EAAO,WAAW;CAAM;AACvF,CACF;AAoBA,IAAa,SAAS,WAA2C,SAAS,OACxE,EACE,WACA,SACA,MACA,MACA,WACA,WAAW,OACX,UAAU,OACV,YAAY,OACZ,UAAU,OACV,UACA,OAAO,UACP,UACA,GAAG,SAEL,KACA;CAIA,MAAM,UAAU,GAAG,eAAe;EAAE;EAAS;EAAM;EAAU;CAAU,CAAC,GAAG,SAAS;CACpF,MAAM,OAAO,mBAAmB,UAAU,EAAE,SAAS,WAAW,UAAU,CAAC;CAE3E,IAAI,SACF,OACE,oBAAC,KAAK,MAAN;EAAgB;EAAK,eAAa;EAAM,WAAW;EAAS,GAAI;EAC7D;CACQ,CAAA;CAMf,MAAM,UAAU,UAAU,oBAAC,SAAD,EAAS,MAAM,SAAS,OAAO,OAAO,KAAO,CAAA,IAAI;CAC3E,OACE,qBAAC,UAAD;EACO;EACC;EACN,eAAa;EACb,WAAW;EACX,UAAU,YAAY;EACtB,aAAW,WAAW,KAAA;EACtB,GAAI;EAPN,UAAA;GASG;GACA,WAAY,UAAU,OAAO,WAAY;GACzC,CAAC,YAAY;EACR;;AAEZ,CAAC"}
1
+ {"version":3,"file":"button-CO0x1yjg.js","names":[],"sources":["../src/hooks/use-reduced-motion.ts","../src/components/ui/spinner.tsx","../src/components/ui/button.tsx"],"sourcesContent":["import { useSyncExternalStore } from 'react'\n\n/*\n * Único hook de leitura de \"menos movimento\" do sistema operacional (prefers-reduced-motion).\n * Usado pelo Spinner e pelo Progress indeterminado para parar a animação sem depender só do\n * CSS, e assim ficar testável (mocke matchMedia para simular a preferência).\n */\n\nfunction subscribe(cb: () => void) {\n const mq = matchMedia('(prefers-reduced-motion: reduce)')\n mq.addEventListener('change', cb)\n return () => mq.removeEventListener('change', cb)\n}\n\nfunction getCurrent() {\n return matchMedia('(prefers-reduced-motion: reduce)').matches\n}\n\nexport function usePrefersReducedMotion() {\n return useSyncExternalStore(subscribe, getCurrent, () => false)\n}\n","import { Loader2 } from 'lucide-react'\nimport { usePrefersReducedMotion } from '@/hooks/use-reduced-motion'\nimport { cn } from '@/lib/cn'\n\n/*\n * Indicador de carregamento único do sistema (A17 do plano da v2). Substitui qualquer\n * `Loader2 animate-spin` solto em Button, Input e Select. Sem label é decorativo\n * (aria-hidden); com label, anuncia o carregamento a quem usa leitor de tela. Para de girar\n * quando o sistema pede menos movimento.\n */\n\nexport interface SpinnerProps {\n size?: 'sm' | 'md' | 'lg'\n /** Nome do que está carregando. Vazio (padrão): decorativo, sem anúncio de leitor de tela. */\n label?: string\n className?: string\n}\n\nconst sizeClass: Record<NonNullable<SpinnerProps['size']>, string> = {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n}\n\nexport function Spinner({ size = 'md', label, className }: SpinnerProps) {\n const reduced = usePrefersReducedMotion()\n const icon = (\n <Loader2 aria-hidden className={cn(sizeClass[size], reduced ? '' : 'animate-spin')} />\n )\n\n if (!label) {\n return (\n <span data-rendra=\"SPIN-001\" aria-hidden className={cn('inline-flex', className)}>\n {icon}\n </span>\n )\n }\n\n return (\n <span\n data-rendra=\"SPIN-001\"\n role=\"status\"\n className={cn('inline-flex items-center gap-2', className)}\n >\n {icon}\n <span className=\"sr-only\">{label}</span>\n </span>\n )\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport { Slot } from 'radix-ui'\nimport { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'\nimport { resolveCatalogCode } from '@/catalog/components'\nimport { Spinner } from '@/components/ui/spinner'\nimport { cn } from '@/lib/cn'\n\n/**\n * Botão único do sistema. Variantes, tamanho, ícone, só ícone, carregamento e\n * largura total são props: nunca crie outro arquivo de botão.\n * Altura vem de h-control-*: 44px ou mais no mobile, compacta a partir de md.\n */\nexport const buttonVariants = cva(\n 'inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-control font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow,transform] duration-150 ease-out select-none active:scale-98 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 motion-reduce:active:scale-100 [&_svg]:pointer-events-none [&_svg]:shrink-0',\n {\n variants: {\n variant: {\n // Hover troca para a cor de hover da marca e eleva um nível; pressionar comprime.\n primary:\n 'bg-primary text-primary-foreground shadow-sm hover:bg-primary-hover hover:text-primary-hover-foreground hover:shadow-md active:bg-primary-hover active:text-primary-hover-foreground active:shadow-sm',\n secondary:\n 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary-hover hover:text-secondary-hover-foreground hover:shadow-md active:bg-secondary-hover active:text-secondary-hover-foreground active:shadow-sm',\n outline:\n 'border border-input bg-card text-foreground hover:border-primary hover:bg-primary-soft hover:text-primary-soft-foreground active:bg-primary-soft',\n // Transparentes: no hover, assumem a cor primária do template.\n ghost:\n 'text-foreground hover:bg-primary-soft hover:text-primary-soft-foreground active:bg-primary-soft',\n destructive:\n 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive-hover hover:shadow-md active:bg-destructive-hover active:shadow-sm',\n link: 'min-h-touch min-w-touch text-primary-text underline-offset-4 hover:text-primary-hover hover:underline md:min-h-0 md:min-w-0',\n },\n size: {\n sm: 'h-control-sm px-3 text-sm [&_svg]:size-icon-sm',\n md: 'h-control-md px-4 text-sm [&_svg]:size-icon-sm',\n lg: 'h-control-lg px-6 text-base [&_svg]:size-icon-md',\n },\n iconOnly: { true: 'px-0', false: '' },\n fullWidth: { true: 'w-full', false: '' },\n },\n compoundVariants: [\n { iconOnly: true, size: 'sm', className: 'w-control-sm' },\n { iconOnly: true, size: 'md', className: 'w-control-md' },\n { iconOnly: true, size: 'lg', className: 'w-control-lg' },\n { variant: 'link', className: 'h-auto px-0' },\n ],\n defaultVariants: { variant: 'primary', size: 'md', iconOnly: false, fullWidth: false },\n },\n)\n\ntype Variants = VariantProps<typeof buttonVariants>\n\nexport interface ButtonProps\n extends ButtonHTMLAttributes<HTMLButtonElement>, Omit<Variants, 'iconOnly' | 'fullWidth'> {\n /** Ícone à esquerda do texto (ou o próprio conteúdo, com iconOnly). */\n icon?: ReactNode\n /** Ícone à direita do texto. */\n iconRight?: ReactNode\n /** Botão só com ícone: quadrado, e aria-label passa a ser obrigatório. */\n iconOnly?: boolean\n /** Mostra indicador, bloqueia cliques e marca aria-busy. */\n loading?: boolean\n /** Ocupa toda a largura do contêiner. */\n fullWidth?: boolean\n /** Renderiza o filho (ex.: <Link>) com a aparência de botão. */\n asChild?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n className,\n variant,\n size,\n icon,\n iconRight,\n iconOnly = false,\n loading = false,\n fullWidth = false,\n asChild = false,\n disabled,\n type = 'button',\n children,\n ...props\n },\n ref,\n) {\n if (import.meta.env.DEV && iconOnly && !props['aria-label']) {\n console.warn('Button com iconOnly precisa de aria-label.')\n }\n const classes = cn(buttonVariants({ variant, size, iconOnly, fullWidth }), className)\n const code = resolveCatalogCode('Button', { variant: variant ?? 'primary' })\n\n if (asChild) {\n return (\n <Slot.Root ref={ref} data-rendra={code} className={classes} {...props}>\n {children}\n </Slot.Root>\n )\n }\n\n // Ícone dentro do botão segue o tamanho do próprio botão (sm/md -> sm, lg -> md, como o\n // [&_svg]:size-icon-* de buttonVariants já fazia com o Loader2 solto).\n const leading = loading ? <Spinner size={size === 'lg' ? 'md' : 'sm'} /> : icon\n return (\n <button\n ref={ref}\n type={type}\n data-rendra={code}\n className={classes}\n disabled={disabled || loading}\n aria-busy={loading || undefined}\n {...props}\n >\n {leading}\n {iconOnly ? (loading ? null : children) : children}\n {!iconOnly && iconRight}\n </button>\n )\n})\n"],"mappings":";;;;;;;;;AAQA,SAAS,UAAU,IAAgB;CACjC,MAAM,KAAK,WAAW,kCAAkC;CACxD,GAAG,iBAAiB,UAAU,EAAE;CAChC,aAAa,GAAG,oBAAoB,UAAU,EAAE;AAClD;AAEA,SAAS,aAAa;CACpB,OAAO,WAAW,kCAAkC,CAAC,CAAC;AACxD;AAEA,SAAgB,0BAA0B;CACxC,OAAO,qBAAqB,WAAW,kBAAkB,KAAK;AAChE;;;ACFA,IAAM,YAA+D;CACnE,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,SAAgB,QAAQ,EAAE,OAAO,MAAM,OAAO,aAA2B;CACvE,MAAM,UAAU,wBAAwB;CACxC,MAAM,OACJ,oBAAC,SAAD;EAAS,eAAA;EAAY,WAAW,GAAG,UAAU,OAAO,UAAU,KAAK,cAAc;CAAI,CAAA;CAGvF,IAAI,CAAC,OACH,OACE,oBAAC,QAAD;EAAM,eAAY;EAAW,eAAA;EAAY,WAAW,GAAG,eAAe,SAAS;EAC5E,UAAA;CACG,CAAA;CAIV,OACE,qBAAC,QAAD;EACE,eAAY;EACZ,MAAK;EACL,WAAW,GAAG,kCAAkC,SAAS;EAH3D,UAAA,CAKG,MACD,oBAAC,QAAD;GAAM,WAAU;GAAW,UAAA;EAAY,CAAA,CACnC;;AAEV;;;;;;;;ACpCA,IAAa,iBAAiB,IAC5B,uaACA;CACE,UAAU;EACR,SAAS;GAEP,SACE;GACF,WACE;GACF,SACE;GAEF,OACE;GACF,aACE;GACF,MAAM;EACR;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EACA,UAAU;GAAE,MAAM;GAAQ,OAAO;EAAG;EACpC,WAAW;GAAE,MAAM;GAAU,OAAO;EAAG;CACzC;CACA,kBAAkB;EAChB;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,UAAU;GAAM,MAAM;GAAM,WAAW;EAAe;EACxD;GAAE,SAAS;GAAQ,WAAW;EAAc;CAC9C;CACA,iBAAiB;EAAE,SAAS;EAAW,MAAM;EAAM,UAAU;EAAO,WAAW;CAAM;AACvF,CACF;AAoBA,IAAa,SAAS,WAA2C,SAAS,OACxE,EACE,WACA,SACA,MACA,MACA,WACA,WAAW,OACX,UAAU,OACV,YAAY,OACZ,UAAU,OACV,UACA,OAAO,UACP,UACA,GAAG,SAEL,KACA;CAIA,MAAM,UAAU,GAAG,eAAe;EAAE;EAAS;EAAM;EAAU;CAAU,CAAC,GAAG,SAAS;CACpF,MAAM,OAAO,mBAAmB,UAAU,EAAE,SAAS,WAAW,UAAU,CAAC;CAE3E,IAAI,SACF,OACE,oBAAC,KAAK,MAAN;EAAgB;EAAK,eAAa;EAAM,WAAW;EAAS,GAAI;EAC7D;CACQ,CAAA;CAMf,MAAM,UAAU,UAAU,oBAAC,SAAD,EAAS,MAAM,SAAS,OAAO,OAAO,KAAO,CAAA,IAAI;CAC3E,OACE,qBAAC,UAAD;EACO;EACC;EACN,eAAa;EACb,WAAW;EACX,UAAU,YAAY;EACtB,aAAW,WAAW,KAAA;EACtB,GAAI;EAPN,UAAA;GASG;GACA,WAAY,UAAU,OAAO,WAAY;GACzC,CAAC,YAAY;EACR;;AAEZ,CAAC"}
package/dist/chart.js CHANGED
@@ -1,8 +1,8 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DVPheE99.js";
3
- import { p as resolveCatalogCode } from "./components-Cm2ezlPT.js";
4
- import { t as Button } from "./button-DIcpJwK9.js";
5
- import { n as useBreakpoint } from "./use-breakpoint-BmXOZtF9.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-BM6MSZpE.js";
3
+ import { p as resolveCatalogCode } from "./components-2fCoBfBU.js";
4
+ import { t as Button } from "./button-CO0x1yjg.js";
5
+ import { n as useBreakpoint } from "./use-breakpoint-BqhYKT3L.js";
6
6
  import { ArrowDown, BarChart3, List } from "lucide-react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { useId, useRef, useState } from "react";
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
3
3
  import { join, relative, sep } from "node:path";
4
4
  //#region src/cli/auditar.ts
@@ -1,5 +1,5 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as CATALOG } from "../components-Cm2ezlPT.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as CATALOG } from "../components-2fCoBfBU.js";
3
3
  //#region src/cli/codigos.ts
4
4
  /** Todo o catálogo, na ordem cadastrada. Sem duplicata: garantido por assertCatalogIntegrity. */
5
5
  function codigos() {
package/dist/cli/help.js CHANGED
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
  //#region src/cli/help.ts
3
3
  /** Linha final de autoria, igual em toda saída publicável (CLI, banner do pacote). */
4
4
  var AUTHOR_LINE = "Rendra Design System, por Bruno Magalhaes: brunomagalhaes.me · instagram.com/brunomagalhaes.me";
@@ -1,5 +1,5 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { f as getCatalogEntry } from "../components-Cm2ezlPT.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { f as getCatalogEntry } from "../components-2fCoBfBU.js";
3
3
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join, relative, sep } from "node:path";
5
5
  //#region src/cli/trocar.ts
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
  import { join } from "node:path";
3
3
  import { createRequire } from "node:module";
4
4
  import { pathToFileURL } from "node:url";
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
  import { clsx } from "clsx";
3
3
  import { extendTailwindMerge } from "tailwind-merge";
4
4
  //#region src/lib/cn.ts
@@ -19,4 +19,4 @@ function cn(...inputs) {
19
19
  //#endregion
20
20
  export { cn as t };
21
21
 
22
- //# sourceMappingURL=cn-DVPheE99.js.map
22
+ //# sourceMappingURL=cn-BM6MSZpE.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cn-DVPheE99.js","names":[],"sources":["../src/lib/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n// Ensina o tailwind-merge sobre os tokens próprios, para que text-xs e text-primary-text\n// não sejam tratados como conflito, e h-control-md substitua h-12 corretamente. text-label e\n// text-help são tamanho (rótulo e orientação do campo); text-label-foreground é cor.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', 'label', 'help'] }],\n },\n },\n})\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"],"mappings":";;;;AAMA,IAAM,UAAU,oBAAoB,EAClC,QAAQ,EACN,aAAa,EACX,aAAa,CAAC,EAAE,MAAM;CAAC;CAAM;CAAM;CAAQ;CAAM;CAAM;CAAO;CAAO;CAAS;AAAM,EAAE,CAAC,EACzF,EACF,EACF,CAAC;AAED,SAAgB,GAAG,GAAG,QAAsB;CAC1C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
1
+ {"version":3,"file":"cn-BM6MSZpE.js","names":[],"sources":["../src/lib/cn.ts"],"sourcesContent":["import { clsx, type ClassValue } from 'clsx'\nimport { extendTailwindMerge } from 'tailwind-merge'\n\n// Ensina o tailwind-merge sobre os tokens próprios, para que text-xs e text-primary-text\n// não sejam tratados como conflito, e h-control-md substitua h-12 corretamente. text-label e\n// text-help são tamanho (rótulo e orientação do campo); text-label-foreground é cor.\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', 'label', 'help'] }],\n },\n },\n})\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"],"mappings":";;;;AAMA,IAAM,UAAU,oBAAoB,EAClC,QAAQ,EACN,aAAa,EACX,aAAa,CAAC,EAAE,MAAM;CAAC;CAAM;CAAM;CAAQ;CAAM;CAAM;CAAO;CAAO;CAAS;AAAM,EAAE,CAAC,EACzF,EACF,EACF,CAAC;AAED,SAAgB,GAAG,GAAG,QAAsB;CAC1C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
  //#region src/catalog/components.ts
3
3
  /** Formato do código: de 3 a 4 letras maiúsculas, hífen, três dígitos. */
4
4
  var COMPONENT_CODE_PATTERN = /^[A-Z]{3,4}-\d{3}$/;
@@ -797,4 +797,4 @@ function assertCatalogIntegrity() {
797
797
  //#endregion
798
798
  export { catalogByComponent as a, findComponentsWithoutDefault as c, findPresetCollisions as d, getCatalogEntry as f, assertCatalogIntegrity as i, findDuplicateCodes as l, CATALOG_EXCLUDED_FILES as n, filesMissingCatalogEntry as o, resolveCatalogCode as p, COMPONENT_CODE_PATTERN as r, findComponentsWithMultipleDefaults as s, CATALOG as t, findInvalidFormatCodes as u };
799
799
 
800
- //# sourceMappingURL=components-Cm2ezlPT.js.map
800
+ //# sourceMappingURL=components-2fCoBfBU.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"components-Cm2ezlPT.js","names":[],"sources":["../src/catalog/components.ts"],"sourcesContent":["/**\n * Catálogo de códigos de componente (etapa 1.2.0-alpha.1 do plano da v2).\n *\n * Cada componente de `src/components/ui` (menos os internos `overlay-shell` e\n * `picker-panel`, e menos os arquivos de teste) tem ao menos um código aqui. Uma variante\n * visual relevante (a prop que muda o formato do componente, não tom nem tamanho) ganha o\n * seu próprio código, numerado 001, 002... na ordem em que aparece neste arquivo.\n *\n * O código é o que o cliente escolhe no `docs/BRIEFING_MODELO.md` (\"Componentes e\n * variantes\") e o que aparece na vitrine (`/componentes`), no `data-rendra` do elemento\n * raiz de cada componente e no `registry.json` (`meta.codes`).\n *\n * Formato: `^[A-Z]{3,4}-\\d{3}$` (ex.: \"ABA-001\", \"BTN-006\"). Nunca editar um código depois\n * de publicado: ele é o identificador estável de uma variante.\n */\n\nexport interface ComponentCatalogEntry {\n /** Formato ^[A-Z]{3,4}-\\d{3}$, ex.: \"ABA-001\". Único no catálogo inteiro. */\n code: string\n /** Nome em português do Brasil, ex.: \"Abas em linha\". */\n name: string\n /** Nome do componente React, ex.: \"Tabs\". */\n component: string\n /** Caminho relativo a partir de src/, ex.: \"components/ui/tabs.tsx\". */\n file: string\n /** Props (nome: valor) que definem esta variante especificamente. */\n variantProps: Record<string, string | number | boolean>\n /** Frase curta de quando usar esta variante, para o BRIEFING_MODELO. */\n whenToUse: string\n /**\n * Esta é a variante que o componente usa quando a prop não é passada (o valor padrão de\n * `variantProps` no próprio componente React, ex.: `variant = 'line'` em `tabs.tsx`)? No\n * máximo uma por componente (nenhuma, para o componente sem variante ou sem um padrão real,\n * como `Chart`, cuja prop `type` é obrigatória); um componente com mais de uma variante tem\n * exatamente uma marcada `true` (teste de integridade, `assertCatalogIntegrity`). Usada por\n * `rendra trocar`: um elemento sem a prop conta como esta variante, e trocar para uma\n * variante `isDefault` remove a prop em vez de escrever o valor por extenso.\n */\n isDefault?: true\n}\n\n/** Formato do código: de 3 a 4 letras maiúsculas, hífen, três dígitos. */\nexport const COMPONENT_CODE_PATTERN = /^[A-Z]{3,4}-\\d{3}$/\n\n/**\n * Arquivos de `src/components/ui` que não entram no catálogo: são peças internas, sem uso\n * direto em tela (nunca aparecem sozinhos numa tela, só dentro de outro componente).\n */\nexport const CATALOG_EXCLUDED_FILES = [\n 'components/ui/overlay-shell.tsx',\n 'components/ui/picker-panel.tsx',\n 'components/ui/sortable-handle.tsx',\n]\n\nexport const CATALOG: ComponentCatalogEntry[] = [\n // ---------------------------------------------------------------- accordion\n {\n code: 'ACRN-001',\n name: 'Acordeão',\n component: 'Accordion',\n file: 'components/ui/accordion.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar conteúdo que a pessoa abre um de cada vez, como perguntas frequentes.',\n },\n // ---------------------------------------------------------------- action-bar\n {\n code: 'ACB-001',\n name: 'Barra de ação',\n component: 'ActionBar',\n file: 'components/ui/action-bar.tsx',\n variantProps: {},\n whenToUse:\n 'Para os botões de salvar, cancelar e ações extras de um formulário, drawer ou modal.',\n },\n // ---------------------------------------------------------------- alert\n {\n code: 'ALRT-001',\n name: 'Alerta',\n component: 'Alert',\n file: 'components/ui/alert.tsx',\n variantProps: {},\n whenToUse: 'Para um aviso de sucesso, erro, atenção ou informação dentro do corpo da tela.',\n },\n // ---------------------------------------------------------------- avatar\n {\n code: 'AVT-001',\n name: 'Avatar',\n component: 'Avatar',\n file: 'components/ui/avatar.tsx',\n variantProps: {},\n whenToUse: 'Para a foto ou as iniciais de uma pessoa.',\n },\n {\n code: 'AVT-002',\n name: 'Avatar em grupo',\n component: 'AvatarGroup',\n file: 'components/ui/avatar.tsx',\n variantProps: {},\n whenToUse: 'Para mostrar várias pessoas sobrepostas, com contador do excedente.',\n },\n // ---------------------------------------------------------------- badge\n {\n code: 'BDG-001',\n name: 'Selo',\n component: 'Badge',\n file: 'components/ui/badge.tsx',\n variantProps: {},\n whenToUse: 'Para marcar situação, categoria ou contagem curta ao lado de um texto.',\n },\n // ---------------------------------------------------------------- brand-feedback-icon\n {\n code: 'BFI-001',\n name: 'Ícone de feedback da marca',\n component: 'BrandFeedbackIcon',\n file: 'components/ui/brand-feedback-icon.tsx',\n variantProps: {},\n whenToUse:\n 'Para o símbolo da marca tingido pela cor semântica em toast, alerta e telas de status.',\n },\n // ---------------------------------------------------------------- brand-logo\n {\n code: 'LOGO-001',\n name: 'Logotipo',\n component: 'BrandLogo',\n file: 'components/ui/brand-logo.tsx',\n variantProps: {},\n whenToUse: 'Para o logotipo da marca na sidebar, no header ou no painel do login.',\n },\n // ---------------------------------------------------------------- breadcrumb\n {\n code: 'BRD-001',\n name: 'Trilha responsiva',\n component: 'Breadcrumb',\n file: 'components/ui/breadcrumb.tsx',\n variantProps: { variant: 'responsive' },\n whenToUse: 'Dentro do corpo da página: trilha no desktop e botão voltar no celular.',\n isDefault: true,\n },\n {\n code: 'BRD-002',\n name: 'Trilha em texto',\n component: 'Breadcrumb',\n file: 'components/ui/breadcrumb.tsx',\n variantProps: { variant: 'trail' },\n whenToUse: 'No header do AppShell, abaixo do título, sempre em texto pequeno.',\n },\n // ---------------------------------------------------------------- button\n {\n code: 'BTN-001',\n name: 'Botão primário',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'primary' },\n whenToUse: 'Para a ação principal da tela, do formulário ou da barra de ações.',\n isDefault: true,\n },\n {\n code: 'BTN-002',\n name: 'Botão secundário',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'secondary' },\n whenToUse:\n 'Para uma segunda cor de destaque da marca, quando a primária já está em uso na tela.',\n },\n {\n code: 'BTN-003',\n name: 'Botão contornado',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'outline' },\n whenToUse: 'Para a ação de cancelar ou para ações secundárias ao lado de um botão primário.',\n },\n {\n code: 'BTN-004',\n name: 'Botão fantasma',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'ghost' },\n whenToUse: 'Para ações discretas dentro de cards, linhas de tabela e barras de ferramenta.',\n },\n {\n code: 'BTN-005',\n name: 'Botão destrutivo',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'destructive' },\n whenToUse: 'Para uma ação irreversível, como excluir ou encerrar.',\n },\n {\n code: 'BTN-006',\n name: 'Botão link',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'link' },\n whenToUse: 'Para uma ação com aparência de link de texto, dentro de uma frase ou de um rodapé.',\n },\n // ---------------------------------------------------------------- button-group\n {\n code: 'BTNG-001',\n name: 'Grupo de botões',\n component: 'ButtonGroup',\n file: 'components/ui/button-group.tsx',\n variantProps: {},\n whenToUse: 'Para botões encostados ou um controle segmentado de escolha única.',\n },\n // ---------------------------------------------------------------- calendar\n {\n code: 'CAL-001',\n name: 'Calendário',\n component: 'Calendar',\n file: 'components/ui/calendar.tsx',\n variantProps: {},\n whenToUse: 'Para agenda com visão de mês, semana, dia ou lista de eventos.',\n },\n // ---------------------------------------------------------------- card\n {\n code: 'CARD-001',\n name: 'Card',\n component: 'Card',\n file: 'components/ui/card.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar conteúdo de página com borda e sem sombra pesada.',\n },\n // ---------------------------------------------------------------- chart\n {\n code: 'CHT-001',\n name: 'Gráfico de linha',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'line' },\n whenToUse: 'Para mostrar a evolução de um valor ao longo do tempo.',\n // A prop `type` do Chart é obrigatória (sem valor padrão no componente): marcado aqui só\n // para satisfazer o teste de integridade (um padrão por componente com mais de uma\n // variante); \"rendra trocar\" nunca encontra um <Chart> sem `type`, porque não compila.\n isDefault: true,\n },\n {\n code: 'CHT-002',\n name: 'Gráfico de barras',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'bar' },\n whenToUse: 'Para comparar valores entre categorias.',\n },\n {\n code: 'CHT-003',\n name: 'Gráfico de área',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'area' },\n whenToUse: 'Para mostrar volume acumulado ao longo do tempo.',\n },\n {\n code: 'CHT-004',\n name: 'Gráfico de pizza',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'pie' },\n whenToUse: 'Para mostrar a proporção de poucas categorias dentro de um total.',\n },\n {\n code: 'CHT-005',\n name: 'Gráfico combinado',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'combo' },\n whenToUse: 'Para juntar barras e linha no mesmo gráfico, como receita e meta.',\n },\n {\n code: 'CHT-006',\n name: 'Velocímetro de meta',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'gauge' },\n whenToUse: 'Para o percentual de uma meta batida, com faixas de cor.',\n },\n {\n code: 'CHT-007',\n name: 'Funil',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'funnel' },\n whenToUse: 'Para etapas que afunilam, com a conversão entre elas.',\n },\n // ---------------------------------------------------------------- chat\n {\n code: 'CHAT-001',\n name: 'Lista de conversas',\n component: 'ConversationList',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para a coluna de conversas do atendimento.',\n },\n {\n code: 'CHAT-002',\n name: 'Linha do tempo da conversa',\n component: 'ChatThread',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para o histórico de mensagens de uma conversa aberta.',\n },\n {\n code: 'CHAT-003',\n name: 'Campo de mensagem',\n component: 'ChatComposer',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para escrever, anexar e enviar uma mensagem no atendimento.',\n },\n // ---------------------------------------------------------------- checkbox\n {\n code: 'CHK-001',\n name: 'Caixa de seleção',\n component: 'Checkbox',\n file: 'components/ui/checkbox.tsx',\n variantProps: {},\n whenToUse: 'Para uma escolha independente, ligada ou desligada (inclusive indeterminada).',\n },\n {\n code: 'CHK-002',\n name: 'Grupo de caixas de seleção',\n component: 'CheckboxGroup',\n file: 'components/ui/checkbox.tsx',\n variantProps: {},\n whenToUse: 'Para uma lista de opções fixas com \"selecionar todos\".',\n },\n // ---------------------------------------------------------------- checklist\n {\n code: 'CKLT-001',\n name: 'Lista de verificação',\n component: 'Checklist',\n file: 'components/ui/checklist.tsx',\n variantProps: {},\n whenToUse:\n 'Para uma lista de itens que a pessoa cria, renomeia, marca e remove, tudo por teclado.',\n },\n // ---------------------------------------------------------------- color-picker\n {\n code: 'COR-001',\n name: 'Seletor de cor',\n component: 'ColorPicker',\n file: 'components/ui/color-picker.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma cor entre amostras da marca ou uma cor livre por hexadecimal.',\n },\n // ---------------------------------------------------------------- data-toolbar\n {\n code: 'DTB-001',\n name: 'Barra de ferramentas da listagem',\n component: 'DataToolbar',\n file: 'components/ui/data-toolbar.tsx',\n variantProps: {},\n whenToUse: 'Para busca, filtros, colunas e a ação Novo, sempre dentro do card da tabela.',\n },\n // ---------------------------------------------------------------- date-picker\n {\n code: 'DTP-001',\n name: 'Seletor de data',\n component: 'DatePicker',\n file: 'components/ui/date-picker.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma data, um período ou um horário.',\n },\n // ---------------------------------------------------------------- document-viewer\n {\n code: 'DOC-001',\n name: 'Visualizador de documentos',\n component: 'DocumentViewer',\n file: 'components/ui/document-viewer.tsx',\n variantProps: {},\n whenToUse: 'Para abrir um PDF (contrato, nota fiscal, comprovante) sem sair da tela.',\n },\n // ---------------------------------------------------------------- drawer\n {\n code: 'GAV-001',\n name: 'Gaveta',\n component: 'Drawer',\n file: 'components/ui/drawer.tsx',\n variantProps: {},\n whenToUse: 'Para formulário ou detalhe de volume médio, até cerca de 12 campos.',\n },\n // ---------------------------------------------------------------- dropdown-menu\n {\n code: 'DDM-001',\n name: 'Menu suspenso',\n component: 'DropdownMenuContent',\n file: 'components/ui/dropdown-menu.tsx',\n variantProps: {},\n whenToUse: 'Para uma lista de ações ou opções que abre a partir de um botão ou de um item.',\n },\n // ---------------------------------------------------------------- empty-state\n {\n code: 'VAZ-001',\n name: 'Estado vazio',\n component: 'EmptyState',\n file: 'components/ui/empty-state.tsx',\n variantProps: {},\n whenToUse: 'Para quando uma lista, busca ou seção não tem nenhum dado ainda.',\n },\n // ---------------------------------------------------------------- error-page\n {\n code: 'ERRO-001',\n name: 'Tela de erro',\n component: 'ErrorPage',\n file: 'components/ui/error-page.tsx',\n variantProps: {},\n whenToUse: 'Para página não encontrada (404) ou falha do lado do servidor (500).',\n },\n // ---------------------------------------------------------------- field\n {\n code: 'FLD-001',\n name: 'Campo com rótulo e ajuda',\n component: 'Field',\n file: 'components/ui/field.tsx',\n variantProps: {},\n whenToUse: 'Para envolver qualquer controle de formulário com rótulo, ajuda e erro.',\n },\n {\n code: 'FLD-002',\n name: 'Rótulo',\n component: 'Label',\n file: 'components/ui/field.tsx',\n variantProps: {},\n whenToUse: 'Para um rótulo avulso, quando o Field completo não se aplica.',\n },\n // ---------------------------------------------------------------- form\n {\n code: 'FORM-001',\n name: 'Formulário',\n component: 'Form',\n file: 'components/ui/form.tsx',\n variantProps: {},\n whenToUse: 'Para envolver um formulário inteiro, ligado ao React Hook Form.',\n },\n {\n code: 'FORM-002',\n name: 'Seção de formulário',\n component: 'FormSection',\n file: 'components/ui/form.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar campos relacionados dentro de um card com título.',\n },\n // ---------------------------------------------------------------- image-cropper\n {\n code: 'CROP-001',\n name: 'Recorte de imagem',\n component: 'ImageCropper',\n file: 'components/ui/image-cropper.tsx',\n variantProps: {},\n whenToUse:\n 'Para recortar uma imagem numa proporção fixa antes de enviar (foto de perfil, capa).',\n },\n // ---------------------------------------------------------------- image-viewer\n {\n code: 'IMG-001',\n name: 'Visualizador de imagens',\n component: 'ImageViewer',\n file: 'components/ui/image-viewer.tsx',\n variantProps: {},\n whenToUse: 'Para abrir uma ou mais imagens em tela cheia, com legenda e navegação.',\n },\n // ---------------------------------------------------------------- info-hint\n {\n code: 'INFO-001',\n name: 'Dica de informação',\n component: 'InfoHint',\n file: 'components/ui/info-hint.tsx',\n variantProps: {},\n whenToUse: 'Para texto orientativo que não tem um título de tela, card ou seção ao lado.',\n },\n // ---------------------------------------------------------------- input\n {\n code: 'CAMP-001',\n name: 'Campo de texto',\n component: 'Input',\n file: 'components/ui/input.tsx',\n variantProps: {},\n whenToUse: 'Para texto, número, senha, telefone, moeda ou qualquer valor de uma linha.',\n },\n // ---------------------------------------------------------------- kanban\n {\n code: 'KANB-001',\n name: 'Quadro kanban',\n component: 'Kanban',\n file: 'components/ui/kanban.tsx',\n // variantProps fica vazio de propósito: a diferença para KANB-002 é a prop `dropTargets`\n // (um array de destinos de arraste), não um literal de string/número/booleano, e o\n // `rendra trocar` não tem como inventar um array, então ele recusa a troca com uma\n // mensagem clara (\"sem prop que os distinga\"), e a variante certa fica documentada aqui.\n variantProps: {},\n whenToUse: 'Para um funil de etapas com cartões que se movem entre colunas.',\n isDefault: true,\n },\n {\n code: 'KANB-002',\n name: 'Quadro kanban com destinos de arraste',\n component: 'Kanban',\n file: 'components/ui/kanban.tsx',\n variantProps: { hasDropTargets: true },\n whenToUse:\n 'Quando o card pode ir para uma ação além de outra coluna (ex.: \"Marcar como ganho\"), ' +\n 'mostrada numa barra durante o arraste e no menu \"Mover para\".',\n },\n // ---------------------------------------------------------------- list\n {\n code: 'LIST-001',\n name: 'Lista',\n component: 'List',\n file: 'components/ui/list.tsx',\n // variantProps fica vazio de propósito: a diferença para LIST-002 é a prop `onReorder`\n // (uma função de callback), não um literal de string/número/booleano, e o `rendra trocar`\n // não tem como inventar uma função, então ele recusa a troca com uma mensagem clara (\"sem\n // prop que os distinga\"), e a variante certa fica documentada aqui.\n variantProps: {},\n whenToUse: 'Para linhas simples com início, título, descrição e fim, navegáveis ou não.',\n isDefault: true,\n },\n {\n code: 'LIST-002',\n name: 'Lista reordenável',\n component: 'List',\n file: 'components/ui/list.tsx',\n variantProps: { sortable: true },\n whenToUse: 'Quando a pessoa pode mudar a ordem das linhas, por arraste ou pelas setas da alça.',\n },\n // ---------------------------------------------------------------- modal\n {\n code: 'MOD-001',\n name: 'Modal de confirmação',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'confirm' },\n whenToUse:\n 'Para confirmar uma ação com uma principal e uma de cancelar; type=\"destructive\" usa o ' +\n 'mesmo código, porque só muda a cor.',\n isDefault: true,\n },\n {\n code: 'MOD-002',\n name: 'Modal de formulário',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'form' },\n whenToUse: 'Para um formulário de até 3 campos simples, sem sair da tela atual.',\n },\n {\n code: 'MOD-003',\n name: 'Modal informativo',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'info' },\n whenToUse:\n 'Para só informar algo, sem decisão a tomar: um botão só, de largura total, que fecha o modal.',\n },\n // ---------------------------------------------------------------- otp-input\n {\n code: 'OTP-001',\n name: 'Código de verificação',\n component: 'OtpInput',\n file: 'components/ui/otp-input.tsx',\n variantProps: {},\n whenToUse: 'Para confirmar um código enviado por SMS ou e-mail (2FA).',\n },\n // ---------------------------------------------------------------- pagination\n {\n code: 'PAG-001',\n name: 'Paginação',\n component: 'Pagination',\n file: 'components/ui/pagination.tsx',\n variantProps: {},\n whenToUse: 'Para navegar entre páginas de uma listagem.',\n },\n // ---------------------------------------------------------------- popover\n {\n code: 'POP-001',\n name: 'Popover',\n component: 'PopoverContent',\n file: 'components/ui/popover.tsx',\n variantProps: {},\n whenToUse: 'Para um painel curto que abre perto de um botão, como um filtro.',\n },\n // ---------------------------------------------------------------- progress\n {\n code: 'PROG-001',\n name: 'Barra de progresso',\n component: 'Progress',\n file: 'components/ui/progress.tsx',\n variantProps: {},\n whenToUse: 'Para o andamento de uma tarefa ou de um envio.',\n },\n // ---------------------------------------------------------------- qr-code\n {\n code: 'QRC-001',\n name: 'Código QR',\n component: 'QrCode',\n file: 'components/ui/qr-code.tsx',\n variantProps: {},\n whenToUse: 'Para gerar um código QR (link, código de acesso) sem depender de serviço externo.',\n },\n // ---------------------------------------------------------------- radio-group\n {\n code: 'RDO-001',\n name: 'Opções em lista',\n component: 'RadioGroup',\n file: 'components/ui/radio-group.tsx',\n variantProps: { variant: 'list' },\n whenToUse: 'Para poucas opções simples, uma escolha só, em lista com bolinha e texto.',\n isDefault: true,\n },\n {\n code: 'RDO-002',\n name: 'Opções em cartões',\n component: 'RadioGroup',\n file: 'components/ui/radio-group.tsx',\n variantProps: { variant: 'cards' },\n whenToUse: 'Para opções com ícone e descrição, quando cada uma merece mais destaque.',\n },\n // ---------------------------------------------------------------- rating\n {\n code: 'RTG-001',\n name: 'Avaliação em estrelas',\n component: 'Rating',\n file: 'components/ui/rating.tsx',\n variantProps: { variant: 'stars' },\n whenToUse: 'Para uma nota rápida de satisfação, em estrelas.',\n isDefault: true,\n },\n {\n code: 'RTG-002',\n name: 'Avaliação em escala',\n component: 'Rating',\n file: 'components/ui/rating.tsx',\n variantProps: { variant: 'scale' },\n whenToUse: 'Para uma nota de 0 a 10 (NPS e pesquisas parecidas), com rótulo nas pontas.',\n },\n // ---------------------------------------------------------------- rendra-credit\n {\n code: 'CRED-001',\n name: 'Crédito Feito com Rendra',\n component: 'RendraCredit',\n file: 'components/ui/rendra-credit.tsx',\n variantProps: {},\n whenToUse:\n 'No rodapé da tela de login, discreto; pode ser removido (credit={false}) ou levado para ' +\n 'outro lugar visível, como uma tela Sobre.',\n },\n // ---------------------------------------------------------------- repeatable-field\n {\n code: 'REP-001',\n name: 'Campo repetível',\n component: 'RepeatableField',\n file: 'components/ui/repeatable-field.tsx',\n variantProps: {},\n whenToUse:\n 'Para uma lista de campos do mesmo tipo (telefones, e-mails, endereços), com um principal.',\n },\n // ---------------------------------------------------------------- rich-text-editor\n {\n code: 'RTE-001',\n name: 'Editor de texto rico',\n component: 'RichTextEditor',\n file: 'components/ui/rich-text-editor.tsx',\n variantProps: {},\n whenToUse: 'Para conteúdo com formatação, listas e imagens, como a descrição de um artigo.',\n },\n // ---------------------------------------------------------------- select\n {\n code: 'SEL-001',\n name: 'Select',\n component: 'Select',\n file: 'components/ui/select.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma ou mais opções de uma lista, com ou sem busca.',\n },\n // ---------------------------------------------------------------- separator\n {\n code: 'SEP-001',\n name: 'Separador',\n component: 'Separator',\n file: 'components/ui/separator.tsx',\n variantProps: {},\n whenToUse: 'Para dividir conteúdo dentro de um card, no lugar de um card dentro de card.',\n },\n // ---------------------------------------------------------------- skeleton\n {\n code: 'SKEL-001',\n name: 'Esqueleto de carregamento',\n component: 'Skeleton',\n file: 'components/ui/skeleton.tsx',\n variantProps: {},\n whenToUse: 'Para o estado de carregamento, com a mesma estrutura do conteúdo final.',\n },\n // ---------------------------------------------------------------- slider\n {\n code: 'SLD-001',\n name: 'Slider',\n component: 'Slider',\n file: 'components/ui/slider.tsx',\n variantProps: {},\n whenToUse: 'Para escolher um número ou uma faixa dentro de um intervalo, arrastando.',\n },\n // ---------------------------------------------------------------- spinner\n {\n code: 'SPIN-001',\n name: 'Indicador de carregamento',\n component: 'Spinner',\n file: 'components/ui/spinner.tsx',\n variantProps: {},\n whenToUse:\n 'Para um carregamento breve dentro de um botão, campo ou lista, sem barra de progresso.',\n },\n // ---------------------------------------------------------------- stat-card\n {\n code: 'STAT-001',\n name: 'Card de indicador',\n component: 'StatCard',\n file: 'components/ui/stat-card.tsx',\n variantProps: {},\n whenToUse: 'Para um número de destaque com variação, num painel ou dashboard.',\n },\n // ---------------------------------------------------------------- switch\n {\n code: 'SWT-001',\n name: 'Switch',\n component: 'Switch',\n file: 'components/ui/switch.tsx',\n variantProps: {},\n whenToUse: 'Para ligar ou desligar uma opção com efeito imediato.',\n },\n // ---------------------------------------------------------------- table\n {\n code: 'TAB-001',\n name: 'Tabela',\n component: 'Table',\n file: 'components/ui/table.tsx',\n variantProps: {},\n whenToUse: 'Para qualquer listagem de registros, com busca, filtros e paginação.',\n },\n // ---------------------------------------------------------------- tabs\n {\n code: 'ABA-001',\n name: 'Abas em linha',\n component: 'Tabs',\n file: 'components/ui/tabs.tsx',\n variantProps: { variant: 'line' },\n whenToUse: 'Padrão para dividir o conteúdo de uma tela em seções, com sublinhado na aba ativa.',\n isDefault: true,\n },\n {\n code: 'ABA-002',\n name: 'Abas em pílula',\n component: 'Tabs',\n file: 'components/ui/tabs.tsx',\n variantProps: { variant: 'pill' },\n whenToUse: 'Quando as abas precisam de mais destaque visual, como pílulas sobre fundo suave.',\n },\n // ---------------------------------------------------------------- textarea\n {\n code: 'TXT-001',\n name: 'Área de texto',\n component: 'Textarea',\n file: 'components/ui/textarea.tsx',\n variantProps: {},\n whenToUse: 'Para texto longo, com contador de caracteres opcional.',\n },\n // ---------------------------------------------------------------- timeline\n {\n code: 'TLN-001',\n name: 'Linha do tempo',\n component: 'Timeline',\n file: 'components/ui/timeline.tsx',\n variantProps: {},\n whenToUse: 'Para uma sequência de eventos em ordem, cada um com um tom semântico.',\n },\n // ---------------------------------------------------------------- toast\n {\n code: 'TST-001',\n name: 'Toast',\n component: 'toast',\n file: 'components/ui/toast.tsx',\n variantProps: {},\n whenToUse: 'Para uma confirmação rápida depois de uma ação, sem interromper a tela.',\n },\n // ---------------------------------------------------------------- tooltip\n {\n code: 'TIP-001',\n name: 'Tooltip',\n component: 'Tooltip',\n file: 'components/ui/tooltip.tsx',\n variantProps: {},\n whenToUse: 'Para informação complementar, nunca essencial, que aparece ao passar o mouse.',\n },\n // ---------------------------------------------------------------- upload\n {\n code: 'UPL-001',\n name: 'Upload',\n component: 'Upload',\n file: 'components/ui/upload.tsx',\n // A prop que distingue de UPL-002 é `layout` ('list' | 'gallery', padrão 'list'): declarar\n // o valor padrão aqui (em vez de {}) é o que permite ao `rendra trocar` inserir\n // layout=\"gallery\" num elemento sem a prop, ao trocar para UPL-002.\n variantProps: { layout: 'list' },\n whenToUse: 'Para arrastar e soltar ou escolher arquivos, com lista e progresso por arquivo.',\n isDefault: true,\n },\n {\n code: 'UPL-002',\n name: 'Upload em galeria',\n component: 'Upload',\n file: 'components/ui/upload.tsx',\n variantProps: { layout: 'gallery' },\n whenToUse: 'Para upload de fotos e vídeos, com miniatura em vez de linha de lista.',\n },\n // ---------------------------------------------------------------- widget-grid\n {\n code: 'WDG-001',\n name: 'Grade de widgets',\n component: 'WidgetGrid',\n file: 'components/ui/widget-grid.tsx',\n variantProps: {},\n whenToUse: 'Para um painel com blocos que a pessoa pode arrastar e redimensionar.',\n },\n // ---------------------------------------------------------------- wizard\n {\n code: 'WIZ-001',\n name: 'Wizard',\n component: 'Wizard',\n file: 'components/ui/wizard.tsx',\n variantProps: {},\n whenToUse: 'Para um cadastro longo dividido em etapas, com validação por etapa.',\n },\n {\n code: 'WIZ-002',\n name: 'Indicador de etapas',\n component: 'Stepper',\n file: 'components/ui/wizard.tsx',\n variantProps: {},\n whenToUse: 'Para mostrar o progresso em etapas por conta própria, sem o Wizard inteiro.',\n },\n]\n\n/** Um código específico do catálogo (ex.: para montar a vitrine ou o registry). */\nexport function getCatalogEntry(code: string): ComponentCatalogEntry | undefined {\n return CATALOG.find((entry) => entry.code === code)\n}\n\n/** Todos os códigos de um componente (ex.: as duas variantes do Tabs), na ordem cadastrada. */\nexport function catalogByComponent(component: string): ComponentCatalogEntry[] {\n return CATALOG.filter((entry) => entry.component === component)\n}\n\n/**\n * Resolve o código de catálogo de um componente a partir das props que definem a variante.\n * Sem variante (um código só, ou nenhuma prop bate), devolve o primeiro código cadastrado\n * para o componente. Usado pelos próprios componentes para calcular o `data-rendra`, sem\n * duplicar a tabela de variantes.\n */\nexport function resolveCatalogCode(component: string, props: Record<string, unknown> = {}): string {\n const entries = catalogByComponent(component)\n if (entries.length === 0) {\n throw new Error(`Catálogo: nenhum código cadastrado para o componente \"${component}\".`)\n }\n const match = entries.find(\n (entry) =>\n Object.keys(entry.variantProps).length > 0 &&\n Object.entries(entry.variantProps).every(([key, value]) => props[key] === value),\n )\n // entries.length > 0 já foi conferido acima: entries[0] sempre existe aqui.\n return (match ?? entries[0]!).code\n}\n\n/*\n * Regras de integridade (usadas pelo teste em components.test.ts e por assertCatalogIntegrity).\n * São funções puras, sem acesso a arquivo: recebem a lista de entradas e devolvem o que\n * está errado, para o teste poder provar a regra com uma lista fabricada, não só com o\n * catálogo real.\n */\n\n/** Código que aparece mais de uma vez na lista. */\nexport function findDuplicateCodes(entries: ComponentCatalogEntry[]): string[] {\n const seen = new Set<string>()\n const duplicates = new Set<string>()\n for (const entry of entries) {\n if (seen.has(entry.code)) duplicates.add(entry.code)\n seen.add(entry.code)\n }\n return [...duplicates]\n}\n\n/** Código fora do formato ^[A-Z]{3,4}-\\d{3}$. */\nexport function findInvalidFormatCodes(entries: ComponentCatalogEntry[]): string[] {\n return entries\n .filter((entry) => !COMPONENT_CODE_PATTERN.test(entry.code))\n .map((entry) => entry.code)\n}\n\n/**\n * Código que colidiria com um código de modelo (T1, C4, M5...) de src/config/presets.ts,\n * cujo padrão é /[TCM]\\d+/ (uma letra T, C ou M seguida direto de dígitos, sem hífen).\n */\nexport function findPresetCollisions(entries: ComponentCatalogEntry[]): string[] {\n return entries.filter((entry) => /[TCM]\\d+/.test(entry.code)).map((entry) => entry.code)\n}\n\n/** Arquivo (caminho relativo a src/) que não tem nenhuma entrada no catálogo. */\nexport function filesMissingCatalogEntry(\n files: string[],\n entries: ComponentCatalogEntry[],\n): string[] {\n const covered = new Set(entries.map((entry) => entry.file))\n return files.filter((file) => !covered.has(file))\n}\n\n/** Componente (nome) com mais de uma variante no catálogo, agrupadas na ordem cadastrada. */\nfunction groupByComponentWithMultipleVariants(\n entries: ComponentCatalogEntry[],\n): Map<string, ComponentCatalogEntry[]> {\n const byComponent = new Map<string, ComponentCatalogEntry[]>()\n for (const entry of entries) {\n const list = byComponent.get(entry.component) ?? []\n list.push(entry)\n byComponent.set(entry.component, list)\n }\n for (const [component, list] of byComponent) if (list.length <= 1) byComponent.delete(component)\n return byComponent\n}\n\n/**\n * Componente com mais de uma variante que não tem nenhuma marcada `isDefault` (fase 3, Lote\n * B: `rendra trocar` precisa saber qual variante um elemento sem a prop representa).\n */\nexport function findComponentsWithoutDefault(entries: ComponentCatalogEntry[]): string[] {\n const groups = groupByComponentWithMultipleVariants(entries)\n return [...groups]\n .filter(([, list]) => !list.some((entry) => entry.isDefault))\n .map(([component]) => component)\n}\n\n/** Componente com mais de uma variante marcada `isDefault` ao mesmo tempo (só pode uma). */\nexport function findComponentsWithMultipleDefaults(entries: ComponentCatalogEntry[]): string[] {\n const groups = groupByComponentWithMultipleVariants(entries)\n return [...groups]\n .filter(([, list]) => list.filter((entry) => entry.isDefault).length > 1)\n .map(([component]) => component)\n}\n\n/**\n * Lança se houver código duplicado, fora do formato, colidindo com um código de modelo, ou\n * componente com mais de uma variante sem exatamente uma marcada `isDefault`.\n */\nexport function assertCatalogIntegrity(): void {\n const duplicates = findDuplicateCodes(CATALOG)\n if (duplicates.length > 0) {\n throw new Error(`Catálogo: código duplicado: ${duplicates.join(', ')}.`)\n }\n const invalid = findInvalidFormatCodes(CATALOG)\n if (invalid.length > 0) {\n throw new Error(`Catálogo: código fora do formato ^[A-Z]{3,4}-\\\\d{3}$: ${invalid.join(', ')}.`)\n }\n const collisions = findPresetCollisions(CATALOG)\n if (collisions.length > 0) {\n throw new Error(\n `Catálogo: código colide com um código de modelo (T/C/M): ${collisions.join(', ')}.`,\n )\n }\n const semPadrao = findComponentsWithoutDefault(CATALOG)\n if (semPadrao.length > 0) {\n throw new Error(\n `Catálogo: componente com mais de uma variante sem nenhuma marcada isDefault: ${semPadrao.join(', ')}.`,\n )\n }\n const padraoDuplicado = findComponentsWithMultipleDefaults(CATALOG)\n if (padraoDuplicado.length > 0) {\n throw new Error(\n `Catálogo: componente com mais de uma variante marcada isDefault: ${padraoDuplicado.join(', ')}.`,\n )\n }\n}\n"],"mappings":"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;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WACE;CACJ;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM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"}
1
+ {"version":3,"file":"components-2fCoBfBU.js","names":[],"sources":["../src/catalog/components.ts"],"sourcesContent":["/**\n * Catálogo de códigos de componente (etapa 1.2.0-alpha.1 do plano da v2).\n *\n * Cada componente de `src/components/ui` (menos os internos `overlay-shell` e\n * `picker-panel`, e menos os arquivos de teste) tem ao menos um código aqui. Uma variante\n * visual relevante (a prop que muda o formato do componente, não tom nem tamanho) ganha o\n * seu próprio código, numerado 001, 002... na ordem em que aparece neste arquivo.\n *\n * O código é o que o cliente escolhe no `docs/BRIEFING_MODELO.md` (\"Componentes e\n * variantes\") e o que aparece na vitrine (`/componentes`), no `data-rendra` do elemento\n * raiz de cada componente e no `registry.json` (`meta.codes`).\n *\n * Formato: `^[A-Z]{3,4}-\\d{3}$` (ex.: \"ABA-001\", \"BTN-006\"). Nunca editar um código depois\n * de publicado: ele é o identificador estável de uma variante.\n */\n\nexport interface ComponentCatalogEntry {\n /** Formato ^[A-Z]{3,4}-\\d{3}$, ex.: \"ABA-001\". Único no catálogo inteiro. */\n code: string\n /** Nome em português do Brasil, ex.: \"Abas em linha\". */\n name: string\n /** Nome do componente React, ex.: \"Tabs\". */\n component: string\n /** Caminho relativo a partir de src/, ex.: \"components/ui/tabs.tsx\". */\n file: string\n /** Props (nome: valor) que definem esta variante especificamente. */\n variantProps: Record<string, string | number | boolean>\n /** Frase curta de quando usar esta variante, para o BRIEFING_MODELO. */\n whenToUse: string\n /**\n * Esta é a variante que o componente usa quando a prop não é passada (o valor padrão de\n * `variantProps` no próprio componente React, ex.: `variant = 'line'` em `tabs.tsx`)? No\n * máximo uma por componente (nenhuma, para o componente sem variante ou sem um padrão real,\n * como `Chart`, cuja prop `type` é obrigatória); um componente com mais de uma variante tem\n * exatamente uma marcada `true` (teste de integridade, `assertCatalogIntegrity`). Usada por\n * `rendra trocar`: um elemento sem a prop conta como esta variante, e trocar para uma\n * variante `isDefault` remove a prop em vez de escrever o valor por extenso.\n */\n isDefault?: true\n}\n\n/** Formato do código: de 3 a 4 letras maiúsculas, hífen, três dígitos. */\nexport const COMPONENT_CODE_PATTERN = /^[A-Z]{3,4}-\\d{3}$/\n\n/**\n * Arquivos de `src/components/ui` que não entram no catálogo: são peças internas, sem uso\n * direto em tela (nunca aparecem sozinhos numa tela, só dentro de outro componente).\n */\nexport const CATALOG_EXCLUDED_FILES = [\n 'components/ui/overlay-shell.tsx',\n 'components/ui/picker-panel.tsx',\n 'components/ui/sortable-handle.tsx',\n]\n\nexport const CATALOG: ComponentCatalogEntry[] = [\n // ---------------------------------------------------------------- accordion\n {\n code: 'ACRN-001',\n name: 'Acordeão',\n component: 'Accordion',\n file: 'components/ui/accordion.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar conteúdo que a pessoa abre um de cada vez, como perguntas frequentes.',\n },\n // ---------------------------------------------------------------- action-bar\n {\n code: 'ACB-001',\n name: 'Barra de ação',\n component: 'ActionBar',\n file: 'components/ui/action-bar.tsx',\n variantProps: {},\n whenToUse:\n 'Para os botões de salvar, cancelar e ações extras de um formulário, drawer ou modal.',\n },\n // ---------------------------------------------------------------- alert\n {\n code: 'ALRT-001',\n name: 'Alerta',\n component: 'Alert',\n file: 'components/ui/alert.tsx',\n variantProps: {},\n whenToUse: 'Para um aviso de sucesso, erro, atenção ou informação dentro do corpo da tela.',\n },\n // ---------------------------------------------------------------- avatar\n {\n code: 'AVT-001',\n name: 'Avatar',\n component: 'Avatar',\n file: 'components/ui/avatar.tsx',\n variantProps: {},\n whenToUse: 'Para a foto ou as iniciais de uma pessoa.',\n },\n {\n code: 'AVT-002',\n name: 'Avatar em grupo',\n component: 'AvatarGroup',\n file: 'components/ui/avatar.tsx',\n variantProps: {},\n whenToUse: 'Para mostrar várias pessoas sobrepostas, com contador do excedente.',\n },\n // ---------------------------------------------------------------- badge\n {\n code: 'BDG-001',\n name: 'Selo',\n component: 'Badge',\n file: 'components/ui/badge.tsx',\n variantProps: {},\n whenToUse: 'Para marcar situação, categoria ou contagem curta ao lado de um texto.',\n },\n // ---------------------------------------------------------------- brand-feedback-icon\n {\n code: 'BFI-001',\n name: 'Ícone de feedback da marca',\n component: 'BrandFeedbackIcon',\n file: 'components/ui/brand-feedback-icon.tsx',\n variantProps: {},\n whenToUse:\n 'Para o símbolo da marca tingido pela cor semântica em toast, alerta e telas de status.',\n },\n // ---------------------------------------------------------------- brand-logo\n {\n code: 'LOGO-001',\n name: 'Logotipo',\n component: 'BrandLogo',\n file: 'components/ui/brand-logo.tsx',\n variantProps: {},\n whenToUse: 'Para o logotipo da marca na sidebar, no header ou no painel do login.',\n },\n // ---------------------------------------------------------------- breadcrumb\n {\n code: 'BRD-001',\n name: 'Trilha responsiva',\n component: 'Breadcrumb',\n file: 'components/ui/breadcrumb.tsx',\n variantProps: { variant: 'responsive' },\n whenToUse: 'Dentro do corpo da página: trilha no desktop e botão voltar no celular.',\n isDefault: true,\n },\n {\n code: 'BRD-002',\n name: 'Trilha em texto',\n component: 'Breadcrumb',\n file: 'components/ui/breadcrumb.tsx',\n variantProps: { variant: 'trail' },\n whenToUse: 'No header do AppShell, abaixo do título, sempre em texto pequeno.',\n },\n // ---------------------------------------------------------------- button\n {\n code: 'BTN-001',\n name: 'Botão primário',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'primary' },\n whenToUse: 'Para a ação principal da tela, do formulário ou da barra de ações.',\n isDefault: true,\n },\n {\n code: 'BTN-002',\n name: 'Botão secundário',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'secondary' },\n whenToUse:\n 'Para uma segunda cor de destaque da marca, quando a primária já está em uso na tela.',\n },\n {\n code: 'BTN-003',\n name: 'Botão contornado',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'outline' },\n whenToUse: 'Para a ação de cancelar ou para ações secundárias ao lado de um botão primário.',\n },\n {\n code: 'BTN-004',\n name: 'Botão fantasma',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'ghost' },\n whenToUse: 'Para ações discretas dentro de cards, linhas de tabela e barras de ferramenta.',\n },\n {\n code: 'BTN-005',\n name: 'Botão destrutivo',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'destructive' },\n whenToUse: 'Para uma ação irreversível, como excluir ou encerrar.',\n },\n {\n code: 'BTN-006',\n name: 'Botão link',\n component: 'Button',\n file: 'components/ui/button.tsx',\n variantProps: { variant: 'link' },\n whenToUse: 'Para uma ação com aparência de link de texto, dentro de uma frase ou de um rodapé.',\n },\n // ---------------------------------------------------------------- button-group\n {\n code: 'BTNG-001',\n name: 'Grupo de botões',\n component: 'ButtonGroup',\n file: 'components/ui/button-group.tsx',\n variantProps: {},\n whenToUse: 'Para botões encostados ou um controle segmentado de escolha única.',\n },\n // ---------------------------------------------------------------- calendar\n {\n code: 'CAL-001',\n name: 'Calendário',\n component: 'Calendar',\n file: 'components/ui/calendar.tsx',\n variantProps: {},\n whenToUse: 'Para agenda com visão de mês, semana, dia ou lista de eventos.',\n },\n // ---------------------------------------------------------------- card\n {\n code: 'CARD-001',\n name: 'Card',\n component: 'Card',\n file: 'components/ui/card.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar conteúdo de página com borda e sem sombra pesada.',\n },\n // ---------------------------------------------------------------- chart\n {\n code: 'CHT-001',\n name: 'Gráfico de linha',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'line' },\n whenToUse: 'Para mostrar a evolução de um valor ao longo do tempo.',\n // A prop `type` do Chart é obrigatória (sem valor padrão no componente): marcado aqui só\n // para satisfazer o teste de integridade (um padrão por componente com mais de uma\n // variante); \"rendra trocar\" nunca encontra um <Chart> sem `type`, porque não compila.\n isDefault: true,\n },\n {\n code: 'CHT-002',\n name: 'Gráfico de barras',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'bar' },\n whenToUse: 'Para comparar valores entre categorias.',\n },\n {\n code: 'CHT-003',\n name: 'Gráfico de área',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'area' },\n whenToUse: 'Para mostrar volume acumulado ao longo do tempo.',\n },\n {\n code: 'CHT-004',\n name: 'Gráfico de pizza',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'pie' },\n whenToUse: 'Para mostrar a proporção de poucas categorias dentro de um total.',\n },\n {\n code: 'CHT-005',\n name: 'Gráfico combinado',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'combo' },\n whenToUse: 'Para juntar barras e linha no mesmo gráfico, como receita e meta.',\n },\n {\n code: 'CHT-006',\n name: 'Velocímetro de meta',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'gauge' },\n whenToUse: 'Para o percentual de uma meta batida, com faixas de cor.',\n },\n {\n code: 'CHT-007',\n name: 'Funil',\n component: 'Chart',\n file: 'components/ui/chart.tsx',\n variantProps: { type: 'funnel' },\n whenToUse: 'Para etapas que afunilam, com a conversão entre elas.',\n },\n // ---------------------------------------------------------------- chat\n {\n code: 'CHAT-001',\n name: 'Lista de conversas',\n component: 'ConversationList',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para a coluna de conversas do atendimento.',\n },\n {\n code: 'CHAT-002',\n name: 'Linha do tempo da conversa',\n component: 'ChatThread',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para o histórico de mensagens de uma conversa aberta.',\n },\n {\n code: 'CHAT-003',\n name: 'Campo de mensagem',\n component: 'ChatComposer',\n file: 'components/ui/chat.tsx',\n variantProps: {},\n whenToUse: 'Para escrever, anexar e enviar uma mensagem no atendimento.',\n },\n // ---------------------------------------------------------------- checkbox\n {\n code: 'CHK-001',\n name: 'Caixa de seleção',\n component: 'Checkbox',\n file: 'components/ui/checkbox.tsx',\n variantProps: {},\n whenToUse: 'Para uma escolha independente, ligada ou desligada (inclusive indeterminada).',\n },\n {\n code: 'CHK-002',\n name: 'Grupo de caixas de seleção',\n component: 'CheckboxGroup',\n file: 'components/ui/checkbox.tsx',\n variantProps: {},\n whenToUse: 'Para uma lista de opções fixas com \"selecionar todos\".',\n },\n // ---------------------------------------------------------------- checklist\n {\n code: 'CKLT-001',\n name: 'Lista de verificação',\n component: 'Checklist',\n file: 'components/ui/checklist.tsx',\n variantProps: {},\n whenToUse:\n 'Para uma lista de itens que a pessoa cria, renomeia, marca e remove, tudo por teclado.',\n },\n // ---------------------------------------------------------------- color-picker\n {\n code: 'COR-001',\n name: 'Seletor de cor',\n component: 'ColorPicker',\n file: 'components/ui/color-picker.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma cor entre amostras da marca ou uma cor livre por hexadecimal.',\n },\n // ---------------------------------------------------------------- data-toolbar\n {\n code: 'DTB-001',\n name: 'Barra de ferramentas da listagem',\n component: 'DataToolbar',\n file: 'components/ui/data-toolbar.tsx',\n variantProps: {},\n whenToUse: 'Para busca, filtros, colunas e a ação Novo, sempre dentro do card da tabela.',\n },\n // ---------------------------------------------------------------- date-picker\n {\n code: 'DTP-001',\n name: 'Seletor de data',\n component: 'DatePicker',\n file: 'components/ui/date-picker.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma data, um período ou um horário.',\n },\n // ---------------------------------------------------------------- document-viewer\n {\n code: 'DOC-001',\n name: 'Visualizador de documentos',\n component: 'DocumentViewer',\n file: 'components/ui/document-viewer.tsx',\n variantProps: {},\n whenToUse: 'Para abrir um PDF (contrato, nota fiscal, comprovante) sem sair da tela.',\n },\n // ---------------------------------------------------------------- drawer\n {\n code: 'GAV-001',\n name: 'Gaveta',\n component: 'Drawer',\n file: 'components/ui/drawer.tsx',\n variantProps: {},\n whenToUse: 'Para formulário ou detalhe de volume médio, até cerca de 12 campos.',\n },\n // ---------------------------------------------------------------- dropdown-menu\n {\n code: 'DDM-001',\n name: 'Menu suspenso',\n component: 'DropdownMenuContent',\n file: 'components/ui/dropdown-menu.tsx',\n variantProps: {},\n whenToUse: 'Para uma lista de ações ou opções que abre a partir de um botão ou de um item.',\n },\n // ---------------------------------------------------------------- empty-state\n {\n code: 'VAZ-001',\n name: 'Estado vazio',\n component: 'EmptyState',\n file: 'components/ui/empty-state.tsx',\n variantProps: {},\n whenToUse: 'Para quando uma lista, busca ou seção não tem nenhum dado ainda.',\n },\n // ---------------------------------------------------------------- error-page\n {\n code: 'ERRO-001',\n name: 'Tela de erro',\n component: 'ErrorPage',\n file: 'components/ui/error-page.tsx',\n variantProps: {},\n whenToUse: 'Para página não encontrada (404) ou falha do lado do servidor (500).',\n },\n // ---------------------------------------------------------------- field\n {\n code: 'FLD-001',\n name: 'Campo com rótulo e ajuda',\n component: 'Field',\n file: 'components/ui/field.tsx',\n variantProps: {},\n whenToUse: 'Para envolver qualquer controle de formulário com rótulo, ajuda e erro.',\n },\n {\n code: 'FLD-002',\n name: 'Rótulo',\n component: 'Label',\n file: 'components/ui/field.tsx',\n variantProps: {},\n whenToUse: 'Para um rótulo avulso, quando o Field completo não se aplica.',\n },\n // ---------------------------------------------------------------- form\n {\n code: 'FORM-001',\n name: 'Formulário',\n component: 'Form',\n file: 'components/ui/form.tsx',\n variantProps: {},\n whenToUse: 'Para envolver um formulário inteiro, ligado ao React Hook Form.',\n },\n {\n code: 'FORM-002',\n name: 'Seção de formulário',\n component: 'FormSection',\n file: 'components/ui/form.tsx',\n variantProps: {},\n whenToUse: 'Para agrupar campos relacionados dentro de um card com título.',\n },\n // ---------------------------------------------------------------- image-cropper\n {\n code: 'CROP-001',\n name: 'Recorte de imagem',\n component: 'ImageCropper',\n file: 'components/ui/image-cropper.tsx',\n variantProps: {},\n whenToUse:\n 'Para recortar uma imagem numa proporção fixa antes de enviar (foto de perfil, capa).',\n },\n // ---------------------------------------------------------------- image-viewer\n {\n code: 'IMG-001',\n name: 'Visualizador de imagens',\n component: 'ImageViewer',\n file: 'components/ui/image-viewer.tsx',\n variantProps: {},\n whenToUse: 'Para abrir uma ou mais imagens em tela cheia, com legenda e navegação.',\n },\n // ---------------------------------------------------------------- info-hint\n {\n code: 'INFO-001',\n name: 'Dica de informação',\n component: 'InfoHint',\n file: 'components/ui/info-hint.tsx',\n variantProps: {},\n whenToUse: 'Para texto orientativo que não tem um título de tela, card ou seção ao lado.',\n },\n // ---------------------------------------------------------------- input\n {\n code: 'CAMP-001',\n name: 'Campo de texto',\n component: 'Input',\n file: 'components/ui/input.tsx',\n variantProps: {},\n whenToUse: 'Para texto, número, senha, telefone, moeda ou qualquer valor de uma linha.',\n },\n // ---------------------------------------------------------------- kanban\n {\n code: 'KANB-001',\n name: 'Quadro kanban',\n component: 'Kanban',\n file: 'components/ui/kanban.tsx',\n // variantProps fica vazio de propósito: a diferença para KANB-002 é a prop `dropTargets`\n // (um array de destinos de arraste), não um literal de string/número/booleano, e o\n // `rendra trocar` não tem como inventar um array, então ele recusa a troca com uma\n // mensagem clara (\"sem prop que os distinga\"), e a variante certa fica documentada aqui.\n variantProps: {},\n whenToUse: 'Para um funil de etapas com cartões que se movem entre colunas.',\n isDefault: true,\n },\n {\n code: 'KANB-002',\n name: 'Quadro kanban com destinos de arraste',\n component: 'Kanban',\n file: 'components/ui/kanban.tsx',\n variantProps: { hasDropTargets: true },\n whenToUse:\n 'Quando o card pode ir para uma ação além de outra coluna (ex.: \"Marcar como ganho\"), ' +\n 'mostrada numa barra durante o arraste e no menu \"Mover para\".',\n },\n // ---------------------------------------------------------------- list\n {\n code: 'LIST-001',\n name: 'Lista',\n component: 'List',\n file: 'components/ui/list.tsx',\n // variantProps fica vazio de propósito: a diferença para LIST-002 é a prop `onReorder`\n // (uma função de callback), não um literal de string/número/booleano, e o `rendra trocar`\n // não tem como inventar uma função, então ele recusa a troca com uma mensagem clara (\"sem\n // prop que os distinga\"), e a variante certa fica documentada aqui.\n variantProps: {},\n whenToUse: 'Para linhas simples com início, título, descrição e fim, navegáveis ou não.',\n isDefault: true,\n },\n {\n code: 'LIST-002',\n name: 'Lista reordenável',\n component: 'List',\n file: 'components/ui/list.tsx',\n variantProps: { sortable: true },\n whenToUse: 'Quando a pessoa pode mudar a ordem das linhas, por arraste ou pelas setas da alça.',\n },\n // ---------------------------------------------------------------- modal\n {\n code: 'MOD-001',\n name: 'Modal de confirmação',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'confirm' },\n whenToUse:\n 'Para confirmar uma ação com uma principal e uma de cancelar; type=\"destructive\" usa o ' +\n 'mesmo código, porque só muda a cor.',\n isDefault: true,\n },\n {\n code: 'MOD-002',\n name: 'Modal de formulário',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'form' },\n whenToUse: 'Para um formulário de até 3 campos simples, sem sair da tela atual.',\n },\n {\n code: 'MOD-003',\n name: 'Modal informativo',\n component: 'Modal',\n file: 'components/ui/modal.tsx',\n variantProps: { type: 'info' },\n whenToUse:\n 'Para só informar algo, sem decisão a tomar: um botão só, de largura total, que fecha o modal.',\n },\n // ---------------------------------------------------------------- otp-input\n {\n code: 'OTP-001',\n name: 'Código de verificação',\n component: 'OtpInput',\n file: 'components/ui/otp-input.tsx',\n variantProps: {},\n whenToUse: 'Para confirmar um código enviado por SMS ou e-mail (2FA).',\n },\n // ---------------------------------------------------------------- pagination\n {\n code: 'PAG-001',\n name: 'Paginação',\n component: 'Pagination',\n file: 'components/ui/pagination.tsx',\n variantProps: {},\n whenToUse: 'Para navegar entre páginas de uma listagem.',\n },\n // ---------------------------------------------------------------- popover\n {\n code: 'POP-001',\n name: 'Popover',\n component: 'PopoverContent',\n file: 'components/ui/popover.tsx',\n variantProps: {},\n whenToUse: 'Para um painel curto que abre perto de um botão, como um filtro.',\n },\n // ---------------------------------------------------------------- progress\n {\n code: 'PROG-001',\n name: 'Barra de progresso',\n component: 'Progress',\n file: 'components/ui/progress.tsx',\n variantProps: {},\n whenToUse: 'Para o andamento de uma tarefa ou de um envio.',\n },\n // ---------------------------------------------------------------- qr-code\n {\n code: 'QRC-001',\n name: 'Código QR',\n component: 'QrCode',\n file: 'components/ui/qr-code.tsx',\n variantProps: {},\n whenToUse: 'Para gerar um código QR (link, código de acesso) sem depender de serviço externo.',\n },\n // ---------------------------------------------------------------- radio-group\n {\n code: 'RDO-001',\n name: 'Opções em lista',\n component: 'RadioGroup',\n file: 'components/ui/radio-group.tsx',\n variantProps: { variant: 'list' },\n whenToUse: 'Para poucas opções simples, uma escolha só, em lista com bolinha e texto.',\n isDefault: true,\n },\n {\n code: 'RDO-002',\n name: 'Opções em cartões',\n component: 'RadioGroup',\n file: 'components/ui/radio-group.tsx',\n variantProps: { variant: 'cards' },\n whenToUse: 'Para opções com ícone e descrição, quando cada uma merece mais destaque.',\n },\n // ---------------------------------------------------------------- rating\n {\n code: 'RTG-001',\n name: 'Avaliação em estrelas',\n component: 'Rating',\n file: 'components/ui/rating.tsx',\n variantProps: { variant: 'stars' },\n whenToUse: 'Para uma nota rápida de satisfação, em estrelas.',\n isDefault: true,\n },\n {\n code: 'RTG-002',\n name: 'Avaliação em escala',\n component: 'Rating',\n file: 'components/ui/rating.tsx',\n variantProps: { variant: 'scale' },\n whenToUse: 'Para uma nota de 0 a 10 (NPS e pesquisas parecidas), com rótulo nas pontas.',\n },\n // ---------------------------------------------------------------- rendra-credit\n {\n code: 'CRED-001',\n name: 'Crédito Feito com Rendra',\n component: 'RendraCredit',\n file: 'components/ui/rendra-credit.tsx',\n variantProps: {},\n whenToUse:\n 'No rodapé da tela de login, discreto; pode ser removido (credit={false}) ou levado para ' +\n 'outro lugar visível, como uma tela Sobre.',\n },\n // ---------------------------------------------------------------- repeatable-field\n {\n code: 'REP-001',\n name: 'Campo repetível',\n component: 'RepeatableField',\n file: 'components/ui/repeatable-field.tsx',\n variantProps: {},\n whenToUse:\n 'Para uma lista de campos do mesmo tipo (telefones, e-mails, endereços), com um principal.',\n },\n // ---------------------------------------------------------------- rich-text-editor\n {\n code: 'RTE-001',\n name: 'Editor de texto rico',\n component: 'RichTextEditor',\n file: 'components/ui/rich-text-editor.tsx',\n variantProps: {},\n whenToUse: 'Para conteúdo com formatação, listas e imagens, como a descrição de um artigo.',\n },\n // ---------------------------------------------------------------- select\n {\n code: 'SEL-001',\n name: 'Select',\n component: 'Select',\n file: 'components/ui/select.tsx',\n variantProps: {},\n whenToUse: 'Para escolher uma ou mais opções de uma lista, com ou sem busca.',\n },\n // ---------------------------------------------------------------- separator\n {\n code: 'SEP-001',\n name: 'Separador',\n component: 'Separator',\n file: 'components/ui/separator.tsx',\n variantProps: {},\n whenToUse: 'Para dividir conteúdo dentro de um card, no lugar de um card dentro de card.',\n },\n // ---------------------------------------------------------------- skeleton\n {\n code: 'SKEL-001',\n name: 'Esqueleto de carregamento',\n component: 'Skeleton',\n file: 'components/ui/skeleton.tsx',\n variantProps: {},\n whenToUse: 'Para o estado de carregamento, com a mesma estrutura do conteúdo final.',\n },\n // ---------------------------------------------------------------- slider\n {\n code: 'SLD-001',\n name: 'Slider',\n component: 'Slider',\n file: 'components/ui/slider.tsx',\n variantProps: {},\n whenToUse: 'Para escolher um número ou uma faixa dentro de um intervalo, arrastando.',\n },\n // ---------------------------------------------------------------- spinner\n {\n code: 'SPIN-001',\n name: 'Indicador de carregamento',\n component: 'Spinner',\n file: 'components/ui/spinner.tsx',\n variantProps: {},\n whenToUse:\n 'Para um carregamento breve dentro de um botão, campo ou lista, sem barra de progresso.',\n },\n // ---------------------------------------------------------------- stat-card\n {\n code: 'STAT-001',\n name: 'Card de indicador',\n component: 'StatCard',\n file: 'components/ui/stat-card.tsx',\n variantProps: {},\n whenToUse: 'Para um número de destaque com variação, num painel ou dashboard.',\n },\n // ---------------------------------------------------------------- switch\n {\n code: 'SWT-001',\n name: 'Switch',\n component: 'Switch',\n file: 'components/ui/switch.tsx',\n variantProps: {},\n whenToUse: 'Para ligar ou desligar uma opção com efeito imediato.',\n },\n // ---------------------------------------------------------------- table\n {\n code: 'TAB-001',\n name: 'Tabela',\n component: 'Table',\n file: 'components/ui/table.tsx',\n variantProps: {},\n whenToUse: 'Para qualquer listagem de registros, com busca, filtros e paginação.',\n },\n // ---------------------------------------------------------------- tabs\n {\n code: 'ABA-001',\n name: 'Abas em linha',\n component: 'Tabs',\n file: 'components/ui/tabs.tsx',\n variantProps: { variant: 'line' },\n whenToUse: 'Padrão para dividir o conteúdo de uma tela em seções, com sublinhado na aba ativa.',\n isDefault: true,\n },\n {\n code: 'ABA-002',\n name: 'Abas em pílula',\n component: 'Tabs',\n file: 'components/ui/tabs.tsx',\n variantProps: { variant: 'pill' },\n whenToUse: 'Quando as abas precisam de mais destaque visual, como pílulas sobre fundo suave.',\n },\n // ---------------------------------------------------------------- textarea\n {\n code: 'TXT-001',\n name: 'Área de texto',\n component: 'Textarea',\n file: 'components/ui/textarea.tsx',\n variantProps: {},\n whenToUse: 'Para texto longo, com contador de caracteres opcional.',\n },\n // ---------------------------------------------------------------- timeline\n {\n code: 'TLN-001',\n name: 'Linha do tempo',\n component: 'Timeline',\n file: 'components/ui/timeline.tsx',\n variantProps: {},\n whenToUse: 'Para uma sequência de eventos em ordem, cada um com um tom semântico.',\n },\n // ---------------------------------------------------------------- toast\n {\n code: 'TST-001',\n name: 'Toast',\n component: 'toast',\n file: 'components/ui/toast.tsx',\n variantProps: {},\n whenToUse: 'Para uma confirmação rápida depois de uma ação, sem interromper a tela.',\n },\n // ---------------------------------------------------------------- tooltip\n {\n code: 'TIP-001',\n name: 'Tooltip',\n component: 'Tooltip',\n file: 'components/ui/tooltip.tsx',\n variantProps: {},\n whenToUse: 'Para informação complementar, nunca essencial, que aparece ao passar o mouse.',\n },\n // ---------------------------------------------------------------- upload\n {\n code: 'UPL-001',\n name: 'Upload',\n component: 'Upload',\n file: 'components/ui/upload.tsx',\n // A prop que distingue de UPL-002 é `layout` ('list' | 'gallery', padrão 'list'): declarar\n // o valor padrão aqui (em vez de {}) é o que permite ao `rendra trocar` inserir\n // layout=\"gallery\" num elemento sem a prop, ao trocar para UPL-002.\n variantProps: { layout: 'list' },\n whenToUse: 'Para arrastar e soltar ou escolher arquivos, com lista e progresso por arquivo.',\n isDefault: true,\n },\n {\n code: 'UPL-002',\n name: 'Upload em galeria',\n component: 'Upload',\n file: 'components/ui/upload.tsx',\n variantProps: { layout: 'gallery' },\n whenToUse: 'Para upload de fotos e vídeos, com miniatura em vez de linha de lista.',\n },\n // ---------------------------------------------------------------- widget-grid\n {\n code: 'WDG-001',\n name: 'Grade de widgets',\n component: 'WidgetGrid',\n file: 'components/ui/widget-grid.tsx',\n variantProps: {},\n whenToUse: 'Para um painel com blocos que a pessoa pode arrastar e redimensionar.',\n },\n // ---------------------------------------------------------------- wizard\n {\n code: 'WIZ-001',\n name: 'Wizard',\n component: 'Wizard',\n file: 'components/ui/wizard.tsx',\n variantProps: {},\n whenToUse: 'Para um cadastro longo dividido em etapas, com validação por etapa.',\n },\n {\n code: 'WIZ-002',\n name: 'Indicador de etapas',\n component: 'Stepper',\n file: 'components/ui/wizard.tsx',\n variantProps: {},\n whenToUse: 'Para mostrar o progresso em etapas por conta própria, sem o Wizard inteiro.',\n },\n]\n\n/** Um código específico do catálogo (ex.: para montar a vitrine ou o registry). */\nexport function getCatalogEntry(code: string): ComponentCatalogEntry | undefined {\n return CATALOG.find((entry) => entry.code === code)\n}\n\n/** Todos os códigos de um componente (ex.: as duas variantes do Tabs), na ordem cadastrada. */\nexport function catalogByComponent(component: string): ComponentCatalogEntry[] {\n return CATALOG.filter((entry) => entry.component === component)\n}\n\n/**\n * Resolve o código de catálogo de um componente a partir das props que definem a variante.\n * Sem variante (um código só, ou nenhuma prop bate), devolve o primeiro código cadastrado\n * para o componente. Usado pelos próprios componentes para calcular o `data-rendra`, sem\n * duplicar a tabela de variantes.\n */\nexport function resolveCatalogCode(component: string, props: Record<string, unknown> = {}): string {\n const entries = catalogByComponent(component)\n if (entries.length === 0) {\n throw new Error(`Catálogo: nenhum código cadastrado para o componente \"${component}\".`)\n }\n const match = entries.find(\n (entry) =>\n Object.keys(entry.variantProps).length > 0 &&\n Object.entries(entry.variantProps).every(([key, value]) => props[key] === value),\n )\n // entries.length > 0 já foi conferido acima: entries[0] sempre existe aqui.\n return (match ?? entries[0]!).code\n}\n\n/*\n * Regras de integridade (usadas pelo teste em components.test.ts e por assertCatalogIntegrity).\n * São funções puras, sem acesso a arquivo: recebem a lista de entradas e devolvem o que\n * está errado, para o teste poder provar a regra com uma lista fabricada, não só com o\n * catálogo real.\n */\n\n/** Código que aparece mais de uma vez na lista. */\nexport function findDuplicateCodes(entries: ComponentCatalogEntry[]): string[] {\n const seen = new Set<string>()\n const duplicates = new Set<string>()\n for (const entry of entries) {\n if (seen.has(entry.code)) duplicates.add(entry.code)\n seen.add(entry.code)\n }\n return [...duplicates]\n}\n\n/** Código fora do formato ^[A-Z]{3,4}-\\d{3}$. */\nexport function findInvalidFormatCodes(entries: ComponentCatalogEntry[]): string[] {\n return entries\n .filter((entry) => !COMPONENT_CODE_PATTERN.test(entry.code))\n .map((entry) => entry.code)\n}\n\n/**\n * Código que colidiria com um código de modelo (T1, C4, M5...) de src/config/presets.ts,\n * cujo padrão é /[TCM]\\d+/ (uma letra T, C ou M seguida direto de dígitos, sem hífen).\n */\nexport function findPresetCollisions(entries: ComponentCatalogEntry[]): string[] {\n return entries.filter((entry) => /[TCM]\\d+/.test(entry.code)).map((entry) => entry.code)\n}\n\n/** Arquivo (caminho relativo a src/) que não tem nenhuma entrada no catálogo. */\nexport function filesMissingCatalogEntry(\n files: string[],\n entries: ComponentCatalogEntry[],\n): string[] {\n const covered = new Set(entries.map((entry) => entry.file))\n return files.filter((file) => !covered.has(file))\n}\n\n/** Componente (nome) com mais de uma variante no catálogo, agrupadas na ordem cadastrada. */\nfunction groupByComponentWithMultipleVariants(\n entries: ComponentCatalogEntry[],\n): Map<string, ComponentCatalogEntry[]> {\n const byComponent = new Map<string, ComponentCatalogEntry[]>()\n for (const entry of entries) {\n const list = byComponent.get(entry.component) ?? []\n list.push(entry)\n byComponent.set(entry.component, list)\n }\n for (const [component, list] of byComponent) if (list.length <= 1) byComponent.delete(component)\n return byComponent\n}\n\n/**\n * Componente com mais de uma variante que não tem nenhuma marcada `isDefault` (fase 3, Lote\n * B: `rendra trocar` precisa saber qual variante um elemento sem a prop representa).\n */\nexport function findComponentsWithoutDefault(entries: ComponentCatalogEntry[]): string[] {\n const groups = groupByComponentWithMultipleVariants(entries)\n return [...groups]\n .filter(([, list]) => !list.some((entry) => entry.isDefault))\n .map(([component]) => component)\n}\n\n/** Componente com mais de uma variante marcada `isDefault` ao mesmo tempo (só pode uma). */\nexport function findComponentsWithMultipleDefaults(entries: ComponentCatalogEntry[]): string[] {\n const groups = groupByComponentWithMultipleVariants(entries)\n return [...groups]\n .filter(([, list]) => list.filter((entry) => entry.isDefault).length > 1)\n .map(([component]) => component)\n}\n\n/**\n * Lança se houver código duplicado, fora do formato, colidindo com um código de modelo, ou\n * componente com mais de uma variante sem exatamente uma marcada `isDefault`.\n */\nexport function assertCatalogIntegrity(): void {\n const duplicates = findDuplicateCodes(CATALOG)\n if (duplicates.length > 0) {\n throw new Error(`Catálogo: código duplicado: ${duplicates.join(', ')}.`)\n }\n const invalid = findInvalidFormatCodes(CATALOG)\n if (invalid.length > 0) {\n throw new Error(`Catálogo: código fora do formato ^[A-Z]{3,4}-\\\\d{3}$: ${invalid.join(', ')}.`)\n }\n const collisions = findPresetCollisions(CATALOG)\n if (collisions.length > 0) {\n throw new Error(\n `Catálogo: código colide com um código de modelo (T/C/M): ${collisions.join(', ')}.`,\n )\n }\n const semPadrao = findComponentsWithoutDefault(CATALOG)\n if (semPadrao.length > 0) {\n throw new Error(\n `Catálogo: componente com mais de uma variante sem nenhuma marcada isDefault: ${semPadrao.join(', ')}.`,\n )\n }\n const padraoDuplicado = findComponentsWithMultipleDefaults(CATALOG)\n if (padraoDuplicado.length > 0) {\n throw new Error(\n `Catálogo: componente com mais de uma variante marcada isDefault: ${padraoDuplicado.join(', ')}.`,\n )\n }\n}\n"],"mappings":"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;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WACE;CACJ;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CACA;EACE,MAAM;EACN,MAAM;EACN,WAAW;EACX,MAAM;EACN,cAAc,CAAC;EACf,WAAW;CACb;CAEA;EACE,MAAM;EACN,MAAM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"}
@@ -1,4 +1,4 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
2
 
3
3
  @layer properties {
4
4
  @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or
@@ -1,7 +1,7 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DVPheE99.js";
3
- import { t as Button } from "./button-DIcpJwK9.js";
4
- import { t as EmptyState } from "./empty-state-DtyZAfYb.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-BM6MSZpE.js";
3
+ import { t as Button } from "./button-CO0x1yjg.js";
4
+ import { t as EmptyState } from "./empty-state-8rIk7QlP.js";
5
5
  import { ChevronLeft, ChevronRight, ExternalLink, ZoomIn, ZoomOut } from "lucide-react";
6
6
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
7
  import { useEffect, useRef, useState } from "react";
@@ -1,5 +1,5 @@
1
- /*! Rendra Design System v2.1.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
- import { t as cn } from "./cn-DVPheE99.js";
1
+ /*! Rendra Design System v2.2.1 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { t as cn } from "./cn-BM6MSZpE.js";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
4
  import { createContext, useContext } from "react";
5
5
  import { cva } from "class-variance-authority";
@@ -165,4 +165,4 @@ function EmptyState({ title, description, type = "info", actions, size = "defaul
165
165
  //#endregion
166
166
  export { BrandContext as a, useBrand as i, BrandFeedbackIcon as n, feedbackLabels as r, EmptyState as t };
167
167
 
168
- //# sourceMappingURL=empty-state-DtyZAfYb.js.map
168
+ //# sourceMappingURL=empty-state-8rIk7QlP.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"empty-state-DtyZAfYb.js","names":[],"sources":["../src/brand/brand-context.ts","../src/brand/use-brand.ts","../src/components/ui/brand-feedback-icon.tsx","../src/components/ui/empty-state.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { Shape } from '@/lib/shape'\nimport type { BrandConfig, PaletteConfig } from './types'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nexport interface BrandContextValue {\n brand: BrandConfig\n brands: BrandConfig[]\n setBrandId: (id: string) => void\n mode: ColorMode\n resolvedMode: 'light' | 'dark'\n setMode: (mode: ColorMode) => void\n /** Formato do template ativo (fixo por template). */\n shape: Shape\n /** Paleta de cores em uso (por padrão, a do próprio modelo). */\n palette: PaletteConfig\n palettes: PaletteConfig[]\n /** Usa a paleta de outro template; null volta às cores do próprio modelo. */\n setPaletteId: (id: string | null) => void\n /** Logotipo adequado para a sidebar com a paleta atual. */\n sidebarLogoVariant: 'light' | 'dark'\n}\n\nexport const BrandContext = createContext<BrandContextValue | null>(null)\n","import { useContext } from 'react'\nimport { BrandContext } from './brand-context'\n\nexport function useBrand() {\n const ctx = useContext(BrandContext)\n if (!ctx) throw new Error('useBrand precisa estar dentro de <BrandProvider>.')\n return ctx\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport type { SVGProps } from 'react'\nimport { useBrand } from '@/brand/use-brand'\nimport type { FeedbackType } from '@/brand/types'\nimport { cn } from '@/lib/cn'\n\nconst iconVariants = cva('relative inline-flex shrink-0', {\n variants: {\n type: {\n success: 'text-success',\n error: 'text-destructive',\n warning: 'text-warning',\n info: 'text-info',\n },\n size: {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n xl: 'size-12',\n '2xl': 'size-16',\n },\n },\n defaultVariants: { type: 'info', size: 'md' },\n})\n\nconst badgeTone: Record<FeedbackType, string> = {\n success: 'bg-success text-success-foreground',\n error: 'bg-destructive text-destructive-foreground',\n warning: 'bg-warning text-warning-foreground',\n info: 'bg-info text-info-foreground',\n}\n\nexport const feedbackLabels: Record<FeedbackType, string> = {\n success: 'Sucesso',\n error: 'Erro',\n warning: 'Atenção',\n info: 'Informação',\n}\n\n/** Glifos universais de status desenhados em traço, herdando currentColor. */\nfunction StatusGlyph({\n type,\n animated,\n ...props\n}: { type: FeedbackType; animated?: boolean } & SVGProps<SVGSVGElement>) {\n // pathLength=1 normaliza o traço: o desenho anima de 1 a 0 em qualquer glifo.\n const draw = animated\n ? ({ pathLength: 1, strokeDasharray: 1, className: 'animate-draw' } as const)\n : {}\n const common = {\n viewBox: '0 0 12 12',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n 'aria-hidden': true,\n ...props,\n }\n switch (type) {\n case 'success':\n return (\n <svg {...common}>\n <path d=\"M3 6.2 5.1 8.2 9 4\" {...draw} />\n </svg>\n )\n case 'error':\n return (\n <svg {...common}>\n <path d=\"M4 4l4 4M8 4 4 8\" {...draw} />\n </svg>\n )\n case 'warning':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v3.3M6 8.8v0\" {...draw} />\n </svg>\n )\n case 'info':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v0M6 5.5v3.3\" {...draw} />\n </svg>\n )\n }\n}\n\nexport interface BrandFeedbackIconProps extends Omit<VariantProps<typeof iconVariants>, 'type'> {\n type: FeedbackType\n /** Texto para leitores de tela. Sem ele o ícone é decorativo (o texto ao lado informa). */\n label?: string\n /**\n * Anima a entrada: o símbolo entra com mola e o glifo se desenha (check se formando,\n * X se riscando). O erro treme ao final. Respeita reduzir movimento.\n * Troque a key do componente para repetir a animação.\n */\n animated?: boolean\n className?: string\n}\n\n/**\n * Ícone de feedback da marca: o símbolo da marca em currentColor, tingido pela cor\n * semântica, com um selo de status para que o significado não dependa só da cor.\n * Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).\n */\nexport function BrandFeedbackIcon({\n type,\n size,\n label,\n animated = false,\n className,\n}: BrandFeedbackIconProps) {\n const { brand } = useBrand()\n const Custom = brand.feedbackIcons?.[type]\n const Symbol = brand.symbol\n const a11y = label\n ? ({ role: 'img', 'aria-label': label } as const)\n : ({ 'aria-hidden': true } as const)\n\n return (\n <span\n data-slot=\"feedback-icon\"\n data-rendra=\"BFI-001\"\n className={cn(\n iconVariants({ type, size }),\n animated && type === 'error' && 'animate-shake',\n className,\n )}\n {...a11y}\n >\n {Custom ? (\n <Custom className={cn('size-full', animated && 'animate-pop')} />\n ) : (\n <>\n <Symbol className={cn('size-full mask-status-cutout', animated && 'animate-pop')} />\n <span\n className={cn(\n 'absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full',\n badgeTone[type],\n animated && 'animate-pop anim-delay-1',\n )}\n >\n <StatusGlyph type={type} animated={animated} className=\"size-full\" />\n </span>\n </>\n )}\n </span>\n )\n}\n","import type { ReactNode } from 'react'\nimport type { FeedbackType } from '@/brand/types'\nimport { BrandFeedbackIcon } from '@/components/ui/brand-feedback-icon'\nimport { cn } from '@/lib/cn'\n\nexport interface EmptyStateProps {\n title: string\n description?: ReactNode\n /** Tipo do ícone de feedback da marca. info para \"sem dados\", error para falha. */\n type?: FeedbackType\n /** Ações: no máximo uma principal e uma secundária. */\n actions?: ReactNode\n /** compact: dentro de tabela ou card pequeno. */\n size?: 'default' | 'compact'\n className?: string\n}\n\n/** Estado vazio (e de erro) com o ícone de feedback da marca. */\nexport function EmptyState({\n title,\n description,\n type = 'info',\n actions,\n size = 'default',\n className,\n}: EmptyStateProps) {\n return (\n <div\n data-rendra=\"VAZ-001\"\n className={cn(\n 'flex flex-col items-center justify-center gap-4 text-center',\n size === 'compact' ? 'px-4 py-8' : 'px-4 py-12 md:py-16',\n className,\n )}\n >\n <span className=\"relative flex items-center justify-center\">\n <span aria-hidden className=\"absolute size-16 rounded-full bg-gradient-soft\" />\n <BrandFeedbackIcon\n type={type}\n size={size === 'compact' ? 'xl' : '2xl'}\n className=\"relative\"\n />\n </span>\n <div className=\"flex w-full flex-col gap-1\">\n <p className=\"text-base font-semibold\">{title}</p>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap justify-center gap-3\">{actions}</div>}\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"empty-state-8rIk7QlP.js","names":[],"sources":["../src/brand/brand-context.ts","../src/brand/use-brand.ts","../src/components/ui/brand-feedback-icon.tsx","../src/components/ui/empty-state.tsx"],"sourcesContent":["import { createContext } from 'react'\nimport type { Shape } from '@/lib/shape'\nimport type { BrandConfig, PaletteConfig } from './types'\n\nexport type ColorMode = 'light' | 'dark' | 'system'\n\nexport interface BrandContextValue {\n brand: BrandConfig\n brands: BrandConfig[]\n setBrandId: (id: string) => void\n mode: ColorMode\n resolvedMode: 'light' | 'dark'\n setMode: (mode: ColorMode) => void\n /** Formato do template ativo (fixo por template). */\n shape: Shape\n /** Paleta de cores em uso (por padrão, a do próprio modelo). */\n palette: PaletteConfig\n palettes: PaletteConfig[]\n /** Usa a paleta de outro template; null volta às cores do próprio modelo. */\n setPaletteId: (id: string | null) => void\n /** Logotipo adequado para a sidebar com a paleta atual. */\n sidebarLogoVariant: 'light' | 'dark'\n}\n\nexport const BrandContext = createContext<BrandContextValue | null>(null)\n","import { useContext } from 'react'\nimport { BrandContext } from './brand-context'\n\nexport function useBrand() {\n const ctx = useContext(BrandContext)\n if (!ctx) throw new Error('useBrand precisa estar dentro de <BrandProvider>.')\n return ctx\n}\n","import { cva, type VariantProps } from 'class-variance-authority'\nimport type { SVGProps } from 'react'\nimport { useBrand } from '@/brand/use-brand'\nimport type { FeedbackType } from '@/brand/types'\nimport { cn } from '@/lib/cn'\n\nconst iconVariants = cva('relative inline-flex shrink-0', {\n variants: {\n type: {\n success: 'text-success',\n error: 'text-destructive',\n warning: 'text-warning',\n info: 'text-info',\n },\n size: {\n sm: 'size-icon-sm',\n md: 'size-icon-md',\n lg: 'size-icon-lg',\n xl: 'size-12',\n '2xl': 'size-16',\n },\n },\n defaultVariants: { type: 'info', size: 'md' },\n})\n\nconst badgeTone: Record<FeedbackType, string> = {\n success: 'bg-success text-success-foreground',\n error: 'bg-destructive text-destructive-foreground',\n warning: 'bg-warning text-warning-foreground',\n info: 'bg-info text-info-foreground',\n}\n\nexport const feedbackLabels: Record<FeedbackType, string> = {\n success: 'Sucesso',\n error: 'Erro',\n warning: 'Atenção',\n info: 'Informação',\n}\n\n/** Glifos universais de status desenhados em traço, herdando currentColor. */\nfunction StatusGlyph({\n type,\n animated,\n ...props\n}: { type: FeedbackType; animated?: boolean } & SVGProps<SVGSVGElement>) {\n // pathLength=1 normaliza o traço: o desenho anima de 1 a 0 em qualquer glifo.\n const draw = animated\n ? ({ pathLength: 1, strokeDasharray: 1, className: 'animate-draw' } as const)\n : {}\n const common = {\n viewBox: '0 0 12 12',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 2,\n strokeLinecap: 'round' as const,\n strokeLinejoin: 'round' as const,\n 'aria-hidden': true,\n ...props,\n }\n switch (type) {\n case 'success':\n return (\n <svg {...common}>\n <path d=\"M3 6.2 5.1 8.2 9 4\" {...draw} />\n </svg>\n )\n case 'error':\n return (\n <svg {...common}>\n <path d=\"M4 4l4 4M8 4 4 8\" {...draw} />\n </svg>\n )\n case 'warning':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v3.3M6 8.8v0\" {...draw} />\n </svg>\n )\n case 'info':\n return (\n <svg {...common}>\n <path d=\"M6 3.2v0M6 5.5v3.3\" {...draw} />\n </svg>\n )\n }\n}\n\nexport interface BrandFeedbackIconProps extends Omit<VariantProps<typeof iconVariants>, 'type'> {\n type: FeedbackType\n /** Texto para leitores de tela. Sem ele o ícone é decorativo (o texto ao lado informa). */\n label?: string\n /**\n * Anima a entrada: o símbolo entra com mola e o glifo se desenha (check se formando,\n * X se riscando). O erro treme ao final. Respeita reduzir movimento.\n * Troque a key do componente para repetir a animação.\n */\n animated?: boolean\n className?: string\n}\n\n/**\n * Ícone de feedback da marca: o símbolo da marca em currentColor, tingido pela cor\n * semântica, com um selo de status para que o significado não dependa só da cor.\n * Cada tipo pode ser substituído por um SVG próprio em brand.config.ts (feedbackIcons).\n */\nexport function BrandFeedbackIcon({\n type,\n size,\n label,\n animated = false,\n className,\n}: BrandFeedbackIconProps) {\n const { brand } = useBrand()\n const Custom = brand.feedbackIcons?.[type]\n const Symbol = brand.symbol\n const a11y = label\n ? ({ role: 'img', 'aria-label': label } as const)\n : ({ 'aria-hidden': true } as const)\n\n return (\n <span\n data-slot=\"feedback-icon\"\n data-rendra=\"BFI-001\"\n className={cn(\n iconVariants({ type, size }),\n animated && type === 'error' && 'animate-shake',\n className,\n )}\n {...a11y}\n >\n {Custom ? (\n <Custom className={cn('size-full', animated && 'animate-pop')} />\n ) : (\n <>\n <Symbol className={cn('size-full mask-status-cutout', animated && 'animate-pop')} />\n <span\n className={cn(\n 'absolute right-0 bottom-0 flex size-1/2 items-center justify-center rounded-full',\n badgeTone[type],\n animated && 'animate-pop anim-delay-1',\n )}\n >\n <StatusGlyph type={type} animated={animated} className=\"size-full\" />\n </span>\n </>\n )}\n </span>\n )\n}\n","import type { ReactNode } from 'react'\nimport type { FeedbackType } from '@/brand/types'\nimport { BrandFeedbackIcon } from '@/components/ui/brand-feedback-icon'\nimport { cn } from '@/lib/cn'\n\nexport interface EmptyStateProps {\n title: string\n description?: ReactNode\n /** Tipo do ícone de feedback da marca. info para \"sem dados\", error para falha. */\n type?: FeedbackType\n /** Ações: no máximo uma principal e uma secundária. */\n actions?: ReactNode\n /** compact: dentro de tabela ou card pequeno. */\n size?: 'default' | 'compact'\n className?: string\n}\n\n/** Estado vazio (e de erro) com o ícone de feedback da marca. */\nexport function EmptyState({\n title,\n description,\n type = 'info',\n actions,\n size = 'default',\n className,\n}: EmptyStateProps) {\n return (\n <div\n data-rendra=\"VAZ-001\"\n className={cn(\n 'flex flex-col items-center justify-center gap-4 text-center',\n size === 'compact' ? 'px-4 py-8' : 'px-4 py-12 md:py-16',\n className,\n )}\n >\n <span className=\"relative flex items-center justify-center\">\n <span aria-hidden className=\"absolute size-16 rounded-full bg-gradient-soft\" />\n <BrandFeedbackIcon\n type={type}\n size={size === 'compact' ? 'xl' : '2xl'}\n className=\"relative\"\n />\n </span>\n <div className=\"flex w-full flex-col gap-1\">\n <p className=\"text-base font-semibold\">{title}</p>\n {description && <p className=\"text-sm text-muted-foreground\">{description}</p>}\n </div>\n {actions && <div className=\"flex flex-wrap justify-center gap-3\">{actions}</div>}\n </div>\n )\n}\n"],"mappings":"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"}