react-lgpd-consent 0.1.7 → 0.1.11

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 CHANGED
@@ -1,16 +1,40 @@
1
1
  # react-lgpd-consent 🍪
2
2
 
3
- [![NPM Version](https://img.shields.io/npm/v/react-lgpd-consent?style=for-the-badge&color=blue)](https://www.n useEffect(() => {
4
- if (consented && preferences.analytics) {
5
- loadScript(
6
- 'ga',
7
- 'https://www.googletagmanager.com/gtag/js?id=GA_ID',
8
- 'analytics' // Aguarda consentimento finalizado
9
- )
10
- }
11
- }, [preferences, consented])package/react-lgpd-consent)
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)
12
4
  [![License](https://img.shields.io/npm/l/react-lgpd-consent?style=for-the-badge)](https://github.com/lucianoedipo/react-lgpd-consent/blob/main/LICENSE)
13
- [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
5
+ [![TypeScrip```
6
+
7
+ ## 🎛️ Botão Flutuante de Preferências
8
+
9
+ Para facilitar o acesso às configurações após consentimento inicial:
10
+
11
+ ```tsx
12
+ import { FloatingPreferencesButton } from 'react-lgpd-consent'
13
+
14
+ function App() {
15
+ return (
16
+ <ConsentProvider>
17
+ <MeuApp />
18
+ <CookieBanner />
19
+
20
+ {/* Botão flutuante opcional */}
21
+ <FloatingPreferencesButton
22
+ position="bottom-right"
23
+ hideWhenConsented={false}
24
+ tooltip="Configurar Cookies"
25
+ />
26
+ </ConsentProvider>
27
+ )
28
+ }
29
+ ```
30
+
31
+ ### Posições Disponíveis
32
+
33
+ - `bottom-left` | `bottom-right` (padrão)
34
+ - `top-left` | `top-right`
35
+
36
+ ## 🔧 API Completahttps://img.shields.io/badge/TypeScript-Ready-blue?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
37
+
14
38
  [![React](https://img.shields.io/badge/React-18%2B-61DAFB?style=for-the-badge&logo=react)](https://reactjs.org/)
15
39
  [![Material-UI](https://img.shields.io/badge/MUI-Ready-007FFF?style=for-the-badge&logo=mui)](https://mui.com/)
16
40
 
@@ -32,6 +56,7 @@ Solução moderna, acessível e personalizável para gerenciar consentimento de
32
56
  - 🎨 **Sistema de Temas**: Temas customizáveis para integração visual perfeita
33
57
  - ⚡ **Carregamento Condicional**: Scripts só executam após consentimento explícito
34
58
  - 🔌 **Modal Automático**: Modal de preferências incluído automaticamente com lazy loading
59
+ - 🎛️ **Botão Flutuante**: Componente opcional para acesso fácil às preferências
35
60
 
36
61
  ## 🚀 Instalação
37
62
 
@@ -52,7 +77,11 @@ npm install @mui/material js-cookie
52
77
  ## � Exemplo Completo
53
78
 
54
79
  ```tsx
55
- import { ConsentProvider, CookieBanner } from 'react-lgpd-consent'
80
+ import {
81
+ ConsentProvider,
82
+ CookieBanner,
83
+ FloatingPreferencesButton,
84
+ } from 'react-lgpd-consent'
56
85
 
57
86
  function App() {
58
87
  return (
@@ -61,8 +90,11 @@ function App() {
61
90
  <h1>Meu Site</h1>
62
91
  <p>Conteúdo do site...</p>
63
92
 
64
- {/* Banner de cookies */}
93
+ {/* Banner de cookies - Modal incluído automaticamente! */}
65
94
  <CookieBanner policyLinkUrl="/privacy-policy" blocking={true} />
95
+
96
+ {/* Botão flutuante opcional para acesso às preferências */}
97
+ <FloatingPreferencesButton position="bottom-right" />
66
98
  </div>
67
99
  </ConsentProvider>
68
100
  )
@@ -351,12 +383,13 @@ Para controle total, desabilite o modal automático:
351
383
 
352
384
  ### Components
353
385
 
354
- | Componente | Descrição | Props Principais |
355
- | ------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
356
- | `ConsentProvider` | Provider principal do contexto | `initialState`, `texts`, `theme`, `PreferencesModalComponent`, `disableAutomaticModal`, callbacks |
357
- | `CookieBanner` | Banner de consentimento | `policyLinkUrl`, `blocking`, `debug`, pass-through MUI props |
358
- | `PreferencesModal` | Modal de preferências (incluído automaticamente) | `DialogProps` (MUI pass-through) - **Opcional** |
359
- | `ConsentGate` | Renderização condicional por categoria | `category`, `children` |
386
+ | Componente | Descrição | Props Principais |
387
+ | --------------------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------- |
388
+ | `ConsentProvider` | Provider principal do contexto | `initialState`, `texts`, `theme`, `hideBranding`, `PreferencesModalComponent`, callbacks |
389
+ | `CookieBanner` | Banner de consentimento | `policyLinkUrl`, `blocking`, `hideBranding`, `debug`, pass-through MUI props |
390
+ | `PreferencesModal` | Modal de preferências (incluído automaticamente) | `DialogProps`, `hideBranding` - **Opcional** |
391
+ | `FloatingPreferencesButton` | Botão flutuante para abrir preferências | `position`, `hideWhenConsented`, `tooltip`, `icon`, `FabProps` |
392
+ | `ConsentGate` | Renderização condicional por categoria | `category`, `children` |
360
393
 
361
394
  ### Hook `useConsent()`
362
395
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  PreferencesModal
3
- } from "./chunk-Y4XEAQXV.js";
3
+ } from "./chunk-LRMSFSP2.js";
4
4
  export {
5
5
  PreferencesModal
6
6
  };
@@ -7,8 +7,8 @@ import DialogTitle from "@mui/material/DialogTitle";
7
7
  import FormControlLabel from "@mui/material/FormControlLabel";
8
8
  import FormGroup from "@mui/material/FormGroup";
9
9
  import Switch from "@mui/material/Switch";
10
- import Typography from "@mui/material/Typography";
11
- import { useState, useEffect as useEffect2 } from "react";
10
+ import Typography2 from "@mui/material/Typography";
11
+ import { useEffect as useEffect2, useState as useState2 } from "react";
12
12
 
13
13
  // src/context/ConsentContext.tsx
14
14
  import * as React from "react";
@@ -20,7 +20,7 @@ var DEFAULT_COOKIE_OPTS = {
20
20
  name: "cookieConsent",
21
21
  maxAgeDays: 365,
22
22
  sameSite: "Lax",
23
- secure: true,
23
+ secure: typeof window !== "undefined" ? window.location.protocol === "https:" : false,
24
24
  path: "/"
25
25
  };
26
26
  function readConsentCookie(name = DEFAULT_COOKIE_OPTS.name) {
@@ -123,7 +123,7 @@ var defaultConsentTheme = createTheme({
123
123
  // src/context/ConsentContext.tsx
124
124
  import { jsx, jsxs } from "react/jsx-runtime";
125
125
  var PreferencesModal = React.lazy(
126
- () => import("./PreferencesModal-4FDYT7VE.js").then((m) => ({
126
+ () => import("./PreferencesModal-IHXZDNYT.js").then((m) => ({
127
127
  default: m.PreferencesModal
128
128
  }))
129
129
  );
@@ -191,6 +191,7 @@ function reducer(state, action) {
191
191
  var StateCtx = React.createContext(null);
192
192
  var ActionsCtx = React.createContext(null);
193
193
  var TextsCtx = React.createContext(DEFAULT_TEXTS);
194
+ var HydrationCtx = React.createContext(false);
194
195
  function ConsentProvider({
195
196
  initialState,
196
197
  texts: textsProp,
@@ -198,6 +199,7 @@ function ConsentProvider({
198
199
  PreferencesModalComponent,
199
200
  preferencesModalProps = {},
200
201
  disableAutomaticModal = false,
202
+ hideBranding = false,
201
203
  onConsentGiven,
202
204
  onPreferencesSaved,
203
205
  cookie: cookieOpts,
@@ -217,14 +219,24 @@ function ConsentProvider({
217
219
  );
218
220
  const boot = React.useMemo(() => {
219
221
  if (initialState) return { ...initialState, isModalOpen: false };
220
- const saved = readConsentCookie(cookie.name);
221
- return saved ?? {
222
+ return {
222
223
  consented: false,
223
224
  preferences: { ...DEFAULT_PREFERENCES },
224
225
  isModalOpen: false
225
226
  };
226
- }, [initialState, cookie.name]);
227
+ }, [initialState]);
227
228
  const [state, dispatch] = React.useReducer(reducer, boot);
229
+ const [isHydrated, setIsHydrated] = React.useState(false);
230
+ React.useEffect(() => {
231
+ if (typeof window !== "undefined" && !initialState) {
232
+ const saved = readConsentCookie(cookie.name);
233
+ if (saved?.consented) {
234
+ console.log("\u{1F680} Immediate hydration: Cookie found", saved);
235
+ dispatch({ type: "HYDRATE", state: saved });
236
+ }
237
+ }
238
+ setIsHydrated(true);
239
+ }, [cookie.name, initialState]);
228
240
  React.useEffect(() => {
229
241
  if (state.consented) writeConsentCookie(state, cookie);
230
242
  }, [state, cookie]);
@@ -266,10 +278,10 @@ function ConsentProvider({
266
278
  resetConsent
267
279
  };
268
280
  }, [state, cookie]);
269
- return /* @__PURE__ */ jsx(ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ jsx(StateCtx.Provider, { value: state, children: /* @__PURE__ */ jsx(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ jsxs(TextsCtx.Provider, { value: texts, children: [
281
+ 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: /* @__PURE__ */ jsxs(HydrationCtx.Provider, { value: isHydrated, children: [
270
282
  children,
271
- !disableAutomaticModal && /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx(PreferencesModal, {}) })
272
- ] }) }) }) });
283
+ !disableAutomaticModal && /* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ jsx(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ jsx(PreferencesModal, { hideBranding }) })
284
+ ] }) }) }) }) });
273
285
  }
274
286
  function useConsentStateInternal() {
275
287
  const ctx = React.useContext(StateCtx);
@@ -287,6 +299,9 @@ function useConsentTextsInternal() {
287
299
  const ctx = React.useContext(TextsCtx);
288
300
  return ctx;
289
301
  }
302
+ function useConsentHydrationInternal() {
303
+ return React.useContext(HydrationCtx);
304
+ }
290
305
 
291
306
  // src/hooks/useConsent.ts
292
307
  function useConsent() {
@@ -308,15 +323,78 @@ function useConsent() {
308
323
  function useConsentTexts() {
309
324
  return useConsentTextsInternal();
310
325
  }
326
+ function useConsentHydration() {
327
+ return useConsentHydrationInternal();
328
+ }
311
329
 
312
- // src/components/PreferencesModal.tsx
330
+ // src/components/Branding.tsx
331
+ import Link from "@mui/material/Link";
332
+ import Typography from "@mui/material/Typography";
313
333
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
334
+ var brandingStyles = {
335
+ banner: {
336
+ fontSize: "0.65rem",
337
+ textAlign: "center",
338
+ mt: 1,
339
+ opacity: 0.7,
340
+ fontStyle: "italic"
341
+ },
342
+ modal: {
343
+ fontSize: "0.65rem",
344
+ textAlign: "center",
345
+ px: 3,
346
+ pb: 1,
347
+ opacity: 0.7,
348
+ fontStyle: "italic"
349
+ }
350
+ };
351
+ var linkStyles = {
352
+ textDecoration: "none",
353
+ fontWeight: 500,
354
+ "&:hover": {
355
+ textDecoration: "underline"
356
+ }
357
+ };
358
+ function Branding({ variant, hidden = false }) {
359
+ if (hidden) return null;
360
+ return /* @__PURE__ */ jsxs2(
361
+ Typography,
362
+ {
363
+ variant: "caption",
364
+ sx: (theme) => ({
365
+ ...brandingStyles[variant],
366
+ color: theme.palette.text.secondary
367
+ }),
368
+ children: [
369
+ "fornecido por",
370
+ " ",
371
+ /* @__PURE__ */ jsx2(
372
+ Link,
373
+ {
374
+ href: "https://ledipo.eti.br",
375
+ target: "_blank",
376
+ rel: "noopener noreferrer",
377
+ sx: (theme) => ({
378
+ ...linkStyles,
379
+ color: theme.palette.primary.main
380
+ }),
381
+ children: "L\xC9dipO.eti.br"
382
+ }
383
+ )
384
+ ]
385
+ }
386
+ );
387
+ }
388
+
389
+ // src/components/PreferencesModal.tsx
390
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
314
391
  function PreferencesModal2({
315
- DialogProps: DialogProps2
392
+ DialogProps: DialogProps2,
393
+ hideBranding = false
316
394
  }) {
317
395
  const { preferences, setPreferences, closePreferences, isModalOpen } = useConsent();
318
396
  const texts = useConsentTexts();
319
- const [tempPreferences, setTempPreferences] = useState(preferences);
397
+ const [tempPreferences, setTempPreferences] = useState2(preferences);
320
398
  useEffect2(() => {
321
399
  if (isModalOpen) {
322
400
  setTempPreferences(preferences);
@@ -330,7 +408,7 @@ function PreferencesModal2({
330
408
  setTempPreferences(preferences);
331
409
  closePreferences();
332
410
  };
333
- return /* @__PURE__ */ jsxs2(
411
+ return /* @__PURE__ */ jsxs3(
334
412
  Dialog,
335
413
  {
336
414
  "aria-labelledby": "cookie-pref-title",
@@ -338,14 +416,14 @@ function PreferencesModal2({
338
416
  onClose: handleCancel,
339
417
  ...DialogProps2,
340
418
  children: [
341
- /* @__PURE__ */ jsx2(DialogTitle, { id: "cookie-pref-title", children: texts.modalTitle }),
342
- /* @__PURE__ */ jsxs2(DialogContent, { dividers: true, children: [
343
- /* @__PURE__ */ jsx2(Typography, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
344
- /* @__PURE__ */ jsxs2(FormGroup, { children: [
345
- /* @__PURE__ */ jsx2(
419
+ /* @__PURE__ */ jsx3(DialogTitle, { id: "cookie-pref-title", children: texts.modalTitle }),
420
+ /* @__PURE__ */ jsxs3(DialogContent, { dividers: true, children: [
421
+ /* @__PURE__ */ jsx3(Typography2, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
422
+ /* @__PURE__ */ jsxs3(FormGroup, { children: [
423
+ /* @__PURE__ */ jsx3(
346
424
  FormControlLabel,
347
425
  {
348
- control: /* @__PURE__ */ jsx2(
426
+ control: /* @__PURE__ */ jsx3(
349
427
  Switch,
350
428
  {
351
429
  checked: tempPreferences.analytics,
@@ -358,10 +436,10 @@ function PreferencesModal2({
358
436
  label: "Cookies Anal\xEDticos (medem uso do site)"
359
437
  }
360
438
  ),
361
- /* @__PURE__ */ jsx2(
439
+ /* @__PURE__ */ jsx3(
362
440
  FormControlLabel,
363
441
  {
364
- control: /* @__PURE__ */ jsx2(
442
+ control: /* @__PURE__ */ jsx3(
365
443
  Switch,
366
444
  {
367
445
  checked: tempPreferences.marketing,
@@ -374,18 +452,19 @@ function PreferencesModal2({
374
452
  label: "Cookies de Marketing/Publicidade"
375
453
  }
376
454
  ),
377
- /* @__PURE__ */ jsx2(
455
+ /* @__PURE__ */ jsx3(
378
456
  FormControlLabel,
379
457
  {
380
- control: /* @__PURE__ */ jsx2(Switch, { checked: true, disabled: true }),
458
+ control: /* @__PURE__ */ jsx3(Switch, { checked: true, disabled: true }),
381
459
  label: texts.necessaryAlwaysOn
382
460
  }
383
461
  )
384
462
  ] })
385
463
  ] }),
386
- /* @__PURE__ */ jsxs2(DialogActions, { children: [
387
- /* @__PURE__ */ jsx2(Button, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
388
- /* @__PURE__ */ jsx2(Button, { variant: "contained", onClick: handleSave, children: texts.save })
464
+ !hideBranding && /* @__PURE__ */ jsx3(Branding, { variant: "modal" }),
465
+ /* @__PURE__ */ jsxs3(DialogActions, { children: [
466
+ /* @__PURE__ */ jsx3(Button, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
467
+ /* @__PURE__ */ jsx3(Button, { variant: "contained", onClick: handleSave, children: texts.save })
389
468
  ] })
390
469
  ]
391
470
  }
@@ -394,8 +473,10 @@ function PreferencesModal2({
394
473
 
395
474
  export {
396
475
  defaultConsentTheme,
476
+ Branding,
397
477
  PreferencesModal2 as PreferencesModal,
398
478
  ConsentProvider,
399
479
  useConsent,
400
- useConsentTexts
480
+ useConsentTexts,
481
+ useConsentHydration
401
482
  };
package/dist/index.cjs CHANGED
@@ -65,7 +65,7 @@ var init_cookieUtils = __esm({
65
65
  name: "cookieConsent",
66
66
  maxAgeDays: 365,
67
67
  sameSite: "Lax",
68
- secure: true,
68
+ secure: typeof window !== "undefined" ? window.location.protocol === "https:" : false,
69
69
  path: "/"
70
70
  };
71
71
  }
@@ -148,13 +148,79 @@ var init_theme = __esm({
148
148
  }
149
149
  });
150
150
 
151
+ // src/components/Branding.tsx
152
+ function Branding({ variant, hidden = false }) {
153
+ if (hidden) return null;
154
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
155
+ import_Typography.default,
156
+ {
157
+ variant: "caption",
158
+ sx: (theme) => ({
159
+ ...brandingStyles[variant],
160
+ color: theme.palette.text.secondary
161
+ }),
162
+ children: [
163
+ "fornecido por",
164
+ " ",
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
166
+ import_Link.default,
167
+ {
168
+ href: "https://ledipo.eti.br",
169
+ target: "_blank",
170
+ rel: "noopener noreferrer",
171
+ sx: (theme) => ({
172
+ ...linkStyles,
173
+ color: theme.palette.primary.main
174
+ }),
175
+ children: "L\xC9dipO.eti.br"
176
+ }
177
+ )
178
+ ]
179
+ }
180
+ );
181
+ }
182
+ var import_Link, import_Typography, import_jsx_runtime, brandingStyles, linkStyles;
183
+ var init_Branding = __esm({
184
+ "src/components/Branding.tsx"() {
185
+ "use strict";
186
+ import_Link = __toESM(require("@mui/material/Link"), 1);
187
+ import_Typography = __toESM(require("@mui/material/Typography"), 1);
188
+ import_jsx_runtime = require("react/jsx-runtime");
189
+ brandingStyles = {
190
+ banner: {
191
+ fontSize: "0.65rem",
192
+ textAlign: "center",
193
+ mt: 1,
194
+ opacity: 0.7,
195
+ fontStyle: "italic"
196
+ },
197
+ modal: {
198
+ fontSize: "0.65rem",
199
+ textAlign: "center",
200
+ px: 3,
201
+ pb: 1,
202
+ opacity: 0.7,
203
+ fontStyle: "italic"
204
+ }
205
+ };
206
+ linkStyles = {
207
+ textDecoration: "none",
208
+ fontWeight: 500,
209
+ "&:hover": {
210
+ textDecoration: "underline"
211
+ }
212
+ };
213
+ }
214
+ });
215
+
151
216
  // src/components/PreferencesModal.tsx
152
217
  var PreferencesModal_exports = {};
153
218
  __export(PreferencesModal_exports, {
154
219
  PreferencesModal: () => PreferencesModal
155
220
  });
156
221
  function PreferencesModal({
157
- DialogProps: DialogProps2
222
+ DialogProps: DialogProps2,
223
+ hideBranding = false
158
224
  }) {
159
225
  const { preferences, setPreferences, closePreferences, isModalOpen } = useConsent();
160
226
  const texts = useConsentTexts();
@@ -172,7 +238,7 @@ function PreferencesModal({
172
238
  setTempPreferences(preferences);
173
239
  closePreferences();
174
240
  };
175
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
241
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
176
242
  import_Dialog.default,
177
243
  {
178
244
  "aria-labelledby": "cookie-pref-title",
@@ -180,14 +246,14 @@ function PreferencesModal({
180
246
  onClose: handleCancel,
181
247
  ...DialogProps2,
182
248
  children: [
183
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_DialogTitle.default, { id: "cookie-pref-title", children: texts.modalTitle }),
184
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_DialogContent.default, { dividers: true, children: [
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Typography.default, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
186
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_FormGroup.default, { children: [
187
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
249
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_DialogTitle.default, { id: "cookie-pref-title", children: texts.modalTitle }),
250
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_DialogContent.default, { dividers: true, children: [
251
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Typography2.default, { variant: "body2", sx: { mb: 2 }, children: texts.modalIntro }),
252
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_FormGroup.default, { children: [
253
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
188
254
  import_FormControlLabel.default,
189
255
  {
190
- control: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
256
+ control: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
191
257
  import_Switch.default,
192
258
  {
193
259
  checked: tempPreferences.analytics,
@@ -200,10 +266,10 @@ function PreferencesModal({
200
266
  label: "Cookies Anal\xEDticos (medem uso do site)"
201
267
  }
202
268
  ),
203
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
269
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
204
270
  import_FormControlLabel.default,
205
271
  {
206
- control: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
+ control: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
207
273
  import_Switch.default,
208
274
  {
209
275
  checked: tempPreferences.marketing,
@@ -216,24 +282,25 @@ function PreferencesModal({
216
282
  label: "Cookies de Marketing/Publicidade"
217
283
  }
218
284
  ),
219
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
285
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
220
286
  import_FormControlLabel.default,
221
287
  {
222
- control: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Switch.default, { checked: true, disabled: true }),
288
+ control: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Switch.default, { checked: true, disabled: true }),
223
289
  label: texts.necessaryAlwaysOn
224
290
  }
225
291
  )
226
292
  ] })
227
293
  ] }),
228
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_DialogActions.default, { children: [
229
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
230
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_Button.default, { variant: "contained", onClick: handleSave, children: texts.save })
294
+ !hideBranding && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Branding, { variant: "modal" }),
295
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_DialogActions.default, { children: [
296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "outlined", onClick: handleCancel, children: "Cancelar" }),
297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_Button.default, { variant: "contained", onClick: handleSave, children: texts.save })
231
298
  ] })
232
299
  ]
233
300
  }
234
301
  );
235
302
  }
236
- var import_Button, import_Dialog, import_DialogActions, import_DialogContent, import_DialogTitle, import_FormControlLabel, import_FormGroup, import_Switch, import_Typography, import_react, import_jsx_runtime;
303
+ var import_Button, import_Dialog, import_DialogActions, import_DialogContent, import_DialogTitle, import_FormControlLabel, import_FormGroup, import_Switch, import_Typography2, import_react, import_jsx_runtime2;
237
304
  var init_PreferencesModal = __esm({
238
305
  "src/components/PreferencesModal.tsx"() {
239
306
  "use strict";
@@ -245,10 +312,11 @@ var init_PreferencesModal = __esm({
245
312
  import_FormControlLabel = __toESM(require("@mui/material/FormControlLabel"), 1);
246
313
  import_FormGroup = __toESM(require("@mui/material/FormGroup"), 1);
247
314
  import_Switch = __toESM(require("@mui/material/Switch"), 1);
248
- import_Typography = __toESM(require("@mui/material/Typography"), 1);
315
+ import_Typography2 = __toESM(require("@mui/material/Typography"), 1);
249
316
  import_react = require("react");
250
317
  init_useConsent();
251
- import_jsx_runtime = require("react/jsx-runtime");
318
+ init_Branding();
319
+ import_jsx_runtime2 = require("react/jsx-runtime");
252
320
  }
253
321
  });
254
322
 
@@ -306,6 +374,7 @@ function ConsentProvider({
306
374
  PreferencesModalComponent,
307
375
  preferencesModalProps = {},
308
376
  disableAutomaticModal = false,
377
+ hideBranding = false,
309
378
  onConsentGiven,
310
379
  onPreferencesSaved,
311
380
  cookie: cookieOpts,
@@ -325,14 +394,24 @@ function ConsentProvider({
325
394
  );
326
395
  const boot = React.useMemo(() => {
327
396
  if (initialState) return { ...initialState, isModalOpen: false };
328
- const saved = readConsentCookie(cookie.name);
329
- return saved ?? {
397
+ return {
330
398
  consented: false,
331
399
  preferences: { ...DEFAULT_PREFERENCES },
332
400
  isModalOpen: false
333
401
  };
334
- }, [initialState, cookie.name]);
402
+ }, [initialState]);
335
403
  const [state, dispatch] = React.useReducer(reducer, boot);
404
+ const [isHydrated, setIsHydrated] = React.useState(false);
405
+ React.useEffect(() => {
406
+ if (typeof window !== "undefined" && !initialState) {
407
+ const saved = readConsentCookie(cookie.name);
408
+ if (saved?.consented) {
409
+ console.log("\u{1F680} Immediate hydration: Cookie found", saved);
410
+ dispatch({ type: "HYDRATE", state: saved });
411
+ }
412
+ }
413
+ setIsHydrated(true);
414
+ }, [cookie.name, initialState]);
336
415
  React.useEffect(() => {
337
416
  if (state.consented) writeConsentCookie(state, cookie);
338
417
  }, [state, cookie]);
@@ -374,10 +453,10 @@ function ConsentProvider({
374
453
  resetConsent
375
454
  };
376
455
  }, [state, cookie]);
377
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_styles2.ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StateCtx.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(TextsCtx.Provider, { value: texts, children: [
456
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_styles2.ThemeProvider, { theme: appliedTheme, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StateCtx.Provider, { value: state, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionsCtx.Provider, { value: api, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TextsCtx.Provider, { value: texts, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(HydrationCtx.Provider, { value: isHydrated, children: [
378
457
  children,
379
- !disableAutomaticModal && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(React.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(PreferencesModal2, {}) })
380
- ] }) }) }) });
458
+ !disableAutomaticModal && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(React.Suspense, { fallback: null, children: PreferencesModalComponent ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PreferencesModalComponent, { ...preferencesModalProps }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PreferencesModal2, { hideBranding }) })
459
+ ] }) }) }) }) });
381
460
  }
382
461
  function useConsentStateInternal() {
383
462
  const ctx = React.useContext(StateCtx);
@@ -395,7 +474,10 @@ function useConsentTextsInternal() {
395
474
  const ctx = React.useContext(TextsCtx);
396
475
  return ctx;
397
476
  }
398
- var React, import_styles2, import_jsx_runtime2, PreferencesModal2, DEFAULT_PREFERENCES, DEFAULT_TEXTS, StateCtx, ActionsCtx, TextsCtx;
477
+ function useConsentHydrationInternal() {
478
+ return React.useContext(HydrationCtx);
479
+ }
480
+ var React, import_styles2, import_jsx_runtime3, PreferencesModal2, DEFAULT_PREFERENCES, DEFAULT_TEXTS, StateCtx, ActionsCtx, TextsCtx, HydrationCtx;
399
481
  var init_ConsentContext = __esm({
400
482
  "src/context/ConsentContext.tsx"() {
401
483
  "use strict";
@@ -403,7 +485,7 @@ var init_ConsentContext = __esm({
403
485
  import_styles2 = require("@mui/material/styles");
404
486
  init_cookieUtils();
405
487
  init_theme();
406
- import_jsx_runtime2 = require("react/jsx-runtime");
488
+ import_jsx_runtime3 = require("react/jsx-runtime");
407
489
  PreferencesModal2 = React.lazy(
408
490
  () => Promise.resolve().then(() => (init_PreferencesModal(), PreferencesModal_exports)).then((m) => ({
409
491
  default: m.PreferencesModal
@@ -427,6 +509,7 @@ var init_ConsentContext = __esm({
427
509
  StateCtx = React.createContext(null);
428
510
  ActionsCtx = React.createContext(null);
429
511
  TextsCtx = React.createContext(DEFAULT_TEXTS);
512
+ HydrationCtx = React.createContext(false);
430
513
  }
431
514
  });
432
515
 
@@ -450,6 +533,9 @@ function useConsent() {
450
533
  function useConsentTexts() {
451
534
  return useConsentTextsInternal();
452
535
  }
536
+ function useConsentHydration() {
537
+ return useConsentHydrationInternal();
538
+ }
453
539
  var init_useConsent = __esm({
454
540
  "src/hooks/useConsent.ts"() {
455
541
  "use strict";
@@ -463,10 +549,12 @@ __export(index_exports, {
463
549
  ConsentGate: () => ConsentGate,
464
550
  ConsentProvider: () => ConsentProvider,
465
551
  CookieBanner: () => CookieBanner,
552
+ FloatingPreferencesButton: () => FloatingPreferencesButton,
466
553
  PreferencesModal: () => PreferencesModal,
467
554
  defaultConsentTheme: () => defaultConsentTheme,
468
555
  loadScript: () => loadScript,
469
556
  useConsent: () => useConsent,
557
+ useConsentHydration: () => useConsentHydration,
470
558
  useConsentTexts: () => useConsentTexts
471
559
  });
472
560
  module.exports = __toCommonJS(index_exports);
@@ -474,37 +562,40 @@ module.exports = __toCommonJS(index_exports);
474
562
  // src/components/CookieBanner.tsx
475
563
  var import_Button2 = __toESM(require("@mui/material/Button"), 1);
476
564
  var import_Box = __toESM(require("@mui/material/Box"), 1);
477
- var import_Link = __toESM(require("@mui/material/Link"), 1);
478
565
  var import_Paper = __toESM(require("@mui/material/Paper"), 1);
479
566
  var import_Snackbar = __toESM(require("@mui/material/Snackbar"), 1);
480
567
  var import_Stack = __toESM(require("@mui/material/Stack"), 1);
481
- var import_Typography2 = __toESM(require("@mui/material/Typography"), 1);
568
+ var import_Typography3 = __toESM(require("@mui/material/Typography"), 1);
569
+ var import_Link2 = __toESM(require("@mui/material/Link"), 1);
482
570
  init_useConsent();
483
- var import_jsx_runtime3 = require("react/jsx-runtime");
571
+ init_Branding();
572
+ var import_jsx_runtime4 = require("react/jsx-runtime");
484
573
  function CookieBanner({
485
574
  policyLinkUrl,
486
575
  debug,
487
576
  blocking = true,
488
577
  // Por padrão, bloqueia até decisão
578
+ hideBranding = false,
489
579
  SnackbarProps,
490
580
  PaperProps
491
581
  }) {
492
582
  const { consented, acceptAll, rejectAll, openPreferences } = useConsent();
493
583
  const texts = useConsentTexts();
494
- const open = debug ? true : !consented;
584
+ const isHydrated = useConsentHydration();
585
+ const open = debug ? true : isHydrated && !consented;
495
586
  if (!open) return null;
496
- const bannerContent = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
587
+ const bannerContent = /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
497
588
  import_Paper.default,
498
589
  {
499
590
  elevation: 3,
500
591
  sx: { p: 2, maxWidth: 720, mx: "auto" },
501
592
  ...PaperProps,
502
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_Stack.default, { spacing: 1, children: [
503
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_Typography2.default, { variant: "body2", children: [
593
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_Stack.default, { spacing: 1, children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_Typography3.default, { variant: "body2", children: [
504
595
  texts.bannerMessage,
505
596
  " ",
506
- policyLinkUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
507
- import_Link.default,
597
+ policyLinkUrl && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
598
+ import_Link2.default,
508
599
  {
509
600
  href: policyLinkUrl,
510
601
  underline: "hover",
@@ -514,25 +605,26 @@ function CookieBanner({
514
605
  }
515
606
  )
516
607
  ] }),
517
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
608
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
518
609
  import_Stack.default,
519
610
  {
520
611
  direction: { xs: "column", sm: "row" },
521
612
  spacing: 1,
522
613
  justifyContent: "flex-end",
523
614
  children: [
524
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Button2.default, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
525
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Button2.default, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
526
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_Button2.default, { variant: "text", onClick: openPreferences, children: texts.preferences })
615
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_Button2.default, { variant: "outlined", onClick: rejectAll, children: texts.declineAll }),
616
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_Button2.default, { variant: "contained", onClick: acceptAll, children: texts.acceptAll }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_Button2.default, { variant: "text", onClick: openPreferences, children: texts.preferences })
527
618
  ]
528
619
  }
529
- )
620
+ ),
621
+ !hideBranding && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Branding, { variant: "banner" })
530
622
  ] })
531
623
  }
532
624
  );
533
625
  if (blocking) {
534
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
535
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
626
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
627
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
536
628
  import_Box.default,
537
629
  {
538
630
  sx: {
@@ -547,7 +639,7 @@ function CookieBanner({
547
639
  }
548
640
  }
549
641
  ),
550
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
642
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
551
643
  import_Box.default,
552
644
  {
553
645
  sx: {
@@ -564,7 +656,7 @@ function CookieBanner({
564
656
  )
565
657
  ] });
566
658
  }
567
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
659
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
568
660
  import_Snackbar.default,
569
661
  {
570
662
  open,
@@ -577,16 +669,78 @@ function CookieBanner({
577
669
 
578
670
  // src/index.ts
579
671
  init_PreferencesModal();
672
+
673
+ // src/components/FloatingPreferencesButton.tsx
674
+ var import_Fab = __toESM(require("@mui/material/Fab"), 1);
675
+ var import_Tooltip = __toESM(require("@mui/material/Tooltip"), 1);
676
+ var import_Settings = __toESM(require("@mui/icons-material/Settings"), 1);
677
+ var import_styles3 = require("@mui/material/styles");
678
+ init_useConsent();
679
+ var import_jsx_runtime5 = require("react/jsx-runtime");
680
+ function FloatingPreferencesButton({
681
+ position = "bottom-right",
682
+ offset = 24,
683
+ icon = /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_Settings.default, {}),
684
+ tooltip,
685
+ FabProps,
686
+ hideWhenConsented = false
687
+ }) {
688
+ const { openPreferences, consented } = useConsent();
689
+ const theme = (0, import_styles3.useTheme)();
690
+ if (hideWhenConsented && consented) {
691
+ return null;
692
+ }
693
+ const tooltipText = tooltip ?? "Gerenciar Prefer\xEAncias de Cookies";
694
+ const getPosition = () => {
695
+ const styles = {
696
+ position: "fixed",
697
+ zIndex: 1200
698
+ // Abaixo do modal mas acima do conteúdo
699
+ };
700
+ switch (position) {
701
+ case "bottom-left":
702
+ return { ...styles, bottom: offset, left: offset };
703
+ case "bottom-right":
704
+ return { ...styles, bottom: offset, right: offset };
705
+ case "top-left":
706
+ return { ...styles, top: offset, left: offset };
707
+ case "top-right":
708
+ return { ...styles, top: offset, right: offset };
709
+ default:
710
+ return { ...styles, bottom: offset, right: offset };
711
+ }
712
+ };
713
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_Tooltip.default, { title: tooltipText, placement: "top", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
714
+ import_Fab.default,
715
+ {
716
+ size: "medium",
717
+ color: "primary",
718
+ onClick: openPreferences,
719
+ sx: {
720
+ ...getPosition(),
721
+ backgroundColor: theme.palette.primary.main,
722
+ "&:hover": {
723
+ backgroundColor: theme.palette.primary.dark
724
+ }
725
+ },
726
+ "aria-label": tooltipText,
727
+ ...FabProps,
728
+ children: icon
729
+ }
730
+ ) });
731
+ }
732
+
733
+ // src/index.ts
580
734
  init_ConsentContext();
581
735
  init_useConsent();
582
736
 
583
737
  // src/utils/ConsentGate.tsx
584
738
  init_useConsent();
585
- var import_jsx_runtime4 = require("react/jsx-runtime");
739
+ var import_jsx_runtime6 = require("react/jsx-runtime");
586
740
  function ConsentGate(props) {
587
741
  const { preferences } = useConsent();
588
742
  if (!preferences[props.category]) return null;
589
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: props.children });
743
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_jsx_runtime6.Fragment, { children: props.children });
590
744
  }
591
745
 
592
746
  // src/utils/scriptLoader.ts
@@ -633,9 +787,11 @@ init_theme();
633
787
  ConsentGate,
634
788
  ConsentProvider,
635
789
  CookieBanner,
790
+ FloatingPreferencesButton,
636
791
  PreferencesModal,
637
792
  defaultConsentTheme,
638
793
  loadScript,
639
794
  useConsent,
795
+ useConsentHydration,
640
796
  useConsentTexts
641
797
  });
package/dist/index.d.cts CHANGED
@@ -2,6 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PaperProps } from '@mui/material/Paper';
3
3
  import { SnackbarProps } from '@mui/material/Snackbar';
4
4
  import { DialogProps } from '@mui/material/Dialog';
5
+ import { FabProps } from '@mui/material/Fab';
5
6
  import * as React$1 from 'react';
6
7
  import * as _mui_material_styles from '@mui/material/styles';
7
8
 
@@ -9,16 +10,34 @@ interface CookieBannerProps {
9
10
  policyLinkUrl?: string;
10
11
  debug?: boolean;
11
12
  blocking?: boolean;
13
+ hideBranding?: boolean;
12
14
  SnackbarProps?: Partial<SnackbarProps>;
13
15
  PaperProps?: Partial<PaperProps>;
14
16
  }
15
17
  declare function CookieBanner({ policyLinkUrl, debug, blocking, // Por padrão, bloqueia até decisão
16
- SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
18
+ hideBranding, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
17
19
 
18
20
  interface PreferencesModalProps {
19
21
  DialogProps?: Partial<DialogProps>;
22
+ hideBranding?: boolean;
20
23
  }
21
- declare function PreferencesModal({ DialogProps, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
24
+ declare function PreferencesModal({ DialogProps, hideBranding, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
25
+
26
+ interface FloatingPreferencesButtonProps {
27
+ /** Posição do botão flutuante. Padrão: 'bottom-right' */
28
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
29
+ /** Offset da borda em pixels. Padrão: 24 */
30
+ offset?: number;
31
+ /** Ícone customizado. Padrão: SettingsIcon */
32
+ icon?: React.ReactNode;
33
+ /** Tooltip customizado */
34
+ tooltip?: string;
35
+ /** Props do Fab do MUI */
36
+ FabProps?: Partial<FabProps>;
37
+ /** Se deve esconder quando consentimento já foi dado. Padrão: false */
38
+ hideWhenConsented?: boolean;
39
+ }
40
+ declare function FloatingPreferencesButton({ position, offset, icon, tooltip, FabProps, hideWhenConsented, }: Readonly<FloatingPreferencesButtonProps>): react_jsx_runtime.JSX.Element | null;
22
41
 
23
42
  /**
24
43
  * Categoria de consentimento para cookies.
@@ -86,6 +105,8 @@ interface ConsentProviderProps {
86
105
  preferencesModalProps?: Record<string, any>;
87
106
  /** Desabilita o modal automático (para usar componente totalmente customizado). */
88
107
  disableAutomaticModal?: boolean;
108
+ /** Esconde branding "fornecido por LÉdipO.eti.br". */
109
+ hideBranding?: boolean;
89
110
  /** Callback chamado quando o consentimento é dado. */
90
111
  onConsentGiven?: (state: ConsentState) => void;
91
112
  /** Callback chamado ao salvar preferências. */
@@ -121,7 +142,7 @@ interface ConsentContextValue {
121
142
  resetConsent: () => void;
122
143
  }
123
144
 
124
- declare function ConsentProvider({ initialState, texts: textsProp, theme, PreferencesModalComponent, preferencesModalProps, disableAutomaticModal, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
145
+ declare function ConsentProvider({ initialState, texts: textsProp, theme, PreferencesModalComponent, preferencesModalProps, disableAutomaticModal, hideBranding, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
125
146
 
126
147
  declare function useConsent(): ConsentContextValue;
127
148
  /**
@@ -129,6 +150,11 @@ declare function useConsent(): ConsentContextValue;
129
150
  * Útil para componentes personalizados que precisam dos textos configurados.
130
151
  */
131
152
  declare function useConsentTexts(): ConsentTexts;
153
+ /**
154
+ * Hook para verificar se a hidratação do cookie foi concluída.
155
+ * Útil para evitar flash do banner antes de verificar cookies existentes.
156
+ */
157
+ declare function useConsentHydration(): boolean;
132
158
 
133
159
  declare function ConsentGate(props: Readonly<{
134
160
  category: Category;
@@ -147,4 +173,4 @@ declare function loadScript(id: string, src: string, category?: 'analytics' | 'm
147
173
  */
148
174
  declare const defaultConsentTheme: _mui_material_styles.Theme;
149
175
 
150
- export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, defaultConsentTheme, loadScript, useConsent, useConsentTexts };
176
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, FloatingPreferencesButton, PreferencesModal, defaultConsentTheme, loadScript, useConsent, useConsentHydration, useConsentTexts };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { PaperProps } from '@mui/material/Paper';
3
3
  import { SnackbarProps } from '@mui/material/Snackbar';
4
4
  import { DialogProps } from '@mui/material/Dialog';
5
+ import { FabProps } from '@mui/material/Fab';
5
6
  import * as React$1 from 'react';
6
7
  import * as _mui_material_styles from '@mui/material/styles';
7
8
 
@@ -9,16 +10,34 @@ interface CookieBannerProps {
9
10
  policyLinkUrl?: string;
10
11
  debug?: boolean;
11
12
  blocking?: boolean;
13
+ hideBranding?: boolean;
12
14
  SnackbarProps?: Partial<SnackbarProps>;
13
15
  PaperProps?: Partial<PaperProps>;
14
16
  }
15
17
  declare function CookieBanner({ policyLinkUrl, debug, blocking, // Por padrão, bloqueia até decisão
16
- SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
18
+ hideBranding, SnackbarProps, PaperProps, }: Readonly<CookieBannerProps>): react_jsx_runtime.JSX.Element | null;
17
19
 
18
20
  interface PreferencesModalProps {
19
21
  DialogProps?: Partial<DialogProps>;
22
+ hideBranding?: boolean;
20
23
  }
21
- declare function PreferencesModal({ DialogProps, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
24
+ declare function PreferencesModal({ DialogProps, hideBranding, }: Readonly<PreferencesModalProps>): react_jsx_runtime.JSX.Element;
25
+
26
+ interface FloatingPreferencesButtonProps {
27
+ /** Posição do botão flutuante. Padrão: 'bottom-right' */
28
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
29
+ /** Offset da borda em pixels. Padrão: 24 */
30
+ offset?: number;
31
+ /** Ícone customizado. Padrão: SettingsIcon */
32
+ icon?: React.ReactNode;
33
+ /** Tooltip customizado */
34
+ tooltip?: string;
35
+ /** Props do Fab do MUI */
36
+ FabProps?: Partial<FabProps>;
37
+ /** Se deve esconder quando consentimento já foi dado. Padrão: false */
38
+ hideWhenConsented?: boolean;
39
+ }
40
+ declare function FloatingPreferencesButton({ position, offset, icon, tooltip, FabProps, hideWhenConsented, }: Readonly<FloatingPreferencesButtonProps>): react_jsx_runtime.JSX.Element | null;
22
41
 
23
42
  /**
24
43
  * Categoria de consentimento para cookies.
@@ -86,6 +105,8 @@ interface ConsentProviderProps {
86
105
  preferencesModalProps?: Record<string, any>;
87
106
  /** Desabilita o modal automático (para usar componente totalmente customizado). */
88
107
  disableAutomaticModal?: boolean;
108
+ /** Esconde branding "fornecido por LÉdipO.eti.br". */
109
+ hideBranding?: boolean;
89
110
  /** Callback chamado quando o consentimento é dado. */
90
111
  onConsentGiven?: (state: ConsentState) => void;
91
112
  /** Callback chamado ao salvar preferências. */
@@ -121,7 +142,7 @@ interface ConsentContextValue {
121
142
  resetConsent: () => void;
122
143
  }
123
144
 
124
- declare function ConsentProvider({ initialState, texts: textsProp, theme, PreferencesModalComponent, preferencesModalProps, disableAutomaticModal, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
145
+ declare function ConsentProvider({ initialState, texts: textsProp, theme, PreferencesModalComponent, preferencesModalProps, disableAutomaticModal, hideBranding, onConsentGiven, onPreferencesSaved, cookie: cookieOpts, children, }: Readonly<ConsentProviderProps>): react_jsx_runtime.JSX.Element;
125
146
 
126
147
  declare function useConsent(): ConsentContextValue;
127
148
  /**
@@ -129,6 +150,11 @@ declare function useConsent(): ConsentContextValue;
129
150
  * Útil para componentes personalizados que precisam dos textos configurados.
130
151
  */
131
152
  declare function useConsentTexts(): ConsentTexts;
153
+ /**
154
+ * Hook para verificar se a hidratação do cookie foi concluída.
155
+ * Útil para evitar flash do banner antes de verificar cookies existentes.
156
+ */
157
+ declare function useConsentHydration(): boolean;
132
158
 
133
159
  declare function ConsentGate(props: Readonly<{
134
160
  category: Category;
@@ -147,4 +173,4 @@ declare function loadScript(id: string, src: string, category?: 'analytics' | 'm
147
173
  */
148
174
  declare const defaultConsentTheme: _mui_material_styles.Theme;
149
175
 
150
- export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, PreferencesModal, defaultConsentTheme, loadScript, useConsent, useConsentTexts };
176
+ export { type Category, type ConsentCookieOptions, ConsentGate, type ConsentPreferences, ConsentProvider, type ConsentState, type ConsentTexts, CookieBanner, FloatingPreferencesButton, PreferencesModal, defaultConsentTheme, loadScript, useConsent, useConsentHydration, useConsentTexts };
package/dist/index.js CHANGED
@@ -1,31 +1,35 @@
1
1
  import {
2
+ Branding,
2
3
  ConsentProvider,
3
4
  PreferencesModal,
4
5
  defaultConsentTheme,
5
6
  useConsent,
7
+ useConsentHydration,
6
8
  useConsentTexts
7
- } from "./chunk-Y4XEAQXV.js";
9
+ } from "./chunk-LRMSFSP2.js";
8
10
 
9
11
  // src/components/CookieBanner.tsx
10
12
  import Button from "@mui/material/Button";
11
13
  import Box from "@mui/material/Box";
12
- import Link from "@mui/material/Link";
13
14
  import Paper from "@mui/material/Paper";
14
15
  import Snackbar from "@mui/material/Snackbar";
15
16
  import Stack from "@mui/material/Stack";
16
17
  import Typography from "@mui/material/Typography";
18
+ import Link from "@mui/material/Link";
17
19
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
20
  function CookieBanner({
19
21
  policyLinkUrl,
20
22
  debug,
21
23
  blocking = true,
22
24
  // Por padrão, bloqueia até decisão
25
+ hideBranding = false,
23
26
  SnackbarProps,
24
27
  PaperProps
25
28
  }) {
26
29
  const { consented, acceptAll, rejectAll, openPreferences } = useConsent();
27
30
  const texts = useConsentTexts();
28
- const open = debug ? true : !consented;
31
+ const isHydrated = useConsentHydration();
32
+ const open = debug ? true : isHydrated && !consented;
29
33
  if (!open) return null;
30
34
  const bannerContent = /* @__PURE__ */ jsx(
31
35
  Paper,
@@ -60,7 +64,8 @@ function CookieBanner({
60
64
  /* @__PURE__ */ jsx(Button, { variant: "text", onClick: openPreferences, children: texts.preferences })
61
65
  ]
62
66
  }
63
- )
67
+ ),
68
+ !hideBranding && /* @__PURE__ */ jsx(Branding, { variant: "banner" })
64
69
  ] })
65
70
  }
66
71
  );
@@ -109,12 +114,71 @@ function CookieBanner({
109
114
  );
110
115
  }
111
116
 
117
+ // src/components/FloatingPreferencesButton.tsx
118
+ import Fab from "@mui/material/Fab";
119
+ import Tooltip from "@mui/material/Tooltip";
120
+ import SettingsIcon from "@mui/icons-material/Settings";
121
+ import { useTheme } from "@mui/material/styles";
122
+ import { jsx as jsx2 } from "react/jsx-runtime";
123
+ function FloatingPreferencesButton({
124
+ position = "bottom-right",
125
+ offset = 24,
126
+ icon = /* @__PURE__ */ jsx2(SettingsIcon, {}),
127
+ tooltip,
128
+ FabProps,
129
+ hideWhenConsented = false
130
+ }) {
131
+ const { openPreferences, consented } = useConsent();
132
+ const theme = useTheme();
133
+ if (hideWhenConsented && consented) {
134
+ return null;
135
+ }
136
+ const tooltipText = tooltip ?? "Gerenciar Prefer\xEAncias de Cookies";
137
+ const getPosition = () => {
138
+ const styles = {
139
+ position: "fixed",
140
+ zIndex: 1200
141
+ // Abaixo do modal mas acima do conteúdo
142
+ };
143
+ switch (position) {
144
+ case "bottom-left":
145
+ return { ...styles, bottom: offset, left: offset };
146
+ case "bottom-right":
147
+ return { ...styles, bottom: offset, right: offset };
148
+ case "top-left":
149
+ return { ...styles, top: offset, left: offset };
150
+ case "top-right":
151
+ return { ...styles, top: offset, right: offset };
152
+ default:
153
+ return { ...styles, bottom: offset, right: offset };
154
+ }
155
+ };
156
+ return /* @__PURE__ */ jsx2(Tooltip, { title: tooltipText, placement: "top", children: /* @__PURE__ */ jsx2(
157
+ Fab,
158
+ {
159
+ size: "medium",
160
+ color: "primary",
161
+ onClick: openPreferences,
162
+ sx: {
163
+ ...getPosition(),
164
+ backgroundColor: theme.palette.primary.main,
165
+ "&:hover": {
166
+ backgroundColor: theme.palette.primary.dark
167
+ }
168
+ },
169
+ "aria-label": tooltipText,
170
+ ...FabProps,
171
+ children: icon
172
+ }
173
+ ) });
174
+ }
175
+
112
176
  // src/utils/ConsentGate.tsx
113
- import { Fragment as Fragment2, jsx as jsx2 } from "react/jsx-runtime";
177
+ import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
114
178
  function ConsentGate(props) {
115
179
  const { preferences } = useConsent();
116
180
  if (!preferences[props.category]) return null;
117
- return /* @__PURE__ */ jsx2(Fragment2, { children: props.children });
181
+ return /* @__PURE__ */ jsx3(Fragment2, { children: props.children });
118
182
  }
119
183
 
120
184
  // src/utils/scriptLoader.ts
@@ -157,9 +221,11 @@ export {
157
221
  ConsentGate,
158
222
  ConsentProvider,
159
223
  CookieBanner,
224
+ FloatingPreferencesButton,
160
225
  PreferencesModal,
161
226
  defaultConsentTheme,
162
227
  loadScript,
163
228
  useConsent,
229
+ useConsentHydration,
164
230
  useConsentTexts
165
231
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-lgpd-consent",
3
- "version": "0.1.7",
3
+ "version": "0.1.11",
4
4
  "description": "Biblioteca de consentimento de cookies (LGPD) para React e Next.js, com contexto, banner e modal personalizáveis usando MUI.",
5
5
  "keywords": [
6
6
  "lgpd",