react-lgpd-consent 0.1.2 → 0.1.4

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.
package/COMPLIANCE.md ADDED
@@ -0,0 +1,177 @@
1
+ # COMPLIANCE.md - react-lgpd-consent
2
+
3
+ ## 📜 Objetivo
4
+
5
+ Este documento descreve as medidas de conformidade da biblioteca **react-lgpd-consent** com a Lei Geral de Proteção de Dados (LGPD) e o Guia Orientativo da ANPD sobre Cookies e Proteção de Dados Pessoais.
6
+
7
+ ## ✅ Conformidade Atual (MVP v0.1.x)
8
+
9
+ ### Implementado e Funcional
10
+
11
+ - ✅ **Consentimento granular**: categorias `analytics` e `marketing` independentes
12
+ - ✅ **Sem pre-check**: cookies não essenciais desativados por padrão
13
+ - ✅ **Revogação e reconfiguração**: funções `resetConsent()` e `openPreferences()`
14
+ - ✅ **Banner não intrusivo**: não bloqueia navegação do usuário
15
+ - ✅ **Suporte SSR**: via prop `initialState` (evita flash)
16
+ - ✅ **Acessibilidade**: foco gerenciado, navegação por teclado, ARIA adequado
17
+ - ✅ **API estável**: identificadores em inglês, textos em pt-BR customizáveis
18
+ - ✅ **Segurança básica**: `SameSite=Lax`, `secure=true`, validação de cookies
19
+
20
+ ## 🚀 Roadmap de Desenvolvimento
21
+
22
+ ### v0.2.0 - Adequação ANPD (Próxima Release)
23
+
24
+ **Foco**: Baixo atrito, 100% backward-compatible, adequação básica ANPD
25
+
26
+ #### ✅ Implementar (Pronto para MVP)
27
+
28
+ - [ ] **Textos ANPD expandidos**: Campos opcionais em `ConsentTexts`
29
+
30
+ ```typescript
31
+ interface ConsentTexts {
32
+ // Existentes (manter)
33
+ bannerMessage: string
34
+ acceptAll: string
35
+ declineAll: string
36
+
37
+ // NOVOS - opcionais
38
+ controllerInfo?: string // "Controlado por [Empresa/CNPJ]"
39
+ dataTypes?: string // "Coletamos dados de navegação..."
40
+ thirdPartySharing?: string // "Compartilhamos com terceiros..."
41
+ userRights?: string // "Seus direitos: acesso, correção..."
42
+ contactInfo?: string // "Contato: dpo@empresa.com"
43
+ }
44
+ ```
45
+
46
+ - [ ] **Link de política visível**: Garantir `policyLinkUrl` destacado
47
+ - [ ] **Validação robusta de cookies**: Sanitização e versioning
48
+ - [ ] **Defaults de segurança**: Documentar configurações recomendadas
49
+ - [ ] **Testes de acessibilidade**: Validação com axe/Lighthouse
50
+
51
+ #### 📋 Critérios de Aceite v0.2.0
52
+
53
+ - [ ] Textos opcionais exibidos condicionalmente
54
+ - [ ] Cookie com `version` para migrações futuras
55
+ - [ ] Validação que ignora cookies malformados
56
+ - [ ] Documentação de segurança atualizada
57
+ - [ ] 100% backward compatible (sem breaking changes)
58
+
59
+ ### v0.3.0 - Compliance Operacional
60
+
61
+ **Foco**: Estruturas avançadas, transparência total
62
+
63
+ #### 🏗️ Funcionalidades Principais
64
+
65
+ - [ ] **Modal detalhado de cookies**: Catálogo completo
66
+
67
+ ```typescript
68
+ interface CookieDetails {
69
+ name: string
70
+ purpose: string
71
+ duration: string
72
+ thirdParty?: string
73
+ transferCountries?: string[]
74
+ }
75
+
76
+ interface CookieRegistry {
77
+ [K in Category]: {
78
+ description: string
79
+ cookies: CookieDetails[]
80
+ required: boolean
81
+ }
82
+ }
83
+ ```
84
+
85
+ - [ ] **Logs de consentimento**: Registro client-side
86
+
87
+ ```typescript
88
+ interface ConsentLog {
89
+ timestamp: Date
90
+ action: 'given' | 'revoked' | 'modified'
91
+ categories: ConsentPreferences
92
+ userAgent: string
93
+ }
94
+ ```
95
+
96
+ - [ ] **Base legal por categoria**: `consent` | `legitimate_interest`
97
+ - [ ] **Callbacks expandidos**: Eventos de auditoria
98
+
99
+ ### v0.4.0 - Auditoria e Relatórios
100
+
101
+ **Foco**: Ferramentas de compliance para DPO
102
+
103
+ #### 📊 Funcionalidades Avançadas
104
+
105
+ - [ ] **Relatórios de compliance**: `generateComplianceReport()`
106
+ - [ ] **Templates por setor**: E-commerce, mídia, educação, etc.
107
+ - [ ] **Exportação para DPO**: Dados estruturados para auditoria
108
+ - [ ] **Categorias dinâmicas**: Configurável via props (opcional)
109
+ - [ ] **Assinatura de cookies**: Integridade opcional
110
+
111
+ ## 🎯 Controle de Progresso
112
+
113
+ ### Marcos de Entrega
114
+
115
+ #### v0.2.0 - MVP Completo ⏳
116
+
117
+ - **Target**: 2 semanas
118
+ - **Status**: Em desenvolvimento
119
+ - **Bloqueadores**: -
120
+ - **Próximos passos**: Expandir `ConsentTexts`
121
+
122
+ #### v0.3.0 - Compliance Avançado 📋
123
+
124
+ - **Target**: 6-8 semanas
125
+ - **Status**: Planejamento
126
+ - **Dependências**: v0.2.0 completa
127
+ - **Próximos passos**: Design do `CookieRegistry`
128
+
129
+ #### v0.4.0 - Ferramentas DPO 📊
130
+
131
+ - **Target**: 12 semanas
132
+ - **Status**: Roadmap
133
+ - **Dependências**: v0.3.0 + feedback de usuários
134
+ - **Próximos passos**: Pesquisa com DPOs
135
+
136
+ ## 🔒 Segurança e Privacidade
137
+
138
+ ### Implementado
139
+
140
+ - ✅ Armazenamento client-side via `js-cookie`
141
+ - ✅ `SameSite=Lax`, `secure=true` em HTTPS
142
+ - ✅ Sem coleta antes do consentimento
143
+ - ✅ Validação básica de entrada
144
+
145
+ ### Planejado
146
+
147
+ - 🔄 **v0.2**: Sanitização robusta + versioning
148
+ - 🔄 **v0.3**: Logs estruturados
149
+ - 🔄 **v0.4**: Assinatura opcional de cookies
150
+
151
+ ## 📈 Métricas de Sucesso
152
+
153
+ ### MVP (v0.2.0)
154
+
155
+ - [ ] 0 breaking changes
156
+ - [ ] Cobertura de testes > 90%
157
+ - [ ] Lighthouse Accessibility = 100
158
+ - [ ] Bundle size < 50KB
159
+
160
+ ### Adoção (v0.3.0)
161
+
162
+ - [ ] 5+ projetos usando em produção
163
+ - [ ] Feedback de DPO/compliance
164
+ - [ ] Documentação de casos de uso
165
+
166
+ ### Maturidade (v0.4.0)
167
+
168
+ - [ ] Template de compliance por setor
169
+ - [ ] Integração com ferramentas de auditoria
170
+ - [ ] Casos de sucesso documentados
171
+
172
+ ## 📚 Referências e Padrões
173
+
174
+ - [Lei 13.709/2018 - LGPD](https://www.planalto.gov.br/ccivil_03/_ato2015-2018/2018/lei/l13709.htm)
175
+ - [Guia ANPD - Cookies e Proteção de Dados](https://www.gov.br/anpd/pt-br/documentos-e-publicacoes/guia-orientativo-cookies-e-protecao-de-dados-pessoais.pdf)
176
+ - [GDPR Recital 32](https://gdpr-info.eu/recitals/no-32/) - Consentimento
177
+ - [ePrivacy Directive](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32002L0058) - Cookies
package/README.md CHANGED
@@ -20,6 +20,9 @@ Solução moderna, acessível e personalizável para gerenciar consentimento de
20
20
  - 🚀 **TypeScript**: API completamente tipada para melhor DX
21
21
  - 📦 **Zero Config**: Funciona out-of-the-box com configurações sensatas
22
22
  - 🎯 **Granular Control**: Controle individual de categorias (analytics, marketing, etc.)
23
+ - 🚫 **Banner Bloqueante**: Modo opcional para exigir interação antes de continuar
24
+ - 🎨 **Sistema de Temas**: Temas customizáveis para integração visual perfeita
25
+ - ⚡ **Carregamento Condicional**: Scripts só executam após consentimento explícito
23
26
 
24
27
  ## 🚀 Instalação
25
28
 
@@ -62,7 +65,10 @@ function Layout() {
62
65
  return (
63
66
  <>
64
67
  <YourContent />
65
- <CookieBanner policyLinkUrl="/politica-privacidade" />
68
+ <CookieBanner
69
+ policyLinkUrl="/politica-privacidade"
70
+ blocking={true} // Padrão: bloqueia até decisão
71
+ />
66
72
  </>
67
73
  )
68
74
  }
@@ -89,7 +95,7 @@ function MyComponent() {
89
95
  ### 4. Carregamento Condicional de Scripts
90
96
 
91
97
  ```tsx
92
- import { ConsentGate, loadScript } from 'react-lgpd-consent'
98
+ import { ConsentGate, loadConditionalScript } from 'react-lgpd-consent'
93
99
 
94
100
  function Analytics() {
95
101
  return (
@@ -99,20 +105,55 @@ function Analytics() {
99
105
  )
100
106
  }
101
107
 
102
- // Ou carregando scripts dinamicamente
108
+ // Ou carregando scripts que aguardam consentimento
103
109
  function MyComponent() {
104
- const { preferences } = useConsent()
110
+ const { preferences, consented } = useConsent()
105
111
 
106
112
  useEffect(() => {
107
- if (preferences.analytics) {
108
- loadScript('ga', 'https://www.googletagmanager.com/gtag/js?id=GA_ID')
113
+ if (consented && preferences.analytics) {
114
+ loadConditionalScript(
115
+ 'ga',
116
+ 'https://www.googletagmanager.com/gtag/js?id=GA_ID',
117
+ () => preferences.analytics, // Condição que aguarda
118
+ )
109
119
  }
110
- }, [preferences.analytics])
120
+ }, [preferences, consented])
111
121
  }
112
122
  ```
113
123
 
114
124
  ## 🎨 Customização
115
125
 
126
+ ### Banner Bloqueante vs Não-bloqueante
127
+
128
+ ```tsx
129
+ // Banner bloqueante (padrão) - impede interação até decisão
130
+ <CookieBanner blocking={true} />
131
+
132
+ // Banner não-intrusivo - permite navegação
133
+ <CookieBanner blocking={false} />
134
+ ```
135
+
136
+ ### Tema Personalizado
137
+
138
+ ```tsx
139
+ import { ConsentProvider, defaultConsentTheme } from 'react-lgpd-consent'
140
+ import { createTheme } from '@mui/material/styles'
141
+
142
+ const meuTema = createTheme({
143
+ ...defaultConsentTheme,
144
+ palette: {
145
+ ...defaultConsentTheme.palette,
146
+ primary: {
147
+ main: '#1976d2', // Sua cor principal
148
+ },
149
+ },
150
+ })
151
+
152
+ <ConsentProvider theme={meuTema}>
153
+ <App />
154
+ </ConsentProvider>
155
+ ```
156
+
116
157
  ### Textos Personalizados
117
158
 
118
159
  ```tsx
@@ -152,16 +193,90 @@ function MyComponent() {
152
193
  >
153
194
  ```
154
195
 
155
- ## 🔧 API Completa
196
+ ## Banner Bloqueante
197
+
198
+ Para cenários onde é necessário bloquear o acesso até obter consentimento explícito:
199
+
200
+ ```tsx
201
+ <CookieBanner blocking />
202
+ ```
203
+
204
+ Com `blocking={true}`, o banner:
205
+
206
+ - Cria um overlay escuro sobre todo o conteúdo
207
+ - Impede interação com o resto da página
208
+ - É útil para casos críticos onde consentimento é obrigatório
209
+
210
+ ## 🎨 Sistema de Temas
211
+
212
+ ### Tema Personalizado
213
+
214
+ ```tsx
215
+ import { createTheme } from '@mui/material/styles'
216
+
217
+ const meuTema = createTheme({
218
+ palette: {
219
+ primary: { main: '#2196f3' },
220
+ secondary: { main: '#f50057' },
221
+ },
222
+ })
223
+
224
+ <ConsentProvider theme={meuTema}>
225
+ <App />
226
+ </ConsentProvider>
227
+ ```
228
+
229
+ ### Tema Padrão
230
+
231
+ O tema padrão do react-lgpd-consent está disponível para customização:
232
+
233
+ ```tsx
234
+ import { defaultConsentTheme } from 'react-lgpd-consent'
235
+
236
+ const temaCustomizado = createTheme({
237
+ ...defaultConsentTheme,
238
+ palette: {
239
+ ...defaultConsentTheme.palette,
240
+ primary: { main: '#your-color' },
241
+ },
242
+ })
243
+ ```
244
+
245
+ ## ⚡ Carregamento Condicional
246
+
247
+ ### Função loadConditionalScript
248
+
249
+ Para scripts que devem aguardar consentimento específico:
250
+
251
+ ```tsx
252
+ import { loadConditionalScript } from 'react-lgpd-consent'
253
+
254
+ // Carrega script apenas quando analytics for aceito
255
+ await loadConditionalScript(
256
+ 'gtag',
257
+ 'https://www.googletagmanager.com/gtag/js?id=GA_ID',
258
+ () => preferences.analytics,
259
+ { timeout: 10000 }, // timeout opcional
260
+ )
261
+ ```
262
+
263
+ ### Parâmetros
264
+
265
+ - `id`: Identificador único para o script
266
+ - `src`: URL do script a ser carregado
267
+ - `condition`: Função que retorna boolean indicando se deve carregar
268
+ - `options`: Configurações opcionais (timeout, etc.)
269
+
270
+ ## �🔧 API Completa
156
271
 
157
272
  ### Components
158
273
 
159
- | Componente | Descrição | Props Principais |
160
- | ------------------ | -------------------------------------- | ------------------------------------------------ |
161
- | `ConsentProvider` | Provider principal do contexto | `initialState`, `texts`, `cookie`, callbacks |
162
- | `CookieBanner` | Banner de consentimento | `policyLinkUrl`, `debug`, pass-through MUI props |
163
- | `PreferencesModal` | Modal de preferências detalhadas | `DialogProps` (MUI pass-through) |
164
- | `ConsentGate` | Renderização condicional por categoria | `category`, `children` |
274
+ | Componente | Descrição | Props Principais |
275
+ | ------------------ | -------------------------------------- | ------------------------------------------------------------ |
276
+ | `ConsentProvider` | Provider principal do contexto | `initialState`, `texts`, `theme`, `cookie`, callbacks |
277
+ | `CookieBanner` | Banner de consentimento | `policyLinkUrl`, `blocking`, `debug`, pass-through MUI props |
278
+ | `PreferencesModal` | Modal de preferências detalhadas | `DialogProps` (MUI pass-through) |
279
+ | `ConsentGate` | Renderização condicional por categoria | `category`, `children` |
165
280
 
166
281
  ### Hook `useConsent()`
167
282
 
@@ -169,6 +284,7 @@ function MyComponent() {
169
284
  interface ConsentContextValue {
170
285
  consented: boolean // usuário já consentiu?
171
286
  preferences: ConsentPreferences // preferências atuais
287
+ isModalOpen: boolean // estado do modal de preferências
172
288
  acceptAll(): void // aceitar todas as categorias
173
289
  rejectAll(): void // recusar opcionais
174
290
  setPreference(cat: Category, value: boolean): void // definir categoria específica
@@ -178,9 +294,19 @@ interface ConsentContextValue {
178
294
  }
179
295
  ```
180
296
 
297
+ ### Hook `useConsentTexts()`
298
+
299
+ ```typescript
300
+ // Acesso aos textos contextuais
301
+ const texts = useConsentTexts()
302
+ console.log(texts.banner.title) // "Política de Cookies"
303
+ ```
304
+
181
305
  ### Utilitários
182
306
 
183
307
  - `loadScript(id, src, attrs?)` - Carrega scripts dinamicamente
308
+ - `loadConditionalScript(id, src, condition, options?)` - Carrega scripts condicionalmente
309
+ - `defaultConsentTheme` - Tema padrão do Material-UI
184
310
  - Tipos TypeScript completos exportados
185
311
 
186
312
  ## 🌐 SSR / Next.js
package/dist/index.cjs CHANGED
@@ -34,6 +34,8 @@ __export(index_exports, {
34
34
  ConsentProvider: () => ConsentProvider,
35
35
  CookieBanner: () => CookieBanner,
36
36
  PreferencesModal: () => PreferencesModal,
37
+ defaultConsentTheme: () => defaultConsentTheme,
38
+ loadConditionalScript: () => loadConditionalScript,
37
39
  loadScript: () => loadScript,
38
40
  useConsent: () => useConsent,
39
41
  useConsentTexts: () => useConsentTexts
@@ -42,6 +44,7 @@ module.exports = __toCommonJS(index_exports);
42
44
 
43
45
  // src/components/CookieBanner.tsx
44
46
  var import_Button = __toESM(require("@mui/material/Button"), 1);
47
+ var import_Box = __toESM(require("@mui/material/Box"), 1);
45
48
  var import_Link = __toESM(require("@mui/material/Link"), 1);
46
49
  var import_Paper = __toESM(require("@mui/material/Paper"), 1);
47
50
  var import_Snackbar = __toESM(require("@mui/material/Snackbar"), 1);
@@ -50,6 +53,7 @@ var import_Typography = __toESM(require("@mui/material/Typography"), 1);
50
53
 
51
54
  // src/context/ConsentContext.tsx
52
55
  var React = __toESM(require("react"), 1);
56
+ var import_styles2 = require("@mui/material/styles");
53
57
 
54
58
  // src/utils/cookieUtils.ts
55
59
  var import_js_cookie = __toESM(require("js-cookie"), 1);
@@ -86,6 +90,77 @@ function removeConsentCookie(opts) {
86
90
  import_js_cookie.default.remove(o.name, { path: o.path });
87
91
  }
88
92
 
93
+ // src/utils/theme.ts
94
+ var import_styles = require("@mui/material/styles");
95
+ var defaultConsentTheme = (0, import_styles.createTheme)({
96
+ palette: {
97
+ primary: {
98
+ main: "#1976d2",
99
+ // Azul institucional
100
+ contrastText: "#ffffff"
101
+ },
102
+ secondary: {
103
+ main: "#dc004e",
104
+ // Rosa/vermelho para ações importantes
105
+ contrastText: "#ffffff"
106
+ },
107
+ background: {
108
+ default: "#fafafa",
109
+ paper: "#ffffff"
110
+ },
111
+ text: {
112
+ primary: "#333333",
113
+ secondary: "#666666"
114
+ },
115
+ action: {
116
+ hover: "rgba(25, 118, 210, 0.04)"
117
+ }
118
+ },
119
+ typography: {
120
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
121
+ body2: {
122
+ fontSize: "0.875rem",
123
+ lineHeight: 1.43
124
+ },
125
+ button: {
126
+ fontWeight: 500,
127
+ textTransform: "none"
128
+ // Manter capitalização original
129
+ }
130
+ },
131
+ components: {
132
+ MuiButton: {
133
+ styleOverrides: {
134
+ root: {
135
+ borderRadius: 8,
136
+ paddingX: 16,
137
+ paddingY: 8
138
+ },
139
+ contained: {
140
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
141
+ "&:hover": {
142
+ boxShadow: "0 4px 8px rgba(0,0,0,0.15)"
143
+ }
144
+ }
145
+ }
146
+ },
147
+ MuiPaper: {
148
+ styleOverrides: {
149
+ root: {
150
+ borderRadius: 12
151
+ }
152
+ }
153
+ },
154
+ MuiDialog: {
155
+ styleOverrides: {
156
+ paper: {
157
+ borderRadius: 16
158
+ }
159
+ }
160
+ }
161
+ }
162
+ });
163
+
89
164
  // src/context/ConsentContext.tsx
90
165
  var import_jsx_runtime = require("react/jsx-runtime");
91
166
  var DEFAULT_PREFERENCES = {
@@ -148,6 +223,7 @@ var TextsCtx = React.createContext(DEFAULT_TEXTS);
148
223
  function ConsentProvider({
149
224
  initialState,
150
225
  texts: textsProp,
226
+ theme,
151
227
  onConsentGiven,
152
228
  onPreferencesSaved,
153
229
  cookie: cookieOpts,
@@ -161,6 +237,10 @@ function ConsentProvider({
161
237
  () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
162
238
  [cookieOpts]
163
239
  );
240
+ const appliedTheme = React.useMemo(
241
+ () => theme || defaultConsentTheme,
242
+ [theme]
243
+ );
164
244
  const boot = React.useMemo(() => {
165
245
  if (initialState) return { ...initialState, isModalOpen: false };
166
246
  const saved = readConsentCookie(cookie.name);
@@ -176,14 +256,15 @@ function ConsentProvider({
176
256
  }, [state, cookie]);
177
257
  const prevConsented = React.useRef(state.consented);
178
258
  React.useEffect(() => {
179
- if (!prevConsented.current && state.consented && onConsentGiven)
180
- onConsentGiven(state);
259
+ if (!prevConsented.current && state.consented && onConsentGiven) {
260
+ setTimeout(() => onConsentGiven(state), 150);
261
+ }
181
262
  prevConsented.current = state.consented;
182
263
  }, [state, onConsentGiven]);
183
264
  const prevPrefs = React.useRef(state.preferences);
184
265
  React.useEffect(() => {
185
266
  if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
186
- onPreferencesSaved(state.preferences);
267
+ setTimeout(() => onPreferencesSaved(state.preferences), 150);
187
268
  prevPrefs.current = state.preferences;
188
269
  }
189
270
  }, [state, onPreferencesSaved]);
@@ -208,7 +289,7 @@ function ConsentProvider({
208
289
  resetConsent
209
290
  };
210
291
  }, [state, cookie]);
211
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateCtx.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextsCtx.Provider, { value: texts, children }) }) });
292
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_styles2.ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StateCtx.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TextsCtx.Provider, { value: texts, children }) }) }) });
212
293
  }
213
294
  function useConsentStateInternal() {
214
295
  const ctx = React.useContext(StateCtx);
@@ -234,6 +315,7 @@ function useConsent() {
234
315
  return {
235
316
  consented: state.consented,
236
317
  preferences: state.preferences,
318
+ isModalOpen: state.isModalOpen,
237
319
  acceptAll: actions.acceptAll,
238
320
  rejectAll: actions.rejectAll,
239
321
  setPreference: actions.setPreference,
@@ -251,6 +333,8 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
251
333
  function CookieBanner({
252
334
  policyLinkUrl,
253
335
  debug,
336
+ blocking = true,
337
+ // Por padrão, bloqueia até decisão
254
338
  SnackbarProps,
255
339
  PaperProps
256
340
  }) {
@@ -258,49 +342,72 @@ function CookieBanner({
258
342
  const texts = useConsentTexts();
259
343
  const open = debug ? true : !consented;
260
344
  if (!open) return null;
345
+ const bannerContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
346
+ import_Paper.default,
347
+ {
348
+ elevation: 3,
349
+ sx: { p: 2, maxWidth: 720, mx: "auto" },
350
+ ...PaperProps,
351
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Stack.default, { spacing: 1, children: [
352
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Typography.default, { variant: "body2", children: [
353
+ texts.bannerMessage,
354
+ " ",
355
+ policyLinkUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
356
+ import_Link.default,
357
+ {
358
+ href: policyLinkUrl,
359
+ underline: "hover",
360
+ target: "_blank",
361
+ rel: "noopener noreferrer",
362
+ children: texts.policyLink ?? "Saiba mais"
363
+ }
364
+ )
365
+ ] }),
366
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
367
+ import_Stack.default,
368
+ {
369
+ direction: { xs: "column", sm: "row" },
370
+ spacing: 1,
371
+ justifyContent: "flex-end",
372
+ children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
375
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "text", onClick: openPreferences, children: texts.preferences })
376
+ ]
377
+ }
378
+ )
379
+ ] })
380
+ }
381
+ );
382
+ if (blocking) {
383
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
384
+ import_Box.default,
385
+ {
386
+ sx: {
387
+ position: "fixed",
388
+ top: 0,
389
+ left: 0,
390
+ right: 0,
391
+ bottom: 0,
392
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
393
+ display: "flex",
394
+ alignItems: "center",
395
+ justifyContent: "center",
396
+ zIndex: 1300,
397
+ // Above MUI Modal
398
+ p: 2
399
+ },
400
+ children: bannerContent
401
+ }
402
+ );
403
+ }
261
404
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
262
405
  import_Snackbar.default,
263
406
  {
264
407
  open,
265
408
  anchorOrigin: { vertical: "bottom", horizontal: "center" },
266
409
  ...SnackbarProps,
267
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
268
- import_Paper.default,
269
- {
270
- elevation: 3,
271
- sx: { p: 2, maxWidth: 720, mx: "auto" },
272
- ...PaperProps,
273
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Stack.default, { spacing: 1, children: [
274
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Typography.default, { variant: "body2", children: [
275
- texts.bannerMessage,
276
- " ",
277
- policyLinkUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
278
- import_Link.default,
279
- {
280
- href: policyLinkUrl,
281
- underline: "hover",
282
- target: "_blank",
283
- rel: "noopener noreferrer",
284
- children: texts.policyLink ?? "Saiba mais"
285
- }
286
- )
287
- ] }),
288
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
289
- import_Stack.default,
290
- {
291
- direction: { xs: "column", sm: "row" },
292
- spacing: 1,
293
- justifyContent: "flex-end",
294
- children: [
295
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
296
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
297
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "text", onClick: openPreferences, children: texts.preferences })
298
- ]
299
- }
300
- )
301
- ] })
302
- }
303
- )
410
+ children: bannerContent
304
411
  }
305
412
  );
306
413
  }
@@ -319,9 +426,9 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
319
426
  function PreferencesModal({
320
427
  DialogProps: DialogProps2
321
428
  }) {
322
- const { preferences, setPreference, closePreferences } = useConsent();
429
+ const { preferences, setPreference, closePreferences, isModalOpen } = useConsent();
323
430
  const texts = useConsentTexts();
324
- const open = (DialogProps2 && "open" in DialogProps2 ? DialogProps2.open : void 0) ?? true;
431
+ const open = DialogProps2?.open ?? isModalOpen ?? false;
325
432
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
326
433
  import_Dialog.default,
327
434
  {
@@ -394,12 +501,34 @@ function loadScript(id, src, attrs = {}) {
394
501
  for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
395
502
  document.body.appendChild(s);
396
503
  }
504
+ function loadConditionalScript(id, src, condition, attrs = {}, maxWaitMs = 5e3) {
505
+ if (typeof document === "undefined") return Promise.resolve();
506
+ if (document.getElementById(id)) return Promise.resolve();
507
+ return new Promise((resolve, reject) => {
508
+ const startTime = Date.now();
509
+ const checkCondition = () => {
510
+ if (condition()) {
511
+ loadScript(id, src, attrs);
512
+ resolve();
513
+ } else if (Date.now() - startTime > maxWaitMs) {
514
+ reject(
515
+ new Error(`Timeout waiting for consent condition for script ${id}`)
516
+ );
517
+ } else {
518
+ setTimeout(checkCondition, 100);
519
+ }
520
+ };
521
+ checkCondition();
522
+ });
523
+ }
397
524
  // Annotate the CommonJS export names for ESM import in node:
398
525
  0 && (module.exports = {
399
526
  ConsentGate,
400
527
  ConsentProvider,
401
528
  CookieBanner,
402
529
  PreferencesModal,
530
+ defaultConsentTheme,
531
+ loadConditionalScript,
403
532
  loadScript,
404
533
  useConsent,
405
534
  useConsentTexts
package/dist/index.d.cts CHANGED
@@ -3,14 +3,17 @@ import { PaperProps } from '@mui/material/Paper';
3
3
  import { SnackbarProps } from '@mui/material/Snackbar';
4
4
  import { DialogProps } from '@mui/material/Dialog';
5
5
  import * as React$1 from 'react';
6
+ import * as _mui_material_styles from '@mui/material/styles';
6
7
 
7
8
  interface CookieBannerProps {
8
9
  policyLinkUrl?: string;
9
10
  debug?: boolean;
11
+ blocking?: boolean;
10
12
  SnackbarProps?: Partial<SnackbarProps>;
11
13
  PaperProps?: Partial<PaperProps>;
12
14
  }
13
- declare function CookieBanner({ policyLinkUrl, debug, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
15
+ declare function CookieBanner({ policyLinkUrl, debug, blocking, // Por padrão, bloqueia até decisão
16
+ SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
14
17
 
15
18
  interface PreferencesModalProps {
16
19
  DialogProps?: Partial<DialogProps>;
@@ -75,6 +78,8 @@ interface ConsentProviderProps {
75
78
  initialState?: ConsentState;
76
79
  /** Textos customizados para a interface. */
77
80
  texts?: Partial<ConsentTexts>;
81
+ /** Tema customizado para os componentes MUI. */
82
+ theme?: any;
78
83
  /** Callback chamado quando o consentimento é dado. */
79
84
  onConsentGiven?: (state: ConsentState) => void;
80
85
  /** Callback chamado ao salvar preferências. */
@@ -92,6 +97,8 @@ interface ConsentContextValue {
92
97
  consented: boolean;
93
98
  /** Preferências atuais do usuário. */
94
99
  preferences: ConsentPreferences;
100
+ /** Indica se o modal de preferências está aberto. */
101
+ isModalOpen?: boolean;
95
102
  /** Aceita todas as categorias de consentimento. */
96
103
  acceptAll: () => void;
97
104
  /** Rejeita todas as categorias de consentimento. */
@@ -106,7 +113,7 @@ interface ConsentContextValue {
106
113
  resetConsent: () => void;
107
114
  }
108
115
 
109
- declare function ConsentProvider({ initialState, texts: textsProp, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
116
+ declare function ConsentProvider({ initialState, texts: textsProp, theme, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
110
117
 
111
118
  declare function useConsent(): ConsentContextValue;
112
119
  /**
@@ -120,6 +127,21 @@ declare function ConsentGate(props: Readonly<{
120
127
  children: React$1.ReactNode;
121
128
  }>): react_jsx_runtime.JSX.Element | null;
122
129
 
130
+ /**
131
+ * Carrega um script dinamicamente após verificação de consentimento.
132
+ * Recomenda-se usar com ConsentGate para melhor controle.
133
+ */
123
134
  declare function loadScript(id: string, src: string, attrs?: Record<string, string>): void;
135
+ /**
136
+ * Carrega script condicionalmente baseado no consentimento.
137
+ * Aguarda o consentimento ser dado antes de executar.
138
+ */
139
+ declare function loadConditionalScript(id: string, src: string, condition: () => boolean, attrs?: Record<string, string>, maxWaitMs?: number): Promise<void>;
140
+
141
+ /**
142
+ * Tema padrão para os componentes de consentimento.
143
+ * Baseado no design system da ANPD/governo brasileiro.
144
+ */
145
+ declare const defaultConsentTheme: _mui_material_styles.Theme;
124
146
 
125
- export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, loadScript, useConsent, useConsentTexts };
147
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, defaultConsentTheme, loadConditionalScript, loadScript, useConsent, useConsentTexts };
package/dist/index.d.ts CHANGED
@@ -3,14 +3,17 @@ import { PaperProps } from '@mui/material/Paper';
3
3
  import { SnackbarProps } from '@mui/material/Snackbar';
4
4
  import { DialogProps } from '@mui/material/Dialog';
5
5
  import * as React$1 from 'react';
6
+ import * as _mui_material_styles from '@mui/material/styles';
6
7
 
7
8
  interface CookieBannerProps {
8
9
  policyLinkUrl?: string;
9
10
  debug?: boolean;
11
+ blocking?: boolean;
10
12
  SnackbarProps?: Partial<SnackbarProps>;
11
13
  PaperProps?: Partial<PaperProps>;
12
14
  }
13
- declare function CookieBanner({ policyLinkUrl, debug, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
15
+ declare function CookieBanner({ policyLinkUrl, debug, blocking, // Por padrão, bloqueia até decisão
16
+ SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
14
17
 
15
18
  interface PreferencesModalProps {
16
19
  DialogProps?: Partial<DialogProps>;
@@ -75,6 +78,8 @@ interface ConsentProviderProps {
75
78
  initialState?: ConsentState;
76
79
  /** Textos customizados para a interface. */
77
80
  texts?: Partial<ConsentTexts>;
81
+ /** Tema customizado para os componentes MUI. */
82
+ theme?: any;
78
83
  /** Callback chamado quando o consentimento é dado. */
79
84
  onConsentGiven?: (state: ConsentState) => void;
80
85
  /** Callback chamado ao salvar preferências. */
@@ -92,6 +97,8 @@ interface ConsentContextValue {
92
97
  consented: boolean;
93
98
  /** Preferências atuais do usuário. */
94
99
  preferences: ConsentPreferences;
100
+ /** Indica se o modal de preferências está aberto. */
101
+ isModalOpen?: boolean;
95
102
  /** Aceita todas as categorias de consentimento. */
96
103
  acceptAll: () => void;
97
104
  /** Rejeita todas as categorias de consentimento. */
@@ -106,7 +113,7 @@ interface ConsentContextValue {
106
113
  resetConsent: () => void;
107
114
  }
108
115
 
109
- declare function ConsentProvider({ initialState, texts: textsProp, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
116
+ declare function ConsentProvider({ initialState, texts: textsProp, theme, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
110
117
 
111
118
  declare function useConsent(): ConsentContextValue;
112
119
  /**
@@ -120,6 +127,21 @@ declare function ConsentGate(props: Readonly<{
120
127
  children: React$1.ReactNode;
121
128
  }>): react_jsx_runtime.JSX.Element | null;
122
129
 
130
+ /**
131
+ * Carrega um script dinamicamente após verificação de consentimento.
132
+ * Recomenda-se usar com ConsentGate para melhor controle.
133
+ */
123
134
  declare function loadScript(id: string, src: string, attrs?: Record<string, string>): void;
135
+ /**
136
+ * Carrega script condicionalmente baseado no consentimento.
137
+ * Aguarda o consentimento ser dado antes de executar.
138
+ */
139
+ declare function loadConditionalScript(id: string, src: string, condition: () => boolean, attrs?: Record<string, string>, maxWaitMs?: number): Promise<void>;
140
+
141
+ /**
142
+ * Tema padrão para os componentes de consentimento.
143
+ * Baseado no design system da ANPD/governo brasileiro.
144
+ */
145
+ declare const defaultConsentTheme: _mui_material_styles.Theme;
124
146
 
125
- export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, loadScript, useConsent, useConsentTexts };
147
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, defaultConsentTheme, loadConditionalScript, loadScript, useConsent, useConsentTexts };
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // src/components/CookieBanner.tsx
2
2
  import Button from "@mui/material/Button";
3
+ import Box from "@mui/material/Box";
3
4
  import Link from "@mui/material/Link";
4
5
  import Paper from "@mui/material/Paper";
5
6
  import Snackbar from "@mui/material/Snackbar";
@@ -8,6 +9,7 @@ import Typography from "@mui/material/Typography";
8
9
 
9
10
  // src/context/ConsentContext.tsx
10
11
  import * as React from "react";
12
+ import { ThemeProvider } from "@mui/material/styles";
11
13
 
12
14
  // src/utils/cookieUtils.ts
13
15
  import Cookies from "js-cookie";
@@ -44,6 +46,77 @@ function removeConsentCookie(opts) {
44
46
  Cookies.remove(o.name, { path: o.path });
45
47
  }
46
48
 
49
+ // src/utils/theme.ts
50
+ import { createTheme } from "@mui/material/styles";
51
+ var defaultConsentTheme = createTheme({
52
+ palette: {
53
+ primary: {
54
+ main: "#1976d2",
55
+ // Azul institucional
56
+ contrastText: "#ffffff"
57
+ },
58
+ secondary: {
59
+ main: "#dc004e",
60
+ // Rosa/vermelho para ações importantes
61
+ contrastText: "#ffffff"
62
+ },
63
+ background: {
64
+ default: "#fafafa",
65
+ paper: "#ffffff"
66
+ },
67
+ text: {
68
+ primary: "#333333",
69
+ secondary: "#666666"
70
+ },
71
+ action: {
72
+ hover: "rgba(25, 118, 210, 0.04)"
73
+ }
74
+ },
75
+ typography: {
76
+ fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif',
77
+ body2: {
78
+ fontSize: "0.875rem",
79
+ lineHeight: 1.43
80
+ },
81
+ button: {
82
+ fontWeight: 500,
83
+ textTransform: "none"
84
+ // Manter capitalização original
85
+ }
86
+ },
87
+ components: {
88
+ MuiButton: {
89
+ styleOverrides: {
90
+ root: {
91
+ borderRadius: 8,
92
+ paddingX: 16,
93
+ paddingY: 8
94
+ },
95
+ contained: {
96
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
97
+ "&:hover": {
98
+ boxShadow: "0 4px 8px rgba(0,0,0,0.15)"
99
+ }
100
+ }
101
+ }
102
+ },
103
+ MuiPaper: {
104
+ styleOverrides: {
105
+ root: {
106
+ borderRadius: 12
107
+ }
108
+ }
109
+ },
110
+ MuiDialog: {
111
+ styleOverrides: {
112
+ paper: {
113
+ borderRadius: 16
114
+ }
115
+ }
116
+ }
117
+ }
118
+ });
119
+
47
120
  // src/context/ConsentContext.tsx
48
121
  import { jsx } from "react/jsx-runtime";
49
122
  var DEFAULT_PREFERENCES = {
@@ -106,6 +179,7 @@ var TextsCtx = React.createContext(DEFAULT_TEXTS);
106
179
  function ConsentProvider({
107
180
  initialState,
108
181
  texts: textsProp,
182
+ theme,
109
183
  onConsentGiven,
110
184
  onPreferencesSaved,
111
185
  cookie: cookieOpts,
@@ -119,6 +193,10 @@ function ConsentProvider({
119
193
  () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
120
194
  [cookieOpts]
121
195
  );
196
+ const appliedTheme = React.useMemo(
197
+ () => theme || defaultConsentTheme,
198
+ [theme]
199
+ );
122
200
  const boot = React.useMemo(() => {
123
201
  if (initialState) return { ...initialState, isModalOpen: false };
124
202
  const saved = readConsentCookie(cookie.name);
@@ -134,14 +212,15 @@ function ConsentProvider({
134
212
  }, [state, cookie]);
135
213
  const prevConsented = React.useRef(state.consented);
136
214
  React.useEffect(() => {
137
- if (!prevConsented.current && state.consented && onConsentGiven)
138
- onConsentGiven(state);
215
+ if (!prevConsented.current && state.consented && onConsentGiven) {
216
+ setTimeout(() => onConsentGiven(state), 150);
217
+ }
139
218
  prevConsented.current = state.consented;
140
219
  }, [state, onConsentGiven]);
141
220
  const prevPrefs = React.useRef(state.preferences);
142
221
  React.useEffect(() => {
143
222
  if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
144
- onPreferencesSaved(state.preferences);
223
+ setTimeout(() => onPreferencesSaved(state.preferences), 150);
145
224
  prevPrefs.current = state.preferences;
146
225
  }
147
226
  }, [state, onPreferencesSaved]);
@@ -166,7 +245,7 @@ function ConsentProvider({
166
245
  resetConsent
167
246
  };
168
247
  }, [state, cookie]);
169
- return /* @__PURE__ */ jsx(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx(TextsCtx.Provider, { value: texts, children }) }) });
248
+ return /* @__PURE__ */ jsx(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsx(TextsCtx.Provider, { value: texts, children }) }) }) });
170
249
  }
171
250
  function useConsentStateInternal() {
172
251
  const ctx = React.useContext(StateCtx);
@@ -192,6 +271,7 @@ function useConsent() {
192
271
  return {
193
272
  consented: state.consented,
194
273
  preferences: state.preferences,
274
+ isModalOpen: state.isModalOpen,
195
275
  acceptAll: actions.acceptAll,
196
276
  rejectAll: actions.rejectAll,
197
277
  setPreference: actions.setPreference,
@@ -209,6 +289,8 @@ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
209
289
  function CookieBanner({
210
290
  policyLinkUrl,
211
291
  debug,
292
+ blocking = true,
293
+ // Por padrão, bloqueia até decisão
212
294
  SnackbarProps,
213
295
  PaperProps
214
296
  }) {
@@ -216,49 +298,72 @@ function CookieBanner({
216
298
  const texts = useConsentTexts();
217
299
  const open = debug ? true : !consented;
218
300
  if (!open) return null;
301
+ const bannerContent = /* @__PURE__ */ jsx2(
302
+ Paper,
303
+ {
304
+ elevation: 3,
305
+ sx: { p: 2, maxWidth: 720, mx: "auto" },
306
+ ...PaperProps,
307
+ children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
308
+ /* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
309
+ texts.bannerMessage,
310
+ " ",
311
+ policyLinkUrl && /* @__PURE__ */ jsx2(
312
+ Link,
313
+ {
314
+ href: policyLinkUrl,
315
+ underline: "hover",
316
+ target: "_blank",
317
+ rel: "noopener noreferrer",
318
+ children: texts.policyLink ?? "Saiba mais"
319
+ }
320
+ )
321
+ ] }),
322
+ /* @__PURE__ */ jsxs(
323
+ Stack,
324
+ {
325
+ direction: { xs: "column", sm: "row" },
326
+ spacing: 1,
327
+ justifyContent: "flex-end",
328
+ children: [
329
+ /* @__PURE__ */ jsx2(Button, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
330
+ /* @__PURE__ */ jsx2(Button, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
331
+ /* @__PURE__ */ jsx2(Button, { variant: "text", onClick: openPreferences, children: texts.preferences })
332
+ ]
333
+ }
334
+ )
335
+ ] })
336
+ }
337
+ );
338
+ if (blocking) {
339
+ return /* @__PURE__ */ jsx2(
340
+ Box,
341
+ {
342
+ sx: {
343
+ position: "fixed",
344
+ top: 0,
345
+ left: 0,
346
+ right: 0,
347
+ bottom: 0,
348
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
349
+ display: "flex",
350
+ alignItems: "center",
351
+ justifyContent: "center",
352
+ zIndex: 1300,
353
+ // Above MUI Modal
354
+ p: 2
355
+ },
356
+ children: bannerContent
357
+ }
358
+ );
359
+ }
219
360
  return /* @__PURE__ */ jsx2(
220
361
  Snackbar,
221
362
  {
222
363
  open,
223
364
  anchorOrigin: { vertical: "bottom", horizontal: "center" },
224
365
  ...SnackbarProps,
225
- children: /* @__PURE__ */ jsx2(
226
- Paper,
227
- {
228
- elevation: 3,
229
- sx: { p: 2, maxWidth: 720, mx: "auto" },
230
- ...PaperProps,
231
- children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
232
- /* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
233
- texts.bannerMessage,
234
- " ",
235
- policyLinkUrl && /* @__PURE__ */ jsx2(
236
- Link,
237
- {
238
- href: policyLinkUrl,
239
- underline: "hover",
240
- target: "_blank",
241
- rel: "noopener noreferrer",
242
- children: texts.policyLink ?? "Saiba mais"
243
- }
244
- )
245
- ] }),
246
- /* @__PURE__ */ jsxs(
247
- Stack,
248
- {
249
- direction: { xs: "column", sm: "row" },
250
- spacing: 1,
251
- justifyContent: "flex-end",
252
- children: [
253
- /* @__PURE__ */ jsx2(Button, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
254
- /* @__PURE__ */ jsx2(Button, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
255
- /* @__PURE__ */ jsx2(Button, { variant: "text", onClick: openPreferences, children: texts.preferences })
256
- ]
257
- }
258
- )
259
- ] })
260
- }
261
- )
366
+ children: bannerContent
262
367
  }
263
368
  );
264
369
  }
@@ -277,9 +382,9 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
277
382
  function PreferencesModal({
278
383
  DialogProps: DialogProps2
279
384
  }) {
280
- const { preferences, setPreference, closePreferences } = useConsent();
385
+ const { preferences, setPreference, closePreferences, isModalOpen } = useConsent();
281
386
  const texts = useConsentTexts();
282
- const open = (DialogProps2 && "open" in DialogProps2 ? DialogProps2.open : void 0) ?? true;
387
+ const open = DialogProps2?.open ?? isModalOpen ?? false;
283
388
  return /* @__PURE__ */ jsxs2(
284
389
  Dialog,
285
390
  {
@@ -352,11 +457,33 @@ function loadScript(id, src, attrs = {}) {
352
457
  for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
353
458
  document.body.appendChild(s);
354
459
  }
460
+ function loadConditionalScript(id, src, condition, attrs = {}, maxWaitMs = 5e3) {
461
+ if (typeof document === "undefined") return Promise.resolve();
462
+ if (document.getElementById(id)) return Promise.resolve();
463
+ return new Promise((resolve, reject) => {
464
+ const startTime = Date.now();
465
+ const checkCondition = () => {
466
+ if (condition()) {
467
+ loadScript(id, src, attrs);
468
+ resolve();
469
+ } else if (Date.now() - startTime > maxWaitMs) {
470
+ reject(
471
+ new Error(`Timeout waiting for consent condition for script ${id}`)
472
+ );
473
+ } else {
474
+ setTimeout(checkCondition, 100);
475
+ }
476
+ };
477
+ checkCondition();
478
+ });
479
+ }
355
480
  export {
356
481
  ConsentGate,
357
482
  ConsentProvider,
358
483
  CookieBanner,
359
484
  PreferencesModal,
485
+ defaultConsentTheme,
486
+ loadConditionalScript,
360
487
  loadScript,
361
488
  useConsent,
362
489
  useConsentTexts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lgpd-consent",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Biblioteca de consentimento de cookies (LGPD) para React e Next.js, com contexto, banner e modal personalizáveis usando MUI.",
5
5
  "keywords": [
6
6
  "lgpd",
@@ -33,7 +33,7 @@
33
33
  "exports": {
34
34
  ".": {
35
35
  "types": "./dist/index.d.ts",
36
- "import": "./dist/index.mjs",
36
+ "import": "./dist/index.js",
37
37
  "require": "./dist/index.cjs"
38
38
  },
39
39
  "./package.json": "./package.json"
@@ -42,7 +42,8 @@
42
42
  "files": [
43
43
  "dist",
44
44
  "README.md",
45
- "LICENSE"
45
+ "LICENSE",
46
+ "COMPLIANCE.md"
46
47
  ],
47
48
  "engines": {
48
49
  "node": ">=18.18"