react-lgpd-consent 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 @lucianoedipo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # react-lgpd-consent 🍪
2
+
3
+ [![NPM Version](https://img.shields.io/npm/v/react-lgpd-consent?style=for-the-badge&color=blue)](https://www.npmjs.com/package/react-lgpd-consent)
4
+ [![License](https://img.shields.io/npm/l/react-lgpd-consent?style=for-the-badge)](./LICENSE)
5
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
6
+ [![React](https://img.shields.io/badge/React-18%2B-61DAFB?style=for-the-badge&logo=react)](https://reactjs.org/)
7
+ [![Material-UI](https://img.shields.io/badge/MUI-Ready-007FFF?style=for-the-badge&logo=mui)](https://mui.com/)
8
+
9
+ > **Biblioteca completa de consentimento de cookies para React e Next.js em conformidade com a LGPD**
10
+
11
+ Solução moderna, acessível e personalizável para gerenciar consentimento de cookies em aplicações React, com suporte completo a SSR, Material-UI e TypeScript.
12
+
13
+ ## ✨ Características Principais
14
+
15
+ - 🇧🇷 **Conformidade LGPD**: Respeita totalmente a legislação brasileira de proteção de dados
16
+ - ⚡ **SSR/Next.js Ready**: Suporte nativo a Server-Side Rendering sem flash de conteúdo
17
+ - 🎨 **Material-UI Integration**: Componentes prontos e customizáveis com MUI
18
+ - ♿ **Acessibilidade**: Navegação por teclado e leitores de tela nativamente suportados
19
+ - 🌐 **Internacionalização**: Textos totalmente customizáveis (padrão pt-BR)
20
+ - 🚀 **TypeScript**: API completamente tipada para melhor DX
21
+ - 📦 **Zero Config**: Funciona out-of-the-box com configurações sensatas
22
+ - 🎯 **Granular Control**: Controle individual de categorias (analytics, marketing, etc.)
23
+
24
+ ## 🚀 Instalação
25
+
26
+ ```bash
27
+ npm install react-lgpd-consent
28
+ # ou
29
+ yarn add react-lgpd-consent
30
+ # ou
31
+ pnpm add react-lgpd-consent
32
+ ```
33
+
34
+ ### Dependências
35
+
36
+ ```bash
37
+ npm install @mui/material js-cookie
38
+ ```
39
+
40
+ ## 📖 Uso Básico
41
+
42
+ ### 1. Setup do Provider
43
+
44
+ ```tsx
45
+ import { ConsentProvider } from 'react-lgpd-consent'
46
+
47
+ function App() {
48
+ return (
49
+ <ConsentProvider>
50
+ <YourApp />
51
+ </ConsentProvider>
52
+ )
53
+ }
54
+ ```
55
+
56
+ ### 2. Banner de Consentimento
57
+
58
+ ```tsx
59
+ import { CookieBanner } from 'react-lgpd-consent'
60
+
61
+ function Layout() {
62
+ return (
63
+ <>
64
+ <YourContent />
65
+ <CookieBanner policyLinkUrl="/politica-privacidade" />
66
+ </>
67
+ )
68
+ }
69
+ ```
70
+
71
+ ### 3. Uso do Hook
72
+
73
+ ```tsx
74
+ import { useConsent } from 'react-lgpd-consent'
75
+
76
+ function MyComponent() {
77
+ const { consented, preferences, acceptAll, openPreferences } = useConsent()
78
+
79
+ return (
80
+ <div>
81
+ <p>Consentimento: {consented ? 'Dado' : 'Pendente'}</p>
82
+ <button onClick={acceptAll}>Aceitar Todos</button>
83
+ <button onClick={openPreferences}>Gerenciar Preferências</button>
84
+ </div>
85
+ )
86
+ }
87
+ ```
88
+
89
+ ### 4. Carregamento Condicional de Scripts
90
+
91
+ ```tsx
92
+ import { ConsentGate, loadScript } from 'react-lgpd-consent'
93
+
94
+ function Analytics() {
95
+ return (
96
+ <ConsentGate category="analytics">
97
+ <GoogleAnalytics />
98
+ </ConsentGate>
99
+ )
100
+ }
101
+
102
+ // Ou carregando scripts dinamicamente
103
+ function MyComponent() {
104
+ const { preferences } = useConsent()
105
+
106
+ useEffect(() => {
107
+ if (preferences.analytics) {
108
+ loadScript('ga', 'https://www.googletagmanager.com/gtag/js?id=GA_ID')
109
+ }
110
+ }, [preferences.analytics])
111
+ }
112
+ ```
113
+
114
+ ## 🎨 Customização
115
+
116
+ ### Textos Personalizados
117
+
118
+ ```tsx
119
+ <ConsentProvider
120
+ texts={{
121
+ bannerMessage: "Utilizamos cookies para melhorar sua experiência.",
122
+ acceptAll: "Aceitar Todos",
123
+ declineAll: "Recusar Opcionais",
124
+ preferences: "Configurar"
125
+ }}
126
+ >
127
+ ```
128
+
129
+ ### Configuração do Cookie
130
+
131
+ ```tsx
132
+ <ConsentProvider
133
+ cookie={{
134
+ name: 'meuSiteConsent',
135
+ maxAgeDays: 180,
136
+ sameSite: 'Strict'
137
+ }}
138
+ >
139
+ ```
140
+
141
+ ### Callbacks
142
+
143
+ ```tsx
144
+ <ConsentProvider
145
+ onConsentGiven={(state) => {
146
+ console.log('Consentimento dado:', state)
147
+ // Inicializar analytics, etc.
148
+ }}
149
+ onPreferencesSaved={(prefs) => {
150
+ console.log('Preferências salvas:', prefs)
151
+ }}
152
+ >
153
+ ```
154
+
155
+ ## 🔧 API Completa
156
+
157
+ ### Components
158
+
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` |
165
+
166
+ ### Hook `useConsent()`
167
+
168
+ ```typescript
169
+ interface ConsentContextValue {
170
+ consented: boolean // usuário já consentiu?
171
+ preferences: ConsentPreferences // preferências atuais
172
+ acceptAll(): void // aceitar todas as categorias
173
+ rejectAll(): void // recusar opcionais
174
+ setPreference(cat: Category, value: boolean): void // definir categoria específica
175
+ openPreferences(): void // abrir modal de preferências
176
+ closePreferences(): void // fechar modal
177
+ resetConsent(): void // resetar tudo
178
+ }
179
+ ```
180
+
181
+ ### Utilitários
182
+
183
+ - `loadScript(id, src, attrs?)` - Carrega scripts dinamicamente
184
+ - Tipos TypeScript completos exportados
185
+
186
+ ## 🌐 SSR / Next.js
187
+
188
+ Para evitar flash de conteúdo em SSR:
189
+
190
+ ```tsx
191
+ // pages/_app.tsx (Next.js)
192
+ function MyApp({ Component, pageProps }) {
193
+ return (
194
+ <ConsentProvider
195
+ initialState={{
196
+ consented: false,
197
+ preferences: { analytics: false, marketing: false },
198
+ }}
199
+ >
200
+ <Component {...pageProps} />
201
+ </ConsentProvider>
202
+ )
203
+ }
204
+ ```
205
+
206
+ ## ♿ Acessibilidade
207
+
208
+ A biblioteca segue as melhores práticas de acessibilidade:
209
+
210
+ - ✅ Navegação por teclado (Tab, Enter, Escape)
211
+ - ✅ Leitores de tela (`aria-labelledby`, `aria-describedby`)
212
+ - ✅ Foco gerenciado automaticamente
213
+ - ✅ Contrastes adequados
214
+ - ✅ Estrutura semântica correta
215
+
216
+ ## 📚 Exemplos
217
+
218
+ Confira exemplos completos no repositório:
219
+
220
+ - [Básico com React](./examples/basic)
221
+ - [Next.js com SSR](./examples/nextjs)
222
+ - [Customização avançada](./examples/advanced)
223
+ - [Integração com analytics](./examples/analytics)
224
+
225
+ ## 🤝 Contribuição
226
+
227
+ Contribuições são bem-vindas! Por favor:
228
+
229
+ 1. Faça fork do projeto
230
+ 2. Crie uma branch para sua feature (`git checkout -b feature/AmazingFeature`)
231
+ 3. Commit suas mudanças (`git commit -m 'Add some AmazingFeature'`)
232
+ 4. Push para a branch (`git push origin feature/AmazingFeature`)
233
+ 5. Abra um Pull Request
234
+
235
+ ## 📄 Licença
236
+
237
+ Este projeto está licenciado sob a Licença MIT - veja o arquivo [LICENSE](LICENSE) para detalhes.
238
+
239
+ ## 🙋‍♀️ Suporte
240
+
241
+ - 📖 [Documentação](./docs)
242
+ - 🐛 [Issues](https://github.com/lucianoedipo/react-lgpd-consent/issues)
243
+ - 💬 [Discussões](https://github.com/lucianoedipo/react-lgpd-consent/discussions)
244
+
245
+ ---
246
+
247
+ <div align="center">
248
+
249
+ **Feito com ❤️ para a comunidade React brasileira**
250
+
251
+ </div>
package/dist/index.cjs ADDED
@@ -0,0 +1,406 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConsentGate: () => ConsentGate,
34
+ ConsentProvider: () => ConsentProvider,
35
+ CookieBanner: () => CookieBanner,
36
+ PreferencesModal: () => PreferencesModal,
37
+ loadScript: () => loadScript,
38
+ useConsent: () => useConsent,
39
+ useConsentTexts: () => useConsentTexts
40
+ });
41
+ module.exports = __toCommonJS(index_exports);
42
+
43
+ // src/components/CookieBanner.tsx
44
+ var import_Button = __toESM(require("@mui/material/Button"), 1);
45
+ var import_Link = __toESM(require("@mui/material/Link"), 1);
46
+ var import_Paper = __toESM(require("@mui/material/Paper"), 1);
47
+ var import_Snackbar = __toESM(require("@mui/material/Snackbar"), 1);
48
+ var import_Stack = __toESM(require("@mui/material/Stack"), 1);
49
+ var import_Typography = __toESM(require("@mui/material/Typography"), 1);
50
+
51
+ // src/context/ConsentContext.tsx
52
+ var React = __toESM(require("react"), 1);
53
+
54
+ // src/utils/cookieUtils.ts
55
+ var import_js_cookie = __toESM(require("js-cookie"), 1);
56
+ var DEFAULT_COOKIE_OPTS = {
57
+ name: "cookieConsent",
58
+ maxAgeDays: 365,
59
+ sameSite: "Lax",
60
+ secure: true,
61
+ path: "/"
62
+ };
63
+ function readConsentCookie(name = DEFAULT_COOKIE_OPTS.name) {
64
+ if (typeof document === "undefined") return null;
65
+ const raw = import_js_cookie.default.get(name);
66
+ if (!raw) return null;
67
+ try {
68
+ return JSON.parse(raw);
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ function writeConsentCookie(state, opts) {
74
+ if (typeof document === "undefined") return;
75
+ const o = { ...DEFAULT_COOKIE_OPTS, ...opts };
76
+ import_js_cookie.default.set(o.name, JSON.stringify(state), {
77
+ expires: o.maxAgeDays,
78
+ sameSite: o.sameSite,
79
+ secure: o.secure,
80
+ path: o.path
81
+ });
82
+ }
83
+ function removeConsentCookie(opts) {
84
+ if (typeof document === "undefined") return;
85
+ const o = { ...DEFAULT_COOKIE_OPTS, ...opts };
86
+ import_js_cookie.default.remove(o.name, { path: o.path });
87
+ }
88
+
89
+ // src/context/ConsentContext.tsx
90
+ var import_jsx_runtime = require("react/jsx-runtime");
91
+ var DEFAULT_PREFERENCES = {
92
+ analytics: false,
93
+ marketing: false
94
+ };
95
+ var DEFAULT_TEXTS = {
96
+ bannerMessage: "Utilizamos cookies para melhorar sua experi\xEAncia.",
97
+ acceptAll: "Aceitar todos",
98
+ declineAll: "Recusar",
99
+ preferences: "Prefer\xEAncias",
100
+ policyLink: "Saiba mais",
101
+ modalTitle: "Prefer\xEAncias de Cookies",
102
+ modalIntro: "Ajuste as categorias de cookies. Cookies necess\xE1rios s\xE3o sempre utilizados para funcionalidades b\xE1sicas.",
103
+ save: "Salvar prefer\xEAncias",
104
+ necessaryAlwaysOn: "Cookies necess\xE1rios (sempre ativos)"
105
+ };
106
+ function reducer(state, action) {
107
+ switch (action.type) {
108
+ case "ACCEPT_ALL":
109
+ return {
110
+ consented: true,
111
+ preferences: { analytics: true, marketing: true },
112
+ isModalOpen: false
113
+ };
114
+ case "REJECT_ALL":
115
+ return {
116
+ consented: true,
117
+ preferences: { analytics: false, marketing: false },
118
+ isModalOpen: false
119
+ };
120
+ case "SET_CATEGORY":
121
+ return {
122
+ ...state,
123
+ preferences: {
124
+ ...state.preferences,
125
+ [action.category]: action.value
126
+ }
127
+ };
128
+ case "OPEN_MODAL":
129
+ return { ...state, isModalOpen: true };
130
+ case "CLOSE_MODAL":
131
+ return { ...state, isModalOpen: false, consented: true };
132
+ // houve interação
133
+ case "RESET":
134
+ return {
135
+ consented: false,
136
+ preferences: { ...DEFAULT_PREFERENCES },
137
+ isModalOpen: false
138
+ };
139
+ case "HYDRATE":
140
+ return { ...action.state };
141
+ default:
142
+ return state;
143
+ }
144
+ }
145
+ var StateCtx = React.createContext(null);
146
+ var ActionsCtx = React.createContext(null);
147
+ var TextsCtx = React.createContext(DEFAULT_TEXTS);
148
+ function ConsentProvider({
149
+ initialState,
150
+ texts: textsProp,
151
+ onConsentGiven,
152
+ onPreferencesSaved,
153
+ cookie: cookieOpts,
154
+ children
155
+ }) {
156
+ const texts = React.useMemo(
157
+ () => ({ ...DEFAULT_TEXTS, ...textsProp ?? {} }),
158
+ [textsProp]
159
+ );
160
+ const cookie = React.useMemo(
161
+ () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
162
+ [cookieOpts]
163
+ );
164
+ const boot = React.useMemo(() => {
165
+ if (initialState) return { ...initialState, isModalOpen: false };
166
+ const saved = readConsentCookie(cookie.name);
167
+ return saved ?? {
168
+ consented: false,
169
+ preferences: { ...DEFAULT_PREFERENCES },
170
+ isModalOpen: false
171
+ };
172
+ }, [initialState, cookie.name]);
173
+ const [state, dispatch] = React.useReducer(reducer, boot);
174
+ React.useEffect(() => {
175
+ if (state.consented) writeConsentCookie(state, cookie);
176
+ }, [state, cookie]);
177
+ const prevConsented = React.useRef(state.consented);
178
+ React.useEffect(() => {
179
+ if (!prevConsented.current && state.consented && onConsentGiven)
180
+ onConsentGiven(state);
181
+ prevConsented.current = state.consented;
182
+ }, [state, onConsentGiven]);
183
+ const prevPrefs = React.useRef(state.preferences);
184
+ React.useEffect(() => {
185
+ if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
186
+ onPreferencesSaved(state.preferences);
187
+ prevPrefs.current = state.preferences;
188
+ }
189
+ }, [state, onPreferencesSaved]);
190
+ const api = React.useMemo(() => {
191
+ const acceptAll = () => dispatch({ type: "ACCEPT_ALL" });
192
+ const rejectAll = () => dispatch({ type: "REJECT_ALL" });
193
+ const setPreference = (category, value) => dispatch({ type: "SET_CATEGORY", category, value });
194
+ const openPreferences = () => dispatch({ type: "OPEN_MODAL" });
195
+ const closePreferences = () => dispatch({ type: "CLOSE_MODAL" });
196
+ const resetConsent = () => {
197
+ removeConsentCookie(cookie);
198
+ dispatch({ type: "RESET" });
199
+ };
200
+ return {
201
+ consented: !!state.consented,
202
+ preferences: state.preferences,
203
+ acceptAll,
204
+ rejectAll,
205
+ setPreference,
206
+ openPreferences,
207
+ closePreferences,
208
+ resetConsent
209
+ };
210
+ }, [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 }) }) });
212
+ }
213
+ function useConsentStateInternal() {
214
+ const ctx = React.useContext(StateCtx);
215
+ if (!ctx)
216
+ throw new Error("useConsentState must be used within ConsentProvider");
217
+ return ctx;
218
+ }
219
+ function useConsentActionsInternal() {
220
+ const ctx = React.useContext(ActionsCtx);
221
+ if (!ctx)
222
+ throw new Error("useConsentActions must be used within ConsentProvider");
223
+ return ctx;
224
+ }
225
+ function useConsentTextsInternal() {
226
+ const ctx = React.useContext(TextsCtx);
227
+ return ctx;
228
+ }
229
+
230
+ // src/hooks/useConsent.ts
231
+ function useConsent() {
232
+ const state = useConsentStateInternal();
233
+ const actions = useConsentActionsInternal();
234
+ return {
235
+ consented: state.consented,
236
+ preferences: state.preferences,
237
+ acceptAll: actions.acceptAll,
238
+ rejectAll: actions.rejectAll,
239
+ setPreference: actions.setPreference,
240
+ openPreferences: actions.openPreferences,
241
+ closePreferences: actions.closePreferences,
242
+ resetConsent: actions.resetConsent
243
+ };
244
+ }
245
+ function useConsentTexts() {
246
+ return useConsentTextsInternal();
247
+ }
248
+
249
+ // src/components/CookieBanner.tsx
250
+ var import_jsx_runtime2 = require("react/jsx-runtime");
251
+ function CookieBanner({
252
+ policyLinkUrl,
253
+ debug,
254
+ SnackbarProps,
255
+ PaperProps
256
+ }) {
257
+ const { consented, acceptAll, rejectAll, openPreferences } = useConsent();
258
+ const texts = useConsentTexts();
259
+ const open = debug ? true : !consented;
260
+ if (!open) return null;
261
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
262
+ import_Snackbar.default,
263
+ {
264
+ open,
265
+ anchorOrigin: { vertical: "bottom", horizontal: "center" },
266
+ ...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
+ )
304
+ }
305
+ );
306
+ }
307
+
308
+ // src/components/PreferencesModal.tsx
309
+ var import_Button2 = __toESM(require("@mui/material/Button"), 1);
310
+ var import_Dialog = __toESM(require("@mui/material/Dialog"), 1);
311
+ var import_DialogActions = __toESM(require("@mui/material/DialogActions"), 1);
312
+ var import_DialogContent = __toESM(require("@mui/material/DialogContent"), 1);
313
+ var import_DialogTitle = __toESM(require("@mui/material/DialogTitle"), 1);
314
+ var import_FormControlLabel = __toESM(require("@mui/material/FormControlLabel"), 1);
315
+ var import_FormGroup = __toESM(require("@mui/material/FormGroup"), 1);
316
+ var import_Switch = __toESM(require("@mui/material/Switch"), 1);
317
+ var import_Typography2 = __toESM(require("@mui/material/Typography"), 1);
318
+ var import_jsx_runtime3 = require("react/jsx-runtime");
319
+ function PreferencesModal({
320
+ DialogProps: DialogProps2
321
+ }) {
322
+ const { preferences, setPreference, closePreferences } = useConsent();
323
+ const texts = useConsentTexts();
324
+ const open = (DialogProps2 && "open" in DialogProps2 ? DialogProps2.open : void 0) ?? true;
325
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
326
+ import_Dialog.default,
327
+ {
328
+ "aria-labelledby": "cookie-pref-title",
329
+ open,
330
+ onClose: closePreferences,
331
+ ...DialogProps2,
332
+ children: [
333
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_DialogTitle.default, { id: "cookie-pref-title", children: texts.modalTitle }),
334
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_DialogContent.default, { dividers: true, children: [
335
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Typography2.default, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_FormGroup.default, { children: [
337
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
338
+ import_FormControlLabel.default,
339
+ {
340
+ control: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
341
+ import_Switch.default,
342
+ {
343
+ checked: preferences.analytics,
344
+ onChange: (e) => setPreference("analytics", e.target.checked)
345
+ }
346
+ ),
347
+ label: "Cookies Anal\xEDticos (medem uso do site)"
348
+ }
349
+ ),
350
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
351
+ import_FormControlLabel.default,
352
+ {
353
+ control: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
354
+ import_Switch.default,
355
+ {
356
+ checked: preferences.marketing,
357
+ onChange: (e) => setPreference("marketing", e.target.checked)
358
+ }
359
+ ),
360
+ label: "Cookies de Marketing/Publicidade"
361
+ }
362
+ ),
363
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
364
+ import_FormControlLabel.default,
365
+ {
366
+ control: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Switch.default, { checked: true, disabled: true }),
367
+ label: texts.necessaryAlwaysOn
368
+ }
369
+ )
370
+ ] })
371
+ ] }),
372
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_DialogActions.default, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Button2.default, { variant: "contained", onClick: closePreferences, children: texts.save }) })
373
+ ]
374
+ }
375
+ );
376
+ }
377
+
378
+ // src/utils/ConsentGate.tsx
379
+ var import_jsx_runtime4 = require("react/jsx-runtime");
380
+ function ConsentGate(props) {
381
+ const { preferences } = useConsent();
382
+ if (!preferences[props.category]) return null;
383
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: props.children });
384
+ }
385
+
386
+ // src/utils/scriptLoader.ts
387
+ function loadScript(id, src, attrs = {}) {
388
+ if (typeof document === "undefined") return;
389
+ if (document.getElementById(id)) return;
390
+ const s = document.createElement("script");
391
+ s.id = id;
392
+ s.src = src;
393
+ s.async = true;
394
+ for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
395
+ document.body.appendChild(s);
396
+ }
397
+ // Annotate the CommonJS export names for ESM import in node:
398
+ 0 && (module.exports = {
399
+ ConsentGate,
400
+ ConsentProvider,
401
+ CookieBanner,
402
+ PreferencesModal,
403
+ loadScript,
404
+ useConsent,
405
+ useConsentTexts
406
+ });
@@ -0,0 +1,125 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PaperProps } from '@mui/material/Paper';
3
+ import { SnackbarProps } from '@mui/material/Snackbar';
4
+ import { DialogProps } from '@mui/material/Dialog';
5
+ import * as React$1 from 'react';
6
+
7
+ interface CookieBannerProps {
8
+ policyLinkUrl?: string;
9
+ debug?: boolean;
10
+ SnackbarProps?: Partial<SnackbarProps>;
11
+ PaperProps?: Partial<PaperProps>;
12
+ }
13
+ declare function CookieBanner({ policyLinkUrl, debug, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
14
+
15
+ interface PreferencesModalProps {
16
+ DialogProps?: Partial<DialogProps>;
17
+ }
18
+ declare function PreferencesModal({ DialogProps, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
19
+
20
+ /**
21
+ * Categoria de consentimento para cookies.
22
+ * Pode ser 'analytics' ou 'marketing'.
23
+ */
24
+ type Category = 'analytics' | 'marketing';
25
+ /**
26
+ * Preferências de consentimento do usuário para cada categoria.
27
+ */
28
+ interface ConsentPreferences {
29
+ analytics: boolean;
30
+ marketing: boolean;
31
+ }
32
+ /**
33
+ * Estado geral do consentimento, incluindo se o usuário consentiu,
34
+ * suas preferências e se o modal está aberto.
35
+ */
36
+ interface ConsentState {
37
+ consented: boolean;
38
+ preferences: ConsentPreferences;
39
+ isModalOpen?: boolean;
40
+ }
41
+ /**
42
+ * Textos utilizados na interface de consentimento.
43
+ */
44
+ interface ConsentTexts {
45
+ bannerMessage: string;
46
+ acceptAll: string;
47
+ declineAll: string;
48
+ preferences: string;
49
+ policyLink?: string;
50
+ modalTitle: string;
51
+ modalIntro: string;
52
+ save: string;
53
+ necessaryAlwaysOn: string;
54
+ }
55
+ /**
56
+ * Opções para configuração do cookie de consentimento.
57
+ */
58
+ interface ConsentCookieOptions {
59
+ /** Nome do cookie. Padrão: 'cookieConsent' */
60
+ name: string;
61
+ /** Tempo de expiração em dias. Padrão: 365 */
62
+ maxAgeDays: number;
63
+ /** Política SameSite do cookie. */
64
+ sameSite: 'Lax' | 'Strict';
65
+ /** Se o cookie deve ser seguro (HTTPS). Padrão: true */
66
+ secure: boolean;
67
+ /** Caminho do cookie. Padrão: '/' */
68
+ path: string;
69
+ }
70
+ /**
71
+ * Propriedades aceitas pelo componente ConsentProvider.
72
+ */
73
+ interface ConsentProviderProps {
74
+ /** Estado inicial do consentimento. */
75
+ initialState?: ConsentState;
76
+ /** Textos customizados para a interface. */
77
+ texts?: Partial<ConsentTexts>;
78
+ /** Callback chamado quando o consentimento é dado. */
79
+ onConsentGiven?: (state: ConsentState) => void;
80
+ /** Callback chamado ao salvar preferências. */
81
+ onPreferencesSaved?: (prefs: ConsentPreferences) => void;
82
+ /** Configurações customizadas do cookie. */
83
+ cookie?: Partial<ConsentCookieOptions>;
84
+ /** Elementos filhos do provider. */
85
+ children: React.ReactNode;
86
+ }
87
+ /**
88
+ * Valor do contexto de consentimento, incluindo estado e métodos de manipulação.
89
+ */
90
+ interface ConsentContextValue {
91
+ /** Indica se o usuário consentiu. */
92
+ consented: boolean;
93
+ /** Preferências atuais do usuário. */
94
+ preferences: ConsentPreferences;
95
+ /** Aceita todas as categorias de consentimento. */
96
+ acceptAll: () => void;
97
+ /** Rejeita todas as categorias de consentimento. */
98
+ rejectAll: () => void;
99
+ /** Define a preferência para uma categoria específica. */
100
+ setPreference: (cat: Category, value: boolean) => void;
101
+ /** Abre o modal de preferências. */
102
+ openPreferences: () => void;
103
+ /** Fecha o modal de preferências. */
104
+ closePreferences: () => void;
105
+ /** Reseta o consentimento do usuário. */
106
+ resetConsent: () => void;
107
+ }
108
+
109
+ declare function ConsentProvider({ initialState, texts: textsProp, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
110
+
111
+ declare function useConsent(): ConsentContextValue;
112
+ /**
113
+ * Hook para acessar textos customizados do ConsentProvider.
114
+ * Útil para componentes personalizados que precisam dos textos configurados.
115
+ */
116
+ declare function useConsentTexts(): ConsentTexts;
117
+
118
+ declare function ConsentGate(props: Readonly<{
119
+ category: Category;
120
+ children: React$1.ReactNode;
121
+ }>): react_jsx_runtime.JSX.Element | null;
122
+
123
+ declare function loadScript(id: string, src: string, attrs?: Record<string, string>): void;
124
+
125
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, loadScript, useConsent, useConsentTexts };
@@ -0,0 +1,125 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PaperProps } from '@mui/material/Paper';
3
+ import { SnackbarProps } from '@mui/material/Snackbar';
4
+ import { DialogProps } from '@mui/material/Dialog';
5
+ import * as React$1 from 'react';
6
+
7
+ interface CookieBannerProps {
8
+ policyLinkUrl?: string;
9
+ debug?: boolean;
10
+ SnackbarProps?: Partial<SnackbarProps>;
11
+ PaperProps?: Partial<PaperProps>;
12
+ }
13
+ declare function CookieBanner({ policyLinkUrl, debug, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
14
+
15
+ interface PreferencesModalProps {
16
+ DialogProps?: Partial<DialogProps>;
17
+ }
18
+ declare function PreferencesModal({ DialogProps, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
19
+
20
+ /**
21
+ * Categoria de consentimento para cookies.
22
+ * Pode ser 'analytics' ou 'marketing'.
23
+ */
24
+ type Category = 'analytics' | 'marketing';
25
+ /**
26
+ * Preferências de consentimento do usuário para cada categoria.
27
+ */
28
+ interface ConsentPreferences {
29
+ analytics: boolean;
30
+ marketing: boolean;
31
+ }
32
+ /**
33
+ * Estado geral do consentimento, incluindo se o usuário consentiu,
34
+ * suas preferências e se o modal está aberto.
35
+ */
36
+ interface ConsentState {
37
+ consented: boolean;
38
+ preferences: ConsentPreferences;
39
+ isModalOpen?: boolean;
40
+ }
41
+ /**
42
+ * Textos utilizados na interface de consentimento.
43
+ */
44
+ interface ConsentTexts {
45
+ bannerMessage: string;
46
+ acceptAll: string;
47
+ declineAll: string;
48
+ preferences: string;
49
+ policyLink?: string;
50
+ modalTitle: string;
51
+ modalIntro: string;
52
+ save: string;
53
+ necessaryAlwaysOn: string;
54
+ }
55
+ /**
56
+ * Opções para configuração do cookie de consentimento.
57
+ */
58
+ interface ConsentCookieOptions {
59
+ /** Nome do cookie. Padrão: 'cookieConsent' */
60
+ name: string;
61
+ /** Tempo de expiração em dias. Padrão: 365 */
62
+ maxAgeDays: number;
63
+ /** Política SameSite do cookie. */
64
+ sameSite: 'Lax' | 'Strict';
65
+ /** Se o cookie deve ser seguro (HTTPS). Padrão: true */
66
+ secure: boolean;
67
+ /** Caminho do cookie. Padrão: '/' */
68
+ path: string;
69
+ }
70
+ /**
71
+ * Propriedades aceitas pelo componente ConsentProvider.
72
+ */
73
+ interface ConsentProviderProps {
74
+ /** Estado inicial do consentimento. */
75
+ initialState?: ConsentState;
76
+ /** Textos customizados para a interface. */
77
+ texts?: Partial<ConsentTexts>;
78
+ /** Callback chamado quando o consentimento é dado. */
79
+ onConsentGiven?: (state: ConsentState) => void;
80
+ /** Callback chamado ao salvar preferências. */
81
+ onPreferencesSaved?: (prefs: ConsentPreferences) => void;
82
+ /** Configurações customizadas do cookie. */
83
+ cookie?: Partial<ConsentCookieOptions>;
84
+ /** Elementos filhos do provider. */
85
+ children: React.ReactNode;
86
+ }
87
+ /**
88
+ * Valor do contexto de consentimento, incluindo estado e métodos de manipulação.
89
+ */
90
+ interface ConsentContextValue {
91
+ /** Indica se o usuário consentiu. */
92
+ consented: boolean;
93
+ /** Preferências atuais do usuário. */
94
+ preferences: ConsentPreferences;
95
+ /** Aceita todas as categorias de consentimento. */
96
+ acceptAll: () => void;
97
+ /** Rejeita todas as categorias de consentimento. */
98
+ rejectAll: () => void;
99
+ /** Define a preferência para uma categoria específica. */
100
+ setPreference: (cat: Category, value: boolean) => void;
101
+ /** Abre o modal de preferências. */
102
+ openPreferences: () => void;
103
+ /** Fecha o modal de preferências. */
104
+ closePreferences: () => void;
105
+ /** Reseta o consentimento do usuário. */
106
+ resetConsent: () => void;
107
+ }
108
+
109
+ declare function ConsentProvider({ initialState, texts: textsProp, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
110
+
111
+ declare function useConsent(): ConsentContextValue;
112
+ /**
113
+ * Hook para acessar textos customizados do ConsentProvider.
114
+ * Útil para componentes personalizados que precisam dos textos configurados.
115
+ */
116
+ declare function useConsentTexts(): ConsentTexts;
117
+
118
+ declare function ConsentGate(props: Readonly<{
119
+ category: Category;
120
+ children: React$1.ReactNode;
121
+ }>): react_jsx_runtime.JSX.Element | null;
122
+
123
+ declare function loadScript(id: string, src: string, attrs?: Record<string, string>): void;
124
+
125
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, loadScript, useConsent, useConsentTexts };
package/dist/index.js ADDED
@@ -0,0 +1,363 @@
1
+ // src/components/CookieBanner.tsx
2
+ import Button from "@mui/material/Button";
3
+ import Link from "@mui/material/Link";
4
+ import Paper from "@mui/material/Paper";
5
+ import Snackbar from "@mui/material/Snackbar";
6
+ import Stack from "@mui/material/Stack";
7
+ import Typography from "@mui/material/Typography";
8
+
9
+ // src/context/ConsentContext.tsx
10
+ import * as React from "react";
11
+
12
+ // src/utils/cookieUtils.ts
13
+ import Cookies from "js-cookie";
14
+ var DEFAULT_COOKIE_OPTS = {
15
+ name: "cookieConsent",
16
+ maxAgeDays: 365,
17
+ sameSite: "Lax",
18
+ secure: true,
19
+ path: "/"
20
+ };
21
+ function readConsentCookie(name = DEFAULT_COOKIE_OPTS.name) {
22
+ if (typeof document === "undefined") return null;
23
+ const raw = Cookies.get(name);
24
+ if (!raw) return null;
25
+ try {
26
+ return JSON.parse(raw);
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ function writeConsentCookie(state, opts) {
32
+ if (typeof document === "undefined") return;
33
+ const o = { ...DEFAULT_COOKIE_OPTS, ...opts };
34
+ Cookies.set(o.name, JSON.stringify(state), {
35
+ expires: o.maxAgeDays,
36
+ sameSite: o.sameSite,
37
+ secure: o.secure,
38
+ path: o.path
39
+ });
40
+ }
41
+ function removeConsentCookie(opts) {
42
+ if (typeof document === "undefined") return;
43
+ const o = { ...DEFAULT_COOKIE_OPTS, ...opts };
44
+ Cookies.remove(o.name, { path: o.path });
45
+ }
46
+
47
+ // src/context/ConsentContext.tsx
48
+ import { jsx } from "react/jsx-runtime";
49
+ var DEFAULT_PREFERENCES = {
50
+ analytics: false,
51
+ marketing: false
52
+ };
53
+ var DEFAULT_TEXTS = {
54
+ bannerMessage: "Utilizamos cookies para melhorar sua experi\xEAncia.",
55
+ acceptAll: "Aceitar todos",
56
+ declineAll: "Recusar",
57
+ preferences: "Prefer\xEAncias",
58
+ policyLink: "Saiba mais",
59
+ modalTitle: "Prefer\xEAncias de Cookies",
60
+ modalIntro: "Ajuste as categorias de cookies. Cookies necess\xE1rios s\xE3o sempre utilizados para funcionalidades b\xE1sicas.",
61
+ save: "Salvar prefer\xEAncias",
62
+ necessaryAlwaysOn: "Cookies necess\xE1rios (sempre ativos)"
63
+ };
64
+ function reducer(state, action) {
65
+ switch (action.type) {
66
+ case "ACCEPT_ALL":
67
+ return {
68
+ consented: true,
69
+ preferences: { analytics: true, marketing: true },
70
+ isModalOpen: false
71
+ };
72
+ case "REJECT_ALL":
73
+ return {
74
+ consented: true,
75
+ preferences: { analytics: false, marketing: false },
76
+ isModalOpen: false
77
+ };
78
+ case "SET_CATEGORY":
79
+ return {
80
+ ...state,
81
+ preferences: {
82
+ ...state.preferences,
83
+ [action.category]: action.value
84
+ }
85
+ };
86
+ case "OPEN_MODAL":
87
+ return { ...state, isModalOpen: true };
88
+ case "CLOSE_MODAL":
89
+ return { ...state, isModalOpen: false, consented: true };
90
+ // houve interação
91
+ case "RESET":
92
+ return {
93
+ consented: false,
94
+ preferences: { ...DEFAULT_PREFERENCES },
95
+ isModalOpen: false
96
+ };
97
+ case "HYDRATE":
98
+ return { ...action.state };
99
+ default:
100
+ return state;
101
+ }
102
+ }
103
+ var StateCtx = React.createContext(null);
104
+ var ActionsCtx = React.createContext(null);
105
+ var TextsCtx = React.createContext(DEFAULT_TEXTS);
106
+ function ConsentProvider({
107
+ initialState,
108
+ texts: textsProp,
109
+ onConsentGiven,
110
+ onPreferencesSaved,
111
+ cookie: cookieOpts,
112
+ children
113
+ }) {
114
+ const texts = React.useMemo(
115
+ () => ({ ...DEFAULT_TEXTS, ...textsProp ?? {} }),
116
+ [textsProp]
117
+ );
118
+ const cookie = React.useMemo(
119
+ () => ({ ...DEFAULT_COOKIE_OPTS, ...cookieOpts ?? {} }),
120
+ [cookieOpts]
121
+ );
122
+ const boot = React.useMemo(() => {
123
+ if (initialState) return { ...initialState, isModalOpen: false };
124
+ const saved = readConsentCookie(cookie.name);
125
+ return saved ?? {
126
+ consented: false,
127
+ preferences: { ...DEFAULT_PREFERENCES },
128
+ isModalOpen: false
129
+ };
130
+ }, [initialState, cookie.name]);
131
+ const [state, dispatch] = React.useReducer(reducer, boot);
132
+ React.useEffect(() => {
133
+ if (state.consented) writeConsentCookie(state, cookie);
134
+ }, [state, cookie]);
135
+ const prevConsented = React.useRef(state.consented);
136
+ React.useEffect(() => {
137
+ if (!prevConsented.current && state.consented && onConsentGiven)
138
+ onConsentGiven(state);
139
+ prevConsented.current = state.consented;
140
+ }, [state, onConsentGiven]);
141
+ const prevPrefs = React.useRef(state.preferences);
142
+ React.useEffect(() => {
143
+ if (state.consented && onPreferencesSaved && prevPrefs.current !== state.preferences) {
144
+ onPreferencesSaved(state.preferences);
145
+ prevPrefs.current = state.preferences;
146
+ }
147
+ }, [state, onPreferencesSaved]);
148
+ const api = React.useMemo(() => {
149
+ const acceptAll = () => dispatch({ type: "ACCEPT_ALL" });
150
+ const rejectAll = () => dispatch({ type: "REJECT_ALL" });
151
+ const setPreference = (category, value) => dispatch({ type: "SET_CATEGORY", category, value });
152
+ const openPreferences = () => dispatch({ type: "OPEN_MODAL" });
153
+ const closePreferences = () => dispatch({ type: "CLOSE_MODAL" });
154
+ const resetConsent = () => {
155
+ removeConsentCookie(cookie);
156
+ dispatch({ type: "RESET" });
157
+ };
158
+ return {
159
+ consented: !!state.consented,
160
+ preferences: state.preferences,
161
+ acceptAll,
162
+ rejectAll,
163
+ setPreference,
164
+ openPreferences,
165
+ closePreferences,
166
+ resetConsent
167
+ };
168
+ }, [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 }) }) });
170
+ }
171
+ function useConsentStateInternal() {
172
+ const ctx = React.useContext(StateCtx);
173
+ if (!ctx)
174
+ throw new Error("useConsentState must be used within ConsentProvider");
175
+ return ctx;
176
+ }
177
+ function useConsentActionsInternal() {
178
+ const ctx = React.useContext(ActionsCtx);
179
+ if (!ctx)
180
+ throw new Error("useConsentActions must be used within ConsentProvider");
181
+ return ctx;
182
+ }
183
+ function useConsentTextsInternal() {
184
+ const ctx = React.useContext(TextsCtx);
185
+ return ctx;
186
+ }
187
+
188
+ // src/hooks/useConsent.ts
189
+ function useConsent() {
190
+ const state = useConsentStateInternal();
191
+ const actions = useConsentActionsInternal();
192
+ return {
193
+ consented: state.consented,
194
+ preferences: state.preferences,
195
+ acceptAll: actions.acceptAll,
196
+ rejectAll: actions.rejectAll,
197
+ setPreference: actions.setPreference,
198
+ openPreferences: actions.openPreferences,
199
+ closePreferences: actions.closePreferences,
200
+ resetConsent: actions.resetConsent
201
+ };
202
+ }
203
+ function useConsentTexts() {
204
+ return useConsentTextsInternal();
205
+ }
206
+
207
+ // src/components/CookieBanner.tsx
208
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
209
+ function CookieBanner({
210
+ policyLinkUrl,
211
+ debug,
212
+ SnackbarProps,
213
+ PaperProps
214
+ }) {
215
+ const { consented, acceptAll, rejectAll, openPreferences } = useConsent();
216
+ const texts = useConsentTexts();
217
+ const open = debug ? true : !consented;
218
+ if (!open) return null;
219
+ return /* @__PURE__ */ jsx2(
220
+ Snackbar,
221
+ {
222
+ open,
223
+ anchorOrigin: { vertical: "bottom", horizontal: "center" },
224
+ ...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
+ )
262
+ }
263
+ );
264
+ }
265
+
266
+ // src/components/PreferencesModal.tsx
267
+ import Button2 from "@mui/material/Button";
268
+ import Dialog from "@mui/material/Dialog";
269
+ import DialogActions from "@mui/material/DialogActions";
270
+ import DialogContent from "@mui/material/DialogContent";
271
+ import DialogTitle from "@mui/material/DialogTitle";
272
+ import FormControlLabel from "@mui/material/FormControlLabel";
273
+ import FormGroup from "@mui/material/FormGroup";
274
+ import Switch from "@mui/material/Switch";
275
+ import Typography2 from "@mui/material/Typography";
276
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
277
+ function PreferencesModal({
278
+ DialogProps: DialogProps2
279
+ }) {
280
+ const { preferences, setPreference, closePreferences } = useConsent();
281
+ const texts = useConsentTexts();
282
+ const open = (DialogProps2 && "open" in DialogProps2 ? DialogProps2.open : void 0) ?? true;
283
+ return /* @__PURE__ */ jsxs2(
284
+ Dialog,
285
+ {
286
+ "aria-labelledby": "cookie-pref-title",
287
+ open,
288
+ onClose: closePreferences,
289
+ ...DialogProps2,
290
+ children: [
291
+ /* @__PURE__ */ jsx3(DialogTitle, { id: "cookie-pref-title", children: texts.modalTitle }),
292
+ /* @__PURE__ */ jsxs2(DialogContent, { dividers: true, children: [
293
+ /* @__PURE__ */ jsx3(Typography2, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
294
+ /* @__PURE__ */ jsxs2(FormGroup, { children: [
295
+ /* @__PURE__ */ jsx3(
296
+ FormControlLabel,
297
+ {
298
+ control: /* @__PURE__ */ jsx3(
299
+ Switch,
300
+ {
301
+ checked: preferences.analytics,
302
+ onChange: (e) => setPreference("analytics", e.target.checked)
303
+ }
304
+ ),
305
+ label: "Cookies Anal\xEDticos (medem uso do site)"
306
+ }
307
+ ),
308
+ /* @__PURE__ */ jsx3(
309
+ FormControlLabel,
310
+ {
311
+ control: /* @__PURE__ */ jsx3(
312
+ Switch,
313
+ {
314
+ checked: preferences.marketing,
315
+ onChange: (e) => setPreference("marketing", e.target.checked)
316
+ }
317
+ ),
318
+ label: "Cookies de Marketing/Publicidade"
319
+ }
320
+ ),
321
+ /* @__PURE__ */ jsx3(
322
+ FormControlLabel,
323
+ {
324
+ control: /* @__PURE__ */ jsx3(Switch, { checked: true, disabled: true }),
325
+ label: texts.necessaryAlwaysOn
326
+ }
327
+ )
328
+ ] })
329
+ ] }),
330
+ /* @__PURE__ */ jsx3(DialogActions, { children: /* @__PURE__ */ jsx3(Button2, { variant: "contained", onClick: closePreferences, children: texts.save }) })
331
+ ]
332
+ }
333
+ );
334
+ }
335
+
336
+ // src/utils/ConsentGate.tsx
337
+ import { Fragment, jsx as jsx4 } from "react/jsx-runtime";
338
+ function ConsentGate(props) {
339
+ const { preferences } = useConsent();
340
+ if (!preferences[props.category]) return null;
341
+ return /* @__PURE__ */ jsx4(Fragment, { children: props.children });
342
+ }
343
+
344
+ // src/utils/scriptLoader.ts
345
+ function loadScript(id, src, attrs = {}) {
346
+ if (typeof document === "undefined") return;
347
+ if (document.getElementById(id)) return;
348
+ const s = document.createElement("script");
349
+ s.id = id;
350
+ s.src = src;
351
+ s.async = true;
352
+ for (const [k, v] of Object.entries(attrs)) s.setAttribute(k, v);
353
+ document.body.appendChild(s);
354
+ }
355
+ export {
356
+ ConsentGate,
357
+ ConsentProvider,
358
+ CookieBanner,
359
+ PreferencesModal,
360
+ loadScript,
361
+ useConsent,
362
+ useConsentTexts
363
+ };
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "react-lgpd-consent",
3
+ "version": "0.1.0",
4
+ "description": "Biblioteca de consentimento de cookies (LGPD) para React e Next.js, com contexto, banner e modal personalizáveis usando MUI.",
5
+ "keywords": [
6
+ "lgpd",
7
+ "cookie",
8
+ "consent",
9
+ "react",
10
+ "nextjs",
11
+ "material-ui",
12
+ "mui",
13
+ "typescript",
14
+ "ssr",
15
+ "js-cookie",
16
+ "consentimento",
17
+ "privacidade",
18
+ "acessibilidade"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "author": {
24
+ "name": "Luciano Édipo",
25
+ "email": "luciano.psilva@anpd.gov.br",
26
+ "url": "https://github.com/lucianoedipo"
27
+ },
28
+ "license": "MIT",
29
+ "type": "module",
30
+ "main": "dist/index.cjs",
31
+ "module": "dist/index.mjs",
32
+ "types": "dist/index.d.ts",
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.mjs",
37
+ "require": "./dist/index.cjs"
38
+ },
39
+ "./package.json": "./package.json"
40
+ },
41
+ "sideEffects": false,
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "engines": {
48
+ "node": ">=18.18"
49
+ },
50
+ "packageManager": "pnpm@9.0.0",
51
+ "scripts": {
52
+ "clean": "rimraf dist",
53
+ "build": "cross-env NODE_ENV=production tsup src/index.ts --format esm,cjs --dts --clean",
54
+ "dev": "cross-env NODE_ENV=development tsup src/index.ts --format esm,cjs --dts --watch",
55
+ "lint": "eslint . --ext .ts,.tsx",
56
+ "format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
57
+ "type-check": "tsc --noEmit",
58
+ "prepublishOnly": "npm run build"
59
+ },
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "git+https://github.com/lucianoedipo/react-lgpd-consent.git"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/lucianoedipo/react-lgpd-consent/issues"
66
+ },
67
+ "homepage": "https://github.com/lucianoedipo/react-lgpd-consent#readme",
68
+ "peerDependencies": {
69
+ "@mui/material": "^7.0.0 || ^6.0.0 || ^5.15.0",
70
+ "@mui/icons-material": "^7.0.0 || ^6.0.0 || ^5.15.0",
71
+ "react": "^18.2.0 || ^19.0.0",
72
+ "react-dom": "^18.2.0 || ^19.0.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "@mui/icons-material": {
76
+ "optional": true
77
+ }
78
+ },
79
+ "dependencies": {
80
+ "js-cookie": "^3.0.5"
81
+ },
82
+ "devDependencies": {
83
+ "@types/js-cookie": "^3.0.6",
84
+ "@types/react": "^19.1.9",
85
+ "@types/react-dom": "^19.1.7",
86
+ "@mui/material": "^7.3.1",
87
+ "@mui/icons-material": "^7.3.1",
88
+ "react": "^19.1.1",
89
+ "react-dom": "^19.1.1",
90
+ "cross-env": "^10.0.0",
91
+ "eslint": "^9.33.0",
92
+ "eslint-config-prettier": "^10.1.8",
93
+ "eslint-plugin-react-hooks": "^5.2.0",
94
+ "prettier": "^3.6.2",
95
+ "rimraf": "^6.0.1",
96
+ "tsup": "^8.5.0",
97
+ "typescript": "^5.9.2"
98
+ }
99
+ }