react-lgpd-consent 0.1.3 → 0.1.5
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/README.md +140 -14
- package/dist/index.cjs +185 -43
- package/dist/index.d.cts +25 -3
- package/dist/index.d.ts +25 -3
- package/dist/index.js +186 -46
- package/package.json +1 -1
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
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]);
|
|
@@ -200,6 +281,7 @@ function ConsentProvider({
|
|
|
200
281
|
return {
|
|
201
282
|
consented: !!state.consented,
|
|
202
283
|
preferences: state.preferences,
|
|
284
|
+
isModalOpen: state.isModalOpen,
|
|
203
285
|
acceptAll,
|
|
204
286
|
rejectAll,
|
|
205
287
|
setPreference,
|
|
@@ -208,7 +290,7 @@ function ConsentProvider({
|
|
|
208
290
|
resetConsent
|
|
209
291
|
};
|
|
210
292
|
}, [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 }) }) });
|
|
293
|
+
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
294
|
}
|
|
213
295
|
function useConsentStateInternal() {
|
|
214
296
|
const ctx = React.useContext(StateCtx);
|
|
@@ -234,6 +316,7 @@ function useConsent() {
|
|
|
234
316
|
return {
|
|
235
317
|
consented: state.consented,
|
|
236
318
|
preferences: state.preferences,
|
|
319
|
+
isModalOpen: state.isModalOpen,
|
|
237
320
|
acceptAll: actions.acceptAll,
|
|
238
321
|
rejectAll: actions.rejectAll,
|
|
239
322
|
setPreference: actions.setPreference,
|
|
@@ -251,6 +334,8 @@ var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
|
251
334
|
function CookieBanner({
|
|
252
335
|
policyLinkUrl,
|
|
253
336
|
debug,
|
|
337
|
+
blocking = true,
|
|
338
|
+
// Por padrão, bloqueia até decisão
|
|
254
339
|
SnackbarProps,
|
|
255
340
|
PaperProps
|
|
256
341
|
}) {
|
|
@@ -258,49 +343,84 @@ function CookieBanner({
|
|
|
258
343
|
const texts = useConsentTexts();
|
|
259
344
|
const open = debug ? true : !consented;
|
|
260
345
|
if (!open) return null;
|
|
346
|
+
const bannerContent = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
347
|
+
import_Paper.default,
|
|
348
|
+
{
|
|
349
|
+
elevation: 3,
|
|
350
|
+
sx: { p: 2, maxWidth: 720, mx: "auto" },
|
|
351
|
+
...PaperProps,
|
|
352
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Stack.default, { spacing: 1, children: [
|
|
353
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_Typography.default, { variant: "body2", children: [
|
|
354
|
+
texts.bannerMessage,
|
|
355
|
+
" ",
|
|
356
|
+
policyLinkUrl && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
357
|
+
import_Link.default,
|
|
358
|
+
{
|
|
359
|
+
href: policyLinkUrl,
|
|
360
|
+
underline: "hover",
|
|
361
|
+
target: "_blank",
|
|
362
|
+
rel: "noopener noreferrer",
|
|
363
|
+
children: texts.policyLink ?? "Saiba mais"
|
|
364
|
+
}
|
|
365
|
+
)
|
|
366
|
+
] }),
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
368
|
+
import_Stack.default,
|
|
369
|
+
{
|
|
370
|
+
direction: { xs: "column", sm: "row" },
|
|
371
|
+
spacing: 1,
|
|
372
|
+
justifyContent: "flex-end",
|
|
373
|
+
children: [
|
|
374
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
|
|
376
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "text", onClick: openPreferences, children: texts.preferences })
|
|
377
|
+
]
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
] })
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
if (blocking) {
|
|
384
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
385
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
386
|
+
import_Box.default,
|
|
387
|
+
{
|
|
388
|
+
sx: {
|
|
389
|
+
position: "fixed",
|
|
390
|
+
top: 0,
|
|
391
|
+
left: 0,
|
|
392
|
+
right: 0,
|
|
393
|
+
bottom: 0,
|
|
394
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
395
|
+
zIndex: 1299
|
|
396
|
+
// Abaixo do banner mas acima do conteúdo
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
),
|
|
400
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
401
|
+
import_Box.default,
|
|
402
|
+
{
|
|
403
|
+
sx: {
|
|
404
|
+
position: "fixed",
|
|
405
|
+
bottom: 0,
|
|
406
|
+
left: 0,
|
|
407
|
+
right: 0,
|
|
408
|
+
zIndex: 1300,
|
|
409
|
+
// Acima do overlay
|
|
410
|
+
p: 2
|
|
411
|
+
},
|
|
412
|
+
children: bannerContent
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
] });
|
|
416
|
+
}
|
|
261
417
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
262
418
|
import_Snackbar.default,
|
|
263
419
|
{
|
|
264
420
|
open,
|
|
265
421
|
anchorOrigin: { vertical: "bottom", horizontal: "center" },
|
|
266
422
|
...SnackbarProps,
|
|
267
|
-
children:
|
|
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
|
-
)
|
|
423
|
+
children: bannerContent
|
|
304
424
|
}
|
|
305
425
|
);
|
|
306
426
|
}
|
|
@@ -319,9 +439,9 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
319
439
|
function PreferencesModal({
|
|
320
440
|
DialogProps: DialogProps2
|
|
321
441
|
}) {
|
|
322
|
-
const { preferences, setPreference, closePreferences } = useConsent();
|
|
442
|
+
const { preferences, setPreference, closePreferences, isModalOpen } = useConsent();
|
|
323
443
|
const texts = useConsentTexts();
|
|
324
|
-
const open =
|
|
444
|
+
const open = DialogProps2?.open ?? isModalOpen ?? false;
|
|
325
445
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
326
446
|
import_Dialog.default,
|
|
327
447
|
{
|
|
@@ -394,12 +514,34 @@ function loadScript(id, src, attrs = {}) {
|
|
|
394
514
|
for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
|
|
395
515
|
document.body.appendChild(s);
|
|
396
516
|
}
|
|
517
|
+
function loadConditionalScript(id, src, condition, attrs = {}, maxWaitMs = 5e3) {
|
|
518
|
+
if (typeof document === "undefined") return Promise.resolve();
|
|
519
|
+
if (document.getElementById(id)) return Promise.resolve();
|
|
520
|
+
return new Promise((resolve, reject) => {
|
|
521
|
+
const startTime = Date.now();
|
|
522
|
+
const checkCondition = () => {
|
|
523
|
+
if (condition()) {
|
|
524
|
+
loadScript(id, src, attrs);
|
|
525
|
+
resolve();
|
|
526
|
+
} else if (Date.now() - startTime > maxWaitMs) {
|
|
527
|
+
reject(
|
|
528
|
+
new Error(`Timeout waiting for consent condition for script ${id}`)
|
|
529
|
+
);
|
|
530
|
+
} else {
|
|
531
|
+
setTimeout(checkCondition, 100);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
checkCondition();
|
|
535
|
+
});
|
|
536
|
+
}
|
|
397
537
|
// Annotate the CommonJS export names for ESM import in node:
|
|
398
538
|
0 && (module.exports = {
|
|
399
539
|
ConsentGate,
|
|
400
540
|
ConsentProvider,
|
|
401
541
|
CookieBanner,
|
|
402
542
|
PreferencesModal,
|
|
543
|
+
defaultConsentTheme,
|
|
544
|
+
loadConditionalScript,
|
|
403
545
|
loadScript,
|
|
404
546
|
useConsent,
|
|
405
547
|
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,
|
|
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,
|
|
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]);
|
|
@@ -158,6 +237,7 @@ function ConsentProvider({
|
|
|
158
237
|
return {
|
|
159
238
|
consented: !!state.consented,
|
|
160
239
|
preferences: state.preferences,
|
|
240
|
+
isModalOpen: state.isModalOpen,
|
|
161
241
|
acceptAll,
|
|
162
242
|
rejectAll,
|
|
163
243
|
setPreference,
|
|
@@ -166,7 +246,7 @@ function ConsentProvider({
|
|
|
166
246
|
resetConsent
|
|
167
247
|
};
|
|
168
248
|
}, [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 }) }) });
|
|
249
|
+
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
250
|
}
|
|
171
251
|
function useConsentStateInternal() {
|
|
172
252
|
const ctx = React.useContext(StateCtx);
|
|
@@ -192,6 +272,7 @@ function useConsent() {
|
|
|
192
272
|
return {
|
|
193
273
|
consented: state.consented,
|
|
194
274
|
preferences: state.preferences,
|
|
275
|
+
isModalOpen: state.isModalOpen,
|
|
195
276
|
acceptAll: actions.acceptAll,
|
|
196
277
|
rejectAll: actions.rejectAll,
|
|
197
278
|
setPreference: actions.setPreference,
|
|
@@ -205,10 +286,12 @@ function useConsentTexts() {
|
|
|
205
286
|
}
|
|
206
287
|
|
|
207
288
|
// src/components/CookieBanner.tsx
|
|
208
|
-
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
289
|
+
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
209
290
|
function CookieBanner({
|
|
210
291
|
policyLinkUrl,
|
|
211
292
|
debug,
|
|
293
|
+
blocking = true,
|
|
294
|
+
// Por padrão, bloqueia até decisão
|
|
212
295
|
SnackbarProps,
|
|
213
296
|
PaperProps
|
|
214
297
|
}) {
|
|
@@ -216,49 +299,84 @@ function CookieBanner({
|
|
|
216
299
|
const texts = useConsentTexts();
|
|
217
300
|
const open = debug ? true : !consented;
|
|
218
301
|
if (!open) return null;
|
|
302
|
+
const bannerContent = /* @__PURE__ */ jsx2(
|
|
303
|
+
Paper,
|
|
304
|
+
{
|
|
305
|
+
elevation: 3,
|
|
306
|
+
sx: { p: 2, maxWidth: 720, mx: "auto" },
|
|
307
|
+
...PaperProps,
|
|
308
|
+
children: /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
|
|
309
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "body2", children: [
|
|
310
|
+
texts.bannerMessage,
|
|
311
|
+
" ",
|
|
312
|
+
policyLinkUrl && /* @__PURE__ */ jsx2(
|
|
313
|
+
Link,
|
|
314
|
+
{
|
|
315
|
+
href: policyLinkUrl,
|
|
316
|
+
underline: "hover",
|
|
317
|
+
target: "_blank",
|
|
318
|
+
rel: "noopener noreferrer",
|
|
319
|
+
children: texts.policyLink ?? "Saiba mais"
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
] }),
|
|
323
|
+
/* @__PURE__ */ jsxs(
|
|
324
|
+
Stack,
|
|
325
|
+
{
|
|
326
|
+
direction: { xs: "column", sm: "row" },
|
|
327
|
+
spacing: 1,
|
|
328
|
+
justifyContent: "flex-end",
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsx2(Button, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
|
|
331
|
+
/* @__PURE__ */ jsx2(Button, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
|
|
332
|
+
/* @__PURE__ */ jsx2(Button, { variant: "text", onClick: openPreferences, children: texts.preferences })
|
|
333
|
+
]
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
] })
|
|
337
|
+
}
|
|
338
|
+
);
|
|
339
|
+
if (blocking) {
|
|
340
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
341
|
+
/* @__PURE__ */ jsx2(
|
|
342
|
+
Box,
|
|
343
|
+
{
|
|
344
|
+
sx: {
|
|
345
|
+
position: "fixed",
|
|
346
|
+
top: 0,
|
|
347
|
+
left: 0,
|
|
348
|
+
right: 0,
|
|
349
|
+
bottom: 0,
|
|
350
|
+
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
|
351
|
+
zIndex: 1299
|
|
352
|
+
// Abaixo do banner mas acima do conteúdo
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
),
|
|
356
|
+
/* @__PURE__ */ jsx2(
|
|
357
|
+
Box,
|
|
358
|
+
{
|
|
359
|
+
sx: {
|
|
360
|
+
position: "fixed",
|
|
361
|
+
bottom: 0,
|
|
362
|
+
left: 0,
|
|
363
|
+
right: 0,
|
|
364
|
+
zIndex: 1300,
|
|
365
|
+
// Acima do overlay
|
|
366
|
+
p: 2
|
|
367
|
+
},
|
|
368
|
+
children: bannerContent
|
|
369
|
+
}
|
|
370
|
+
)
|
|
371
|
+
] });
|
|
372
|
+
}
|
|
219
373
|
return /* @__PURE__ */ jsx2(
|
|
220
374
|
Snackbar,
|
|
221
375
|
{
|
|
222
376
|
open,
|
|
223
377
|
anchorOrigin: { vertical: "bottom", horizontal: "center" },
|
|
224
378
|
...SnackbarProps,
|
|
225
|
-
children:
|
|
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
|
-
)
|
|
379
|
+
children: bannerContent
|
|
262
380
|
}
|
|
263
381
|
);
|
|
264
382
|
}
|
|
@@ -277,9 +395,9 @@ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
|
277
395
|
function PreferencesModal({
|
|
278
396
|
DialogProps: DialogProps2
|
|
279
397
|
}) {
|
|
280
|
-
const { preferences, setPreference, closePreferences } = useConsent();
|
|
398
|
+
const { preferences, setPreference, closePreferences, isModalOpen } = useConsent();
|
|
281
399
|
const texts = useConsentTexts();
|
|
282
|
-
const open =
|
|
400
|
+
const open = DialogProps2?.open ?? isModalOpen ?? false;
|
|
283
401
|
return /* @__PURE__ */ jsxs2(
|
|
284
402
|
Dialog,
|
|
285
403
|
{
|
|
@@ -334,11 +452,11 @@ function PreferencesModal({
|
|
|
334
452
|
}
|
|
335
453
|
|
|
336
454
|
// src/utils/ConsentGate.tsx
|
|
337
|
-
import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
|
|
455
|
+
import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
|
|
338
456
|
function ConsentGate(props) {
|
|
339
457
|
const { preferences } = useConsent();
|
|
340
458
|
if (!preferences[props.category]) return null;
|
|
341
|
-
return /* @__PURE__ */ jsx4(
|
|
459
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: props.children });
|
|
342
460
|
}
|
|
343
461
|
|
|
344
462
|
// src/utils/scriptLoader.ts
|
|
@@ -352,11 +470,33 @@ function loadScript(id, src, attrs = {}) {
|
|
|
352
470
|
for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
|
|
353
471
|
document.body.appendChild(s);
|
|
354
472
|
}
|
|
473
|
+
function loadConditionalScript(id, src, condition, attrs = {}, maxWaitMs = 5e3) {
|
|
474
|
+
if (typeof document === "undefined") return Promise.resolve();
|
|
475
|
+
if (document.getElementById(id)) return Promise.resolve();
|
|
476
|
+
return new Promise((resolve, reject) => {
|
|
477
|
+
const startTime = Date.now();
|
|
478
|
+
const checkCondition = () => {
|
|
479
|
+
if (condition()) {
|
|
480
|
+
loadScript(id, src, attrs);
|
|
481
|
+
resolve();
|
|
482
|
+
} else if (Date.now() - startTime > maxWaitMs) {
|
|
483
|
+
reject(
|
|
484
|
+
new Error(`Timeout waiting for consent condition for script ${id}`)
|
|
485
|
+
);
|
|
486
|
+
} else {
|
|
487
|
+
setTimeout(checkCondition, 100);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
checkCondition();
|
|
491
|
+
});
|
|
492
|
+
}
|
|
355
493
|
export {
|
|
356
494
|
ConsentGate,
|
|
357
495
|
ConsentProvider,
|
|
358
496
|
CookieBanner,
|
|
359
497
|
PreferencesModal,
|
|
498
|
+
defaultConsentTheme,
|
|
499
|
+
loadConditionalScript,
|
|
360
500
|
loadScript,
|
|
361
501
|
useConsent,
|
|
362
502
|
useConsentTexts
|
package/package.json
CHANGED