Para usuários finais: Veja
packages/react-lgpd-consent/QUICKSTART.mdEste guia fornece tudo o que você precisa para integrar rapidamente a bibliotecareact-lgpd-consentem seu projeto React.
Este guia é para desenvolvedores que querem contribuir ou fazer build local do monorepo.## 📦 Instalação
npm install react-lgpd-consent
react-lgpd-consent/yarn add react-lgpd-consent
├── packages/```
│ ├── core/ # @react-lgpd-consent/core (headless)
│ ├── mui/ # @react-lgpd-consent/mui (componentes)### Dependências Peer
│ └── react-lgpd-consent/ # react-lgpd-consent (agregador)
├── examples/```bash
│ ├── vite/ # Exemplo Vitenpm install @mui/material @mui/icons-material @emotion/react @emotion/styled
│ └── next-app-router/ # Exemplo Next.js```
└── docs/ # TypeDoc + Storybook (gh-pages)
```> ℹ️ **Modularização (v0.5.0+)**
>
## 🛠️ Setup Inicial> - `react-lgpd-consent` continua sendo o pacote principal publicado.
> - `@react-lgpd-consent/core` expõe apenas contextos, hooks e utilitários (sem UI).
### Pré-requisitos> - `@react-lgpd-consent/mui` publica os componentes baseados em Material-UI (atualmente um proxy).
- Node.js >= 20 (veja `.nvmrc`)> - Use os novos subpaths (`react-lgpd-consent/core`, `react-lgpd-consent/mui`) conforme a necessidade.
- pnpm >= 10
## 🎯 Uso Básico (30 segundos)
### Instalação
````tsx
```bashimport React from 'react'
# Clonar repositórioimport { ConsentProvider } from 'react-lgpd-consent'
git clone https://github.com/lucianoedipo/react-lgpd-consent.git
cd react-lgpd-consentfunction App() {
return (
# Instalar dependências (workspace completo)
# Rodar testes em todos os pacotes Minha Aplicação
pnpm test {/* Seu conteúdo aqui */}
```
## 📝 Comandos Principais )
### Build & Dev## 🧭 Storybook — quick note
```bash
# Build de todos os pacotesThis repository ships an interactive Storybook playground used for manual testing and visual exploration of components. Quick commands:
pnpm run build
- Run locally (development):
# Watch mode (rebuild automático)
pnpm run dev```bash
npm run storybook
# Build apenas do core````
pnpm --filter @react-lgpd-consent/core run build
- Build static Storybook (for publishing to GitHub Pages):
# Build apenas do mui
pnpm --filter @react-lgpd-consent/mui run build```bash
```npm run build-storybook
# Rodar todos os testes
pnpm test- The Storybook preview (`.storybook/preview.tsx`) applies a clean environment between stories (removes consent cookie and performs defensive DOM cleanup). Check that file when creating stories that rely on a clean initial state.
# Watch mode}
pnpm test -- --watch
export default App
# Coverage
pnpm test -- --coverage````
# Mutation testing (Stryker)## ⚡ Quickstarts: Next.js (App Router) e Vite
pnpm run mutation
```Os exemplos a seguir integram GTM/GA4 com Consent Mode v2 e garantem que nenhum script de tracking rode antes do consentimento. Eles também mostram como usar `ConsentScriptLoader` e sincronizar os sinais do Consent Mode via `gtag('consent', ...)`.
### Qualidade de Código- Exemplos completos: `examples/next-app-router/*`, `examples/vite/*`
```bash
# Type-check (todos os pacotes)### Next.js 14/15 — App Router (SSR-safe)
pnpm run type-check
1) Criar app Next e instalar deps
# Linting
pnpm run lint```bash
npm create next-app@latest my-app --ts --eslint --src-dir --app --no-tailwind --no-experimental-app
# Formataçãocd my-app
pnpm run formatnpm i react-lgpd-consent @mui/material @mui/icons-material @emotion/react @emotion/styled
pnpm run size-check2) Variáveis públicas no .env.local
# Storybook (playground interativo)```
pnpm run storybook
3) Copiar os arquivos do exemplo e ajustar imports
# Build Storybook
pnpm run build-storybook- De `examples/next-app-router/app/layout.tsx` → `app/layout.tsx`
- De `examples/next-app-router/app/page.tsx` → `app/page.tsx`
# Gerar TypeDoc- De `examples/next-app-router/components/ClientConsent.tsx` → `app/components/ClientConsent.tsx`
pnpm run docs:generate
```Observação: nos arquivos copiados, troque imports relativos para `import { ConsentProvider, ConsentScriptLoader } from 'react-lgpd-consent'`.
### Exemplos4) O que esse setup faz
```bash
# Rodar exemplo Vite- `ClientConsent` é um componente client-only (via `dynamic(..., { ssr: false })` no layout) que:
cd examples/vite - Injeta um stub de `dataLayer/gtag` e define `consent default = denied` para todos os sinais (ad_storage, ad_user_data, ad_personalization, analytics_storage).
pnpm dev - Sincroniza as mudanças do consentimento com `gtag('consent','update', ...)` mapeando as categorias: `analytics → analytics_storage`, `marketing → ad_*`.
- Usa `ConsentScriptLoader` para carregar GTM/GA4 somente quando as categorias permitirem. Antes disso, nenhum script de tracking é carregado.
# Rodar exemplo Next.js
cd examples/next-app-router5) Rodar
pnpm dev
``````bash
npm run dev
## 📤 Publicação (CI/CD)```
A publicação é **automática via CI** ao criar uma tag:Validação rápida:
- Acesse em aba anônima: a rede não carrega `gtm.js`/`gtag/js` até aceitar preferências.
```bash- Ao aceitar `analytics`, o GA4 é carregado; ao aceitar `marketing`, os sinais `ad_*` são atualizados como granted.
# 1. Atualizar versões nos packages (manualmente)
# packages/core/package.json - version: "0.5.1"### Vite (CSR)
# packages/mui/package.json - version: "0.5.1"
# packages/react-lgpd-consent/package.json - version: "0.5.3"1) Criar app Vite e instalar deps
# 2. Commitar mudanças```bash
git add .npm create vite@latest my-app -- --template react-ts
git commit -m "chore: bump versions to 0.5.x"cd my-app
npm i react-lgpd-consent @mui/material @mui/icons-material @emotion/react @emotion/styled
# 3. Criar tag (formato: vX.Y.Z)```
git tag v0.5.3
2) Variáveis no `.env`
# 4. Push com tags
git push --tags```
VITE_GA_ID=G-XXXXXXXXXX
# CI vai automaticamente:VITE_GTM_ID=GTM-XXXXXXX
# - Publicar @react-lgpd-consent/core@0.5.1```
# - Publicar @react-lgpd-consent/mui@0.5.1
# - Publicar react-lgpd-consent@0.5.33) Copiar os arquivos do exemplo e ajustar imports
examples/vite/index.html → index.html (não adicione scripts do GA/GTM aqui)Workflow: .github/workflows/publish-github-packages.yml- De examples/vite/src/main.tsx → src/main.tsx
examples/vite/src/App.tsx → src/App.tsxexamples/vite/src/consent/GtagConsent.tsx → src/consent/GtagConsent.tsximport { ... } from 'react-lgpd-consent'.Headless (sem UI)
Context, hooks, lógica de consentimento4) Rodar
Target: Desenvolvedores que querem criar UI própria
Size: ~86 KB```bash
npm run dev
Componentes Material-UI
CookieBanner, PreferencesModal, FloatingButtonValidação rápida:
Depende de @react-lgpd-consent/core- Ao abrir a app (em nova sessão), nenhum script de tracking é baixado até que o usuário consinta.
Target: Desenvolvedores que querem UI pronta- Preferências atualizam gtag('consent','update', ...) corretamente por categoria.
Size: ~104 KB
Re-exporta tudo do @react-lgpd-consent/mui
Melhor DX (developer experience)## 🍪 Categorias: definição, uso e exemplos
Target: Maioria dos usuários finais
Size: ~104 KBFonte única de verdade
Defina as categorias do seu projeto SOMENTE na prop categories do ConsentProvider.
useConsent, useCategories) e as integrações (ConsentScriptLoader) leem a mesma definição. Não declare categorias em outros lugares.Usar Conventional Commits:- Apenas a categoria necessary é obrigatória (e já é sempre incluída automaticamente).
feat: - Nova funcionalidade- Todas as demais (analytics, marketing, functional, etc.) são opcionais e dependem do seu caso de negócio. Se você não usa analytics/ads/chat, simplesmente não habilite essas categorias.
fix: - Correção de bug
docs: - DocumentaçãoComo “esconder” categorias que não uso?
test: - Testes- Basta não incluí-las em enabledCategories e não declará-las em customCategories. A UI não exibirá toggles para categorias ausentes.
chore: - Manutenção
refactor: - RefatoraçãoExemplo A — Somente necessários (mínimo, comum para apps internos/governo sem tracking)
### Códigoimport { ConsentProvider } from 'react-lgpd-consent'
- **Linguagem**: TypeScript (ESM)
- **Formatação**: Prettier (no semicolons, single quotes, width 100)export default function App() {
- **Linting**: ESLint flat config return (
- **Testes**: Jest + Testing Library <ConsentProvider
categories={{ enabledCategories: [] }}
### Documentação (TSDoc) texts={{ bannerMessage: 'Usamos apenas cookies necessários para funcionamento.' }}
- `@category` - Categoria do export >
- `@component` - Tag para componentes React <YourApp />
- `@example` - Exemplos de uso </ConsentProvider>
- `@param` - Parâmetros de função )
- `@returns` - Tipo de retorno}
- `@since` - Versão introduzida```
## 🐛 DebuggingExemplo B — Conjunto completo (site com analytics e marketing)
```tsx
### Problemas com peer dependenciesimport { ConsentProvider } from 'react-lgpd-consent'
```bash
pnpm install --legacy-peer-depsexport default function App() {
``` return (
<ConsentProvider
### Limpar build categories={{ enabledCategories: ['analytics', 'marketing', 'functional'] }}
```bash >
pnpm run clean <YourApp />
pnpm install </ConsentProvider>
pnpm run build )
```}
cd packages/react-lgpd-consent- Sempre passe `categories` explicitamente. Em DEV, a biblioteca avisa quando `categories` não foi configurado para evitar ambiguidades.
pnpm run size-check- Não classifique scripts de analytics/ads como “necessary” — use `ConsentScriptLoader` e categorias adequadas.
```- Em dúvidas, comece com “somente necessários” e evolua quando o negócio exigir outras categorias.
## 📖 Documentação Completa### 🔎 Validação de configuração (DEV)
- **API Reference**: https://lucianoedipo.github.io/react-lgpd-consentEm desenvolvimento, a biblioteca valida a configuração e mostra mensagens amigáveis no console. Nada disso impacta produção (onde só ocorre uma sanitização leve).
- **Storybook**: https://lucianoedipo.github.io/react-lgpd-consent/storybook
- **Contributing**: [`DEVELOPMENT.md`](DEVELOPMENT.md)Avisos comuns e como corrigir:
- **Architecture**: [`AGENTS.md`](AGENTS.md)- `Prop 'categories' não fornecida...` — defina `categories.enabledCategories` de forma explícita; exemplo mínimo: `categories={{ enabledCategories: [] }}`.
- `'necessary' é sempre incluída automaticamente` — remova `'necessary'` de `enabledCategories` (ela já é incluída por padrão).
## 🤝 Contribuindo- `IDs de categoria duplicados detectados` — revise `enabledCategories` e `customCategories` para garantir que não há IDs repetidos.
- `enabledCategories contém valores inválidos` — verifique se todos os itens são strings não vazias (IDs de categoria).
1. Fork o repositório- `customCategories: ... — ... deve ser uma string não vazia` — preencha `id`, `name` e `description` das categorias customizadas.
2. Crie uma branch: `git checkout -b feat/minha-feature`
3. Faça as mudanças e **teste localmente**:Notas:
```bash- Validação detalhada roda apenas em `NODE_ENV !== 'production'`.
pnpm run type-check- Em produção, a lib não carrega o validador; somente remove `'necessary'` se vier por engano, mantendo o comportamento seguro.
pnpm test
pnpm run lint## 🧱 SSR/Next.js (App Router) — Padrões seguros
pnpm run build
```Objetivo: evitar hydration mismatch, hooks em Server Components e vazamento de scripts.
4. Commit: `git commit -m "feat: adiciona nova feature"`
5. Push: `git push origin feat/minha-feature`Padrões recomendados
6. Abra um Pull Request- Envolva o app com o `ConsentProvider` apenas no cliente.
- Use `dynamic(() => import('./ClientConsent'), { ssr: false })` no `RootLayout` (Server Component) e mova hooks e efeitos para o componente cliente.
## 📄 Licença- Nenhum acesso a `window`/`document` no topo de módulo; use apenas dentro de `useEffect`.
- Inicialize Consent Mode v2 com `gtag('consent','default', denied)` antes de carregar GTM/GA4; depois, atualize sinais na mudança de preferências.
MIT - [@lucianoedipo](https://github.com/lucianoedipo)
Exemplo de RootLayout (Server) + Client wrapper
```tsx
// app/layout.tsx (Server Component)
import dynamic from 'next/dynamic'
const ClientConsent = dynamic(() => import('./components/ClientConsent'), { ssr: false })
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
)
}
// app/components/ClientConsent.tsx (Client Component)
'use client'
import React from 'react'
import { ConsentProvider, ConsentScriptLoader } from 'react-lgpd-consent'
import { COMMON_INTEGRATIONS } from 'react-lgpd-consent'
import { useConsent } from 'react-lgpd-consent'
function BootstrapConsentMode() {
React.useEffect(() => {
const w = window as any
w.dataLayer = w.dataLayer ?? []
w.gtag = w.gtag ?? ((...args: any[]) => w.dataLayer.push(args))
w.gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
})
}, [])
return null
}
function SyncConsentMode() {
const { consented, preferences } = useConsent()
React.useEffect(() => {
if (!consented) return
const w = window as any
w.gtag?.('consent', 'update', {
analytics_storage: preferences.analytics ? 'granted' : 'denied',
ad_storage: preferences.marketing ? 'granted' : 'denied',
ad_user_data: preferences.marketing ? 'granted' : 'denied',
ad_personalization: preferences.marketing ? 'granted' : 'denied',
})
}, [consented, preferences])
return null
}
export default function ClientConsent({ children }: { children: React.ReactNode }) {
const GA = process.env.NEXT_PUBLIC_GA_ID!
const GTM = process.env.NEXT_PUBLIC_GTM_ID!
return (
<ConsentProvider categories={{ enabledCategories: ['analytics', 'marketing', 'functional'] }} blocking>
<BootstrapConsentMode />
<SyncConsentMode />
<ConsentScriptLoader
integrations={[
COMMON_INTEGRATIONS.googleAnalytics({ measurementId: GA }),
COMMON_INTEGRATIONS.googleTagManager({ containerId: GTM }),
]}
/>
{children}
</ConsentProvider>
)
}
Ordem de provedores e estilos (MUI/Emotion)
CacheProvider (Emotion) ou StyledEngineProvider com injectFirstThemeProvider (MUI)CssBaselineConsentProvider (sem criar tema por padrão)Z-index e Portals
zIndex do tema; modals/portals padrão usam zIndex.modal = 1300.z-index: 1299; o Modal/Banner usa camadas ≥ 1300.theme.zIndex (ex.: appBar: 1200, modal: 1300+) ou os designTokens conforme a necessidade.Checklist SSR (evite hydration mismatch)
'use client' no topo).window/document/localStorage no topo de módulo (apenas em useEffect).dynamic(..., { ssr: false }) para wrappers que usam hooks e efeitos do consentimento.ConsentScriptLoader).<script> de GTM/GA4 em head/body; todo carregamento vem do loader.No modo bloqueante, o banner usa um backdrop para focar a atenção do usuário. Você pode controlar via design tokens:
<ConsentProvider
categories={{ enabledCategories: ['analytics'] }}
designTokens={{
layout: {
// false: transparente | 'auto': ajusta ao tema | string: cor custom (ex.: '#00000088')
backdrop: 'auto',
},
colors: {
// Se omitido, usa o palette do tema MUI (background.paper, text.primary)
// background: '#1e1e1e',
// text: '#ffffff',
},
}}
>
<App />
</ConsentProvider>
Se colors.background ou colors.text não forem fornecidos, a lib usa automaticamente theme.palette.background.paper e theme.palette.text.primary do MUI, garantindo compatibilidade com dark mode.
Durante o desenvolvimento, o console exibe um guia com:
Adicione categorias específicas do seu projeto (ex.: chat de suporte, players de vídeo, AB testing):
<ConsentProvider
categories={{
enabledCategories: ['analytics'],
customCategories: [
{ id: 'chat', name: 'Chat de Suporte', description: 'Widget de chat' },
{ id: 'video', name: 'Vídeo', description: 'Players incorporados' },
{ id: 'abTesting', name: 'A/B Testing', description: 'Experimentos de interface' },
],
}}
>
<App />
</ConsentProvider>
import { useConsent } from 'react-lgpd-consent'
function MyComponent() {
const { consent } = useConsent()
// Verificar se o usuário consentiu com categorias específicas
const canShowChat = consent?.preferences?.chat === true
const canLoadVideos = consent?.preferences?.video === true
const canRunABTests = consent?.preferences?.abTesting === true
return (
<div>
{canShowChat && <ChatWidget />}
{canLoadVideos && <VideoPlayer src="..." />}
{canRunABTests && <ABTestVariant />}
</div>
)
}
| Prop | Tipo | Obrigatória | Padrão | Descrição |
|---|---|---|---|---|
categories |
ProjectCategoriesConfig |
✅ Sim | - | Define as categorias de cookies do projeto |
texts |
Partial<ConsentTexts> |
❌ Não | Textos padrão PT-BR | Customiza textos da interface |
theme |
any |
❌ Não | Tema padrão | Tema Material-UI para os componentes |
designTokens |
DesignTokens |
❌ Não | Tokens padrão | Tokens de design para customização avançada |
blocking |
boolean |
❌ Não | false |
Exibe overlay bloqueando interação até decisão |
blockingStrategy |
'auto' | 'provider' |
❌ Não | 'auto' |
Estratégia de renderização do overlay |
hideBranding |
boolean |
❌ Não | false |
Oculta branding "fornecido por" |
onConsentGiven |
(state: ConsentState) => void |
❌ Não | - | Callback na primeira vez que usuário consente |
onPreferencesSaved |
(prefs: ConsentPreferences) => void |
❌ Não | - | Callback quando preferências são salvas |
disableDeveloperGuidance |
boolean |
❌ Não | false |
Desativa orientações no console |
disableFloatingPreferencesButton |
boolean |
❌ Não | false |
Desabilita botão flutuante de preferências |
CookieBannerComponent |
React.ComponentType<CustomCookieBannerProps> |
❌ Não | Banner padrão | Componente de banner customizado |
PreferencesModalComponent |
React.ComponentType<CustomPreferencesModalProps> |
❌ Não | Modal padrão | Componente de modal customizado |
FloatingPreferencesButtonComponent |
React.ComponentType<CustomFloatingPreferencesButtonProps> |
❌ Não | Botão padrão | Componente de botão flutuante customizado |
cookieBannerProps |
object |
❌ Não | {} |
Props adicionais para o banner |
preferencesModalProps |
object |
❌ Não | {} |
Props adicionais para o modal |
floatingPreferencesButtonProps |
object |
❌ Não | {} |
Props adicionais para o botão flutuante |
initialState |
ConsentState |
❌ Não | - | Estado inicial para hidratação SSR |
cookie |
Partial<ConsentCookieOptions> |
❌ Não | Opções padrão | Configurações do cookie de consentimento |
import React from 'react'
import { ConsentProvider, type CustomCookieBannerProps } from 'react-lgpd-consent'
// Componente de banner customizado
const MeuBannerCustomizado: React.FC<CustomCookieBannerProps> = ({
consented,
acceptAll,
rejectAll,
openPreferences,
texts,
blocking,
}) => {
return (
<div
style={{
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
backgroundColor: blocking ? 'red' : 'blue',
color: 'white',
padding: '1rem',
zIndex: 1000,
}}
>
<p>{texts.bannerMessage}</p>
<div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}>
<button onClick={acceptAll}>{texts.acceptAll}</button>
<button onClick={rejectAll}>{texts.declineAll}</button>
<button onClick={openPreferences}>{texts.preferences}</button>
</div>
</div>
)
}
// Usando o banner customizado
function App() {
return (
<ConsentProvider
categories={{ enabledCategories: ['analytics'] }}
CookieBannerComponent={MeuBannerCustomizado}
blocking={true}
>
<main>Minha App</main>
</ConsentProvider>
)
}
A biblioteca react-lgpd-consent não injeta um ThemeProvider global por conta própria. Ela foi projetada para herdar o tema do app quando um ThemeProvider do MUI está presente. Se você precisa garantir um tema de fallback apenas para os componentes da biblioteca, use a fábrica exportada createDefaultConsentTheme() e passe pelo prop theme do ConsentProvider:
import { ConsentProvider, createDefaultConsentTheme } from 'react-lgpd-consent'
;<ConsentProvider
theme={createDefaultConsentTheme()}
categories={{ enabledCategories: ['analytics'] }}
>
<App />
</ConsentProvider>
Isso evita alterações indesejadas no contexto do MUI do seu app e problemas de SSR.
import React from 'react'
import { ConsentProvider, type CustomPreferencesModalProps } from 'react-lgpd-consent'
const MeuModalCustomizado: React.FC<CustomPreferencesModalProps> = ({
preferences,
setPreferences,
closePreferences,
isModalOpen,
texts,
}) => {
if (!isModalOpen) return null
return (
<div
style={{
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
backgroundColor: 'white',
border: '2px solid #ccc',
borderRadius: '8px',
padding: '2rem',
zIndex: 2000,
minWidth: '400px',
}}
>
<h2>{texts.modalTitle}</h2>
<p>{texts.modalIntro}</p>
{/* Lista de categorias */}
<div style={{ margin: '1rem 0' }}>
{Object.entries(preferences).map(([category, enabled]) => (
<label key={category} style={{ display: 'block', marginBottom: '0.5rem' }}>
<input
type="checkbox"
checked={enabled}
onChange={(e) =>
setPreferences({
...preferences,
[category]: e.target.checked,
})
}
disabled={category === 'necessary'}
/>{' '}
{category === 'necessary' ? texts.necessaryAlwaysOn : `Cookies ${category}`}
</label>
))}
</div>
<div style={{ display: 'flex', gap: '1rem', justifyContent: 'flex-end' }}>
<button onClick={closePreferences}>Cancelar</button>
<button onClick={closePreferences}>{texts.save}</button>
</div>
</div>
)
}
function App() {
return (
<ConsentProvider
categories={{ enabledCategories: ['analytics', 'marketing'] }}
PreferencesModalComponent={MeuModalCustomizado}
>
<main>Minha App</main>
</ConsentProvider>
)
}
Para casos mais avançados onde você precisa exibir informações detalhadas sobre cada cookie (nome, finalidade, duração, provedor), use getCookiesInfoForCategory junto com useCategories:
import React from 'react'
import {
ConsentProvider,
useCategories,
getCookiesInfoForCategory,
type CustomPreferencesModalProps,
type CookieDescriptor,
} from 'react-lgpd-consent'
const ModalComDetalhesCookies: React.FC<CustomPreferencesModalProps> = ({
preferences,
setPreferences,
closePreferences,
isModalOpen,
texts,
}) => {
const { allCategories } = useCategories()
if (!isModalOpen) return null
// Simula integrações usadas no projeto (normalmente você teria isso em contexto)
const integracoesUsadas = ['google-analytics', 'google-tag-manager', 'mixpanel']
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 2000,
}}
>
<div
style={{
backgroundColor: 'white',
borderRadius: '12px',
padding: '2rem',
maxWidth: '800px',
maxHeight: '80vh',
overflow: 'auto',
boxShadow: '0 10px 25px rgba(0, 0, 0, 0.25)',
}}
>
<h2 style={{ marginBottom: '1rem', color: '#333' }}>{texts.modalTitle}</h2>
<p style={{ marginBottom: '2rem', color: '#666' }}>{texts.modalIntro}</p>
{/* Lista de categorias com detalhes dos cookies */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}>
{allCategories.map((categoria) => {
const cookiesDetalhados: CookieDescriptor[] = getCookiesInfoForCategory(
categoria.id as any,
integracoesUsadas,
)
return (
<div
key={categoria.id}
style={{
border: '1px solid #e0e0e0',
borderRadius: '8px',
padding: '1.5rem',
backgroundColor: '#fafafa',
}}
>
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '1rem' }}>
<input
type="checkbox"
id={`categoria-${categoria.id}`}
checked={preferences[categoria.id] || false}
onChange={(e) =>
setPreferences({
...preferences,
[categoria.id]: e.target.checked,
})
}
disabled={categoria.essential}
style={{ marginRight: '0.75rem', transform: 'scale(1.2)' }}
/>
<label
htmlFor={`categoria-${categoria.id}`}
style={{ fontSize: '1.1rem', fontWeight: 'bold', color: '#333' }}
>
{categoria.name}
{categoria.essential && (
<span style={{ fontSize: '0.8rem', color: '#888', marginLeft: '0.5rem' }}>
(sempre ativo)
</span>
)}
</label>
</div>
<p style={{ marginBottom: '1rem', color: '#666', fontSize: '0.95rem' }}>
{categoria.description}
</p>
{/* Lista de cookies desta categoria */}
{cookiesDetalhados.length > 0 && (
<details style={{ marginTop: '1rem' }}>
<summary
style={{
cursor: 'pointer',
fontWeight: '500',
color: '#4f46e5',
marginBottom: '0.5rem',
}}
>
Ver cookies desta categoria ({cookiesDetalhados.length})
</summary>
<div style={{ marginTop: '0.75rem', paddingLeft: '1rem' }}>
{cookiesDetalhados.map((cookie, index) => (
<div
key={`${cookie.name}-${index}`}
style={{
backgroundColor: 'white',
border: '1px solid #e5e5e5',
borderRadius: '6px',
padding: '1rem',
marginBottom: '0.75rem',
}}
>
<h4
style={{ margin: '0 0 0.5rem 0', color: '#333', fontSize: '0.95rem' }}
>
<code
style={{
backgroundColor: '#f3f4f6',
padding: '2px 6px',
borderRadius: '4px',
fontFamily: 'monospace',
}}
>
{cookie.name}
</code>
</h4>
{cookie.purpose && (
<p style={{ margin: '0.25rem 0', fontSize: '0.9rem', color: '#555' }}>
<strong>Finalidade:</strong> {cookie.purpose}
</p>
)}
{cookie.duration && (
<p style={{ margin: '0.25rem 0', fontSize: '0.9rem', color: '#555' }}>
<strong>Duração:</strong> {cookie.duration}
</p>
)}
{cookie.provider && (
<p style={{ margin: '0.25rem 0', fontSize: '0.9rem', color: '#555' }}>
<strong>Provedor:</strong> {cookie.provider}
</p>
)}
</div>
))}
</div>
</details>
)}
{/* Fallback para categorias sem cookies catalogados */}
{cookiesDetalhados.length === 0 &&
categoria.cookies &&
categoria.cookies.length > 0 && (
<div style={{ marginTop: '1rem', fontSize: '0.9rem', color: '#666' }}>
<strong>Padrões de cookies:</strong>{' '}
{categoria.cookies.map((pattern, i) => (
<code
key={i}
style={{
backgroundColor: '#f3f4f6',
padding: '2px 4px',
borderRadius: '3px',
marginRight: '0.5rem',
fontFamily: 'monospace',
fontSize: '0.8rem',
}}
>
{pattern}
</code>
))}
</div>
)}
</div>
)
})}
</div>
{/* Botões de ação */}
<div
style={{
display: 'flex',
gap: '1rem',
justifyContent: 'flex-end',
marginTop: '2rem',
paddingTop: '1rem',
borderTop: '1px solid #e0e0e0',
}}
>
<button
onClick={closePreferences}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#6b7280',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '0.95rem',
}}
>
Cancelar
</button>
<button
onClick={closePreferences}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#4f46e5',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontSize: '0.95rem',
}}
>
{texts.save}
</button>
</div>
</div>
</div>
)
}
function AppComModalAvancado() {
return (
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'marketing', 'functional'],
}}
PreferencesModalComponent={ModalComDetalhesCookies}
// Especifique as integrações para obter informações detalhadas dos cookies
scriptIntegrations={[
{ id: 'google-analytics', config: { measurementId: 'GA_MEASUREMENT_ID' } },
{ id: 'google-tag-manager', config: { containerId: 'GTM-XXXXXXX' } },
{ id: 'mixpanel', config: { token: 'MIXPANEL_TOKEN' } },
]}
>
<main>Minha App com Modal Avançado</main>
</ConsentProvider>
)
}
useCategories(): Hook que retorna informações sobre todas as categorias ativasgetCookiesInfoForCategory(categoryId, integrations): Função que retorna detalhes completos dos cookiesCookieDescriptor: Interface TypeScript com name, purpose, duration, provider<details> expansívelimport React from 'react'
import { useOpenPreferencesModal, useConsent } from 'react-lgpd-consent'
function MeuComponente() {
const openPreferences = useOpenPreferencesModal()
const { preferences, acceptAll, rejectAll } = useConsent()
return (
<div>
<h3>Status atual: {preferences.analytics ? 'Analytics ativo' : 'Analytics inativo'}</h3>
<button onClick={openPreferences}>⚙️ Gerenciar Preferências</button>
<button onClick={acceptAll}>✅ Aceitar Todos</button>
<button onClick={rejectAll}>❌ Recusar Todos</button>
</div>
)
}
<!-- Em templates HTML, emails ou widgets externos -->
<button onclick="window.openPreferencesModal?.()">Configurar Cookies</button>
<script>
// Ou em JavaScript puro
function abrirConfiguracoesCookies() {
if (window.openPreferencesModal) {
window.openPreferencesModal()
} else {
console.warn('Sistema de consentimento não carregado')
}
}
// Verificar se função está disponível
if (typeof window.openPreferencesModal === 'function') {
console.log('✅ Sistema de consentimento disponível')
}
</script>
import { setDebugLogging, LogLevel } from 'react-lgpd-consent'
// Ativar todos os logs (desenvolvimento)
setDebugLogging(true, LogLevel.DEBUG)
// Apenas logs importantes (staging)
setDebugLogging(true, LogLevel.INFO)
// Apenas erros (produção)
setDebugLogging(true, LogLevel.ERROR)
// Desativar completamente
setDebugLogging(false)
// Em desenvolvimento, você verá logs como:
// [🍪 LGPD-CONSENT] 🔧 Categorias Ativas (para UI customizada)
// [🍪 LGPD-CONSENT] ℹ️ User accepted all cookies
// [🍪 LGPD-CONSENT] 🐛 Category preference changed: analytics = true
import { useConsent } from 'react-lgpd-consent'
function DebugPanel() {
const { preferences, consented } = useConsent()
// Apenas mostrar em desenvolvimento
if (process.env.NODE_ENV !== 'development') return null
return (
<div
style={{
position: 'fixed',
top: 0,
right: 0,
background: 'rgba(0,0,0,0.8)',
color: 'white',
padding: '1rem',
fontSize: '12px',
fontFamily: 'monospace',
}}
>
<h4>🍪 Debug LGPD</h4>
<p>Consentimento: {consented ? '✅' : '❌'}</p>
<pre>{JSON.stringify(preferences, null, 2)}</pre>
</div>
)
}
import React from 'react'
import { ThemeProvider, createTheme } from '@mui/material/styles'
import CssBaseline from '@mui/material/CssBaseline'
import { ConsentProvider } from 'react-lgpd-consent'
// Seu tema personalizado
const meuTema = createTheme({
palette: {
primary: {
main: '#1976d2',
},
secondary: {
main: '#dc004e',
},
},
typography: {
fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
},
})
// Tema específico para componentes de consentimento (opcional)
const temaConsentimento = createTheme({
...meuTema,
components: {
MuiPaper: {
styleOverrides: {
root: {
borderRadius: 12,
},
},
},
MuiButton: {
styleOverrides: {
root: {
textTransform: 'none',
fontWeight: 600,
},
},
},
},
})
function App() {
return (
<ThemeProvider theme={meuTema}>
<CssBaseline />
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'marketing', 'advertising'],
}}
theme={temaConsentimento} // Tema específico para consentimento
texts={{
bannerMessage: 'Utilizamos cookies para personalizar sua experiência.',
acceptAll: 'Aceitar Todos',
declineAll: 'Apenas Necessários',
preferences: 'Personalizar',
}}
onConsentGiven={(state) => {
console.log('✅ Consentimento dado:', state.preferences)
}}
>
<main>
<h1>Minha Aplicação com Material-UI</h1>
{/* Seus componentes aqui */}
</main>
</ConsentProvider>
</ThemeProvider>
)
}
export default App
import { ConsentProvider, type DesignTokens } from 'react-lgpd-consent'
const meusTokens: DesignTokens = {
colors: {
primary: '#6366f1',
secondary: '#f59e0b',
background: '#ffffff',
text: '#1f2937',
border: '#e5e7eb',
},
layout: {
borderRadius: '12px',
spacing: '1rem',
backdrop: 'rgba(0, 0, 0, 0.6)', // ou false para transparente
},
typography: {
fontFamily: '"Inter", system-ui, sans-serif',
fontSize: '14px',
fontWeight: '500',
},
}
function App() {
return (
<ConsentProvider
categories={{ enabledCategories: ['analytics'] }}
designTokens={meusTokens}
blocking={true}
blockingStrategy="provider"
>
<main>Minha App</main>
</ConsentProvider>
)
}
import { ConsentProvider } from 'react-lgpd-consent'
function App() {
return (
<ConsentProvider
categories={{ enabledCategories: ['analytics'] }}
cookie={{
name: 'meu_app_consent', // Nome customizado
maxAge: 365 * 24 * 60 * 60, // 1 ano em segundos
domain: '.meudominio.com.br', // Cookie compartilhado entre subdomínios
secure: true, // Apenas HTTPS
sameSite: 'Lax', // Política SameSite
}}
>
<main>Minha App</main>
</ConsentProvider>
)
}
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'marketing', 'advertising'],
}}
texts={{
bannerMessage:
'Usamos cookies para melhorar sua experiência de compra e exibir ofertas personalizadas.',
acceptAll: 'Aceitar e continuar',
declineAll: 'Apenas essenciais',
}}
onConsentGiven={(state) => {
// Inicializar ferramentas baseado no consentimento
if (state.preferences.analytics) {
// gtag('config', 'GA_MEASUREMENT_ID')
}
if (state.preferences.marketing) {
// fbq('init', 'FACEBOOK_PIXEL_ID')
}
}}
>
{/* Sua loja */}
</ConsentProvider>
<ConsentProvider
categories={{
enabledCategories: ['analytics'],
}}
disableFloatingPreferencesButton={false}
hideBranding={true}
>
{/* Seu conteúdo */}
</ConsentProvider>
<ConsentProvider
categories={{
enabledCategories: ['analytics', 'functional'],
}}
blocking={true}
blockingStrategy="provider"
disableDeveloperGuidance={false}
onPreferencesSaved={(prefs) => {
// Log de auditoria
console.log('Audit: User preferences updated', prefs)
}}
>
{/* Sua app corporativa */}
</ConsentProvider>
// ❌ Errado - hook usado fora do provider
function MeuComponente() {
const { preferences } = useConsent() // Erro!
return <div>...</div>
}
function App() {
return (
<div>
<MeuComponente /> {/* useConsent usado aqui falhará */}
<ConsentProvider categories={{ enabledCategories: ['analytics'] }}>
<main>App</main>
</ConsentProvider>
</div>
)
}
// ✅ Correto - hook usado dentro do provider
function App() {
return (
<ConsentProvider categories={{ enabledCategories: ['analytics'] }}>
<div>
<MeuComponente /> {/* Agora funciona */}
<main>App</main>
</div>
</ConsentProvider>
)
}
// Limpar cookie para teste
document.cookie = 'cookieConsent=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/'
hideBranding e outras configs estão corretasz-index não está sendo sobrescrito por outros elementos// Se você tiver problemas com tipos, adicione ao tsconfig.json:
{
"compilerOptions": {
"moduleResolution": "bundler", // ou "node"
"skipLibCheck": true
}
}
💡 Dica: Use setDebugLogging(true, LogLevel.DEBUG) durante o desenvolvimento para ver logs detalhados do comportamento da biblioteca.