react-cookie-consent-popup 1.0.5 → 1.5.1
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 +81 -12
- package/dist/ConsentContext.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/popup/ConsentPopup.d.ts +1 -1
- package/dist/styles/style.css +1 -1
- package/package.json +9 -12
- package/dist/config.d.ts +0 -4
- package/dist/core/activate-services.d.ts +0 -2
- package/dist/core/cookies/clear-cookies.d.ts +0 -2
- package/dist/core/deactivate-services.d.ts +0 -2
- package/dist/core/local-storage/clear-items.d.ts +0 -1
- package/dist/core/local-storage/persist.d.ts +0 -6
- package/dist/core/local-storage/retrieve.d.ts +0 -6
- package/dist/core/reconcile-services.d.ts +0 -2
- package/dist/core/scripts/add/external-script.d.ts +0 -1
- package/dist/core/scripts/add/inline-script.d.ts +0 -1
- package/dist/core/scripts/load-scripts.d.ts +0 -2
- package/dist/core/scripts/type-guards.d.ts +0 -3
- package/dist/core/scripts/unload-scripts.d.ts +0 -2
- package/dist/core/session-storage/clear-items.d.ts +0 -1
- package/dist/settings/ServiceItem.d.ts +0 -9
- package/dist/settings/useSelectedServices.d.ts +0 -4
- package/dist/useConsentActions.d.ts +0 -5
- package/dist/useConsentState.d.ts +0 -13
package/README.md
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
# react-cookie-consent-popup
|
|
1
|
+
# :cookie: react-cookie-consent-popup
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/react-cookie-consent-popup)
|
|
4
4
|
[](https://www.npmjs.com/package/react-cookie-consent-popup)
|
|
5
5
|
[](https://www.npmjs.com/package/react-cookie-consent-popup)
|
|
6
|
+
[](https://github.com/Mas-HJ/react-cookie-consent-popup/actions/workflows/ci.yml)
|
|
6
7
|
[](https://github.com/Mas-HJ/react-cookie-consent-popup/blob/main/LICENSE)
|
|
7
8
|
|
|
8
|
-
A React cookie consent popup
|
|
9
|
+
A **zero-dependency** React cookie consent popup with GDPR-compliant service management, automatic script loading, storage cleanup, and light/dark themes.
|
|
9
10
|
|
|
10
11
|
---
|
|
11
12
|
|
|
12
|
-
### Live Demo
|
|
13
|
+
### :cookie: Live Demo
|
|
13
14
|
|
|
14
15
|
> **Try it out instantly!**
|
|
15
16
|
>
|
|
@@ -17,6 +18,19 @@ A React cookie consent popup component with a centered modal dialog. Supports se
|
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- :package: **Zero runtime dependencies** — only React as a peer dependency
|
|
24
|
+
- :gear: **Service management** — define scripts, cookies, localStorage and sessionStorage per service
|
|
25
|
+
- :broom: **Automatic cleanup** — scripts, cookies and storage are removed when consent is revoked
|
|
26
|
+
- :hash: **Hash-based invalidation** — popup reappears automatically when your service config changes
|
|
27
|
+
- :wrench: **Settings modal** — per-service toggles with mandatory service support
|
|
28
|
+
- :art: **Light & dark themes** — built-in themes with full CSS custom property support
|
|
29
|
+
- :wheelchair: **Accessible** — focus trapping, keyboard navigation, `Escape` to close, ARIA labels
|
|
30
|
+
- :globe_with_meridians: **SSR compatible** — works with Next.js, Remix, and other server-rendered frameworks
|
|
31
|
+
- :pencil2: **Fully typed** — written in strict TypeScript with exported types
|
|
32
|
+
- :zap: **Tiny footprint** — ~8 KB minified (JS + CSS)
|
|
33
|
+
|
|
20
34
|
## Installation
|
|
21
35
|
|
|
22
36
|
```bash
|
|
@@ -29,7 +43,7 @@ yarn add react-cookie-consent-popup
|
|
|
29
43
|
|
|
30
44
|
```tsx
|
|
31
45
|
import { ConsentPopup, ConsentProvider } from 'react-cookie-consent-popup';
|
|
32
|
-
import 'react-cookie-consent-popup/
|
|
46
|
+
import 'react-cookie-consent-popup/styles';
|
|
33
47
|
|
|
34
48
|
const services = [
|
|
35
49
|
{
|
|
@@ -44,7 +58,13 @@ const services = [
|
|
|
44
58
|
description: 'Helps us understand how visitors use our site.',
|
|
45
59
|
scripts: [
|
|
46
60
|
{ id: 'gtag', src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX' },
|
|
47
|
-
{
|
|
61
|
+
{
|
|
62
|
+
id: 'gtag-init',
|
|
63
|
+
code: `window.dataLayer = window.dataLayer || [];
|
|
64
|
+
function gtag(){dataLayer.push(arguments);}
|
|
65
|
+
gtag('js', new Date());
|
|
66
|
+
gtag('config', 'G-XXXXXXX');`,
|
|
67
|
+
},
|
|
48
68
|
],
|
|
49
69
|
cookies: [{ pattern: /^_ga/ }],
|
|
50
70
|
},
|
|
@@ -63,7 +83,13 @@ const services = [
|
|
|
63
83
|
|
|
64
84
|
function App() {
|
|
65
85
|
return (
|
|
66
|
-
<ConsentProvider
|
|
86
|
+
<ConsentProvider
|
|
87
|
+
options={{
|
|
88
|
+
services,
|
|
89
|
+
theme: 'light',
|
|
90
|
+
onConsentChange: (consent) => console.log('Consent updated:', consent),
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
67
93
|
<main>
|
|
68
94
|
<h1>My Website</h1>
|
|
69
95
|
</main>
|
|
@@ -92,11 +118,12 @@ Wraps your application and provides consent state to all child components.
|
|
|
92
118
|
|
|
93
119
|
#### `ConsentOptions`
|
|
94
120
|
|
|
95
|
-
| Field
|
|
96
|
-
|
|
97
|
-
| `services`
|
|
98
|
-
| `theme`
|
|
99
|
-
| `customHash`
|
|
121
|
+
| Field | Type | Required | Description |
|
|
122
|
+
|--------------------|-------------------------------|----------|-------------------------------------------------------------------------------------------------------------------|
|
|
123
|
+
| `services` | `ConsentService[]` | Yes | Array of services requiring consent |
|
|
124
|
+
| `theme` | `'light' \| 'dark'` | No | Color theme (default: `'light'`) |
|
|
125
|
+
| `customHash` | `string` | No | Override the auto-generated config hash. When this changes, stored consent is invalidated and the popup reappears |
|
|
126
|
+
| `onConsentChange` | `(consent: string[]) => void` | No | Callback fired whenever consent changes. Receives the array of consented service IDs |
|
|
100
127
|
|
|
101
128
|
#### `ConsentService`
|
|
102
129
|
|
|
@@ -180,7 +207,7 @@ function MyComponent() {
|
|
|
180
207
|
|
|
181
208
|
## Hash-Based Invalidation
|
|
182
209
|
|
|
183
|
-
Consent is persisted in `localStorage`. A hash is computed from your service configuration. If you add, remove, or rename services, the hash changes automatically and the popup will reappear, prompting users to re-consent.
|
|
210
|
+
Consent is persisted in `localStorage`. A hash is computed from your service configuration (`id` and `name` fields). If you add, remove, or rename services, the hash changes automatically and the popup will reappear, prompting users to re-consent.
|
|
184
211
|
|
|
185
212
|
You can also force re-consent by providing a `customHash`:
|
|
186
213
|
|
|
@@ -196,6 +223,40 @@ Supports `'light'` and `'dark'` themes out of the box via CSS custom properties.
|
|
|
196
223
|
<ConsentProvider options={{ services, theme: 'dark' }}>
|
|
197
224
|
```
|
|
198
225
|
|
|
226
|
+
### Custom Theme
|
|
227
|
+
|
|
228
|
+
Override any CSS variable to match your brand:
|
|
229
|
+
|
|
230
|
+
```css
|
|
231
|
+
[data-theme='light'] {
|
|
232
|
+
--rcc-text: #1a1a2e;
|
|
233
|
+
--rcc-bg: #ffffff;
|
|
234
|
+
--rcc-backdrop: rgb(0 0 0 / 45%);
|
|
235
|
+
--rcc-border: #e0e0e0;
|
|
236
|
+
--rcc-btn-primary-bg: #2563eb;
|
|
237
|
+
--rcc-btn-primary-text: #ffffff;
|
|
238
|
+
--rcc-btn-secondary-bg: transparent;
|
|
239
|
+
--rcc-btn-secondary-text: #2563eb;
|
|
240
|
+
--rcc-btn-secondary-border: #2563eb;
|
|
241
|
+
--rcc-scrollbar-track: #f0f0f0;
|
|
242
|
+
--rcc-scrollbar-thumb: #c0c0c0;
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Accessibility
|
|
247
|
+
|
|
248
|
+
The popup and settings modal include:
|
|
249
|
+
|
|
250
|
+
- **Focus trapping** — Tab and Shift+Tab cycle within the modal
|
|
251
|
+
- **Escape key** — closes the settings modal
|
|
252
|
+
- **Focus restoration** — returns focus to the previously focused element on close
|
|
253
|
+
- **ARIA attributes** — `role="dialog"`, `aria-modal`, `aria-labelledby` on both modals
|
|
254
|
+
- **Keyboard-friendly toggles** — custom toggle switches with focus-visible outlines
|
|
255
|
+
|
|
256
|
+
## SSR / Next.js
|
|
257
|
+
|
|
258
|
+
All browser API access (`document`, `window`, `localStorage`, `sessionStorage`) is guarded with runtime checks. The library works out of the box with Next.js, Remix, Gatsby, and other server-rendered React frameworks.
|
|
259
|
+
|
|
199
260
|
## Development
|
|
200
261
|
|
|
201
262
|
```bash
|
|
@@ -218,6 +279,14 @@ yarn dev
|
|
|
218
279
|
yarn coverage
|
|
219
280
|
```
|
|
220
281
|
|
|
282
|
+
## Contributing
|
|
283
|
+
|
|
284
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
285
|
+
|
|
286
|
+
## Changelog
|
|
287
|
+
|
|
288
|
+
See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
289
|
+
|
|
221
290
|
## License
|
|
222
291
|
|
|
223
292
|
MIT
|
package/dist/ConsentContext.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var V=Object.defineProperty;var ie=Object.getOwnPropertyDescriptor;var ce=Object.getOwnPropertyNames;var ae=Object.prototype.hasOwnProperty;var pe=(t,e)=>{for(var n in e)V(t,n,{get:e[n],enumerable:!0})},le=(t,e,n,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of ce(e))!ae.call(t,r)&&r!==n&&V(t,r,{get:()=>e[r],enumerable:!(s=ie(e,r))||s.enumerable});return t};var de=t=>le(V({},"__esModule",{value:!0}),t);var Se={};pe(Se,{ConsentContext:()=>v,ConsentPopup:()=>K,ConsentProvider:()=>oe,ConsentSettings:()=>k,useConsent:()=>l});module.exports=de(Se);var F=require("react");var M=require("react");var A=require("react"),me={consent:[],services:[],theme:"light",isPopupVisible:!0,isSettingsVisible:!1,setConsent:()=>{},hasConsent:()=>!1,showPopup:()=>{},hidePopup:()=>{},toggleSettings:()=>{}},v=(0,A.createContext)(me);function l(){return(0,M.useContext)(v)}var E=require("react");function N(){let{services:t,setConsent:e,hidePopup:n}=l(),s=(0,E.useCallback)(()=>{e(t.map(o=>o.id)),n()},[t,e,n]),r=(0,E.useCallback)(o=>{e(o),n()},[e,n]),i=(0,E.useCallback)(()=>{let o=t.filter(c=>c.mandatory).map(c=>c.id);e(o),n()},[t,e,n]);return{approveAll:s,approveSelected:r,declineAll:i}}var x=require("react"),O='a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';function H(t){let e=(0,x.useRef)(null),n=(0,x.useRef)(null);return(0,x.useEffect)(()=>{if(!t||typeof document>"u")return;n.current=document.activeElement;let s=e.current;s&&s.querySelector(O)?.focus();function r(i){if(i.key!=="Tab"||!s)return;let o=s.querySelectorAll(O);if(o.length===0)return;let c=o[0],u=o[o.length-1];i.shiftKey?document.activeElement===c&&(i.preventDefault(),u.focus()):document.activeElement===u&&(i.preventDefault(),c.focus())}return document.addEventListener("keydown",r),()=>{document.removeEventListener("keydown",r),n.current?.focus()}},[t]),e}var P=require("react");function D(){let{consent:t,services:e}=l(),[n,s]=(0,P.useState)(()=>{let i=e.filter(o=>o.mandatory).map(o=>o.id);return[...new Set([...t,...i])]}),r=(0,P.useCallback)((i,o)=>{s(c=>o?[...c,i]:c.filter(u=>u!==i))},[]);return{selectedIds:n,toggleService:r}}var g=require("react/jsx-runtime");function $({serviceId:t,name:e,description:n,mandatory:s,onChange:r}){let{hasConsent:i}=l(),o=c=>{r(t,c.target.checked)};return(0,g.jsxs)("div",{className:"rcc-settings__item",children:[(0,g.jsxs)("div",{className:"rcc-settings__item__info",children:[(0,g.jsx)("label",{htmlFor:`rcc-toggle-${t}`,className:"rcc-settings__item__name",children:e}),n&&(0,g.jsx)("p",{className:"rcc-settings__item__description",children:n})]}),(0,g.jsxs)("label",{className:"rcc-toggle","aria-label":`Toggle ${e}`,children:[(0,g.jsx)("input",{type:"checkbox",id:`rcc-toggle-${t}`,className:"rcc-toggle__input",defaultChecked:s||i(t),disabled:s,onChange:o}),(0,g.jsx)("span",{className:"rcc-toggle__track"})]})]})}var d=require("react/jsx-runtime");function k({onClose:t,modal:e}){let{services:n}=l(),{approveSelected:s,declineAll:r}=N(),{selectedIds:i,toggleService:o}=D(),c=()=>{s(i),t()},u=()=>{r(),t()},b=e?.title??"Cookie Settings",C=e?.approve??"Save Selection",S=e?.decline??"Decline All",h=e?.close??"Close";return(0,d.jsx)("div",{className:"rcc-settings",role:"dialog","aria-modal":"true","aria-labelledby":"rcc-settings-title",children:(0,d.jsxs)("div",{className:"rcc-settings__content",children:[(0,d.jsxs)("header",{className:"rcc-settings__header",children:[(0,d.jsx)("h2",{id:"rcc-settings-title",className:"rcc-settings__title",children:b}),(0,d.jsx)("button",{type:"button",className:"rcc-settings__close",onClick:t,"aria-label":"Close settings",children:h})]}),(0,d.jsx)("main",{className:"rcc-settings__body",children:n.map(f=>(0,d.jsx)($,{serviceId:f.id,name:f.name,description:f.description,mandatory:f.mandatory,onChange:o},f.id))}),(0,d.jsxs)("footer",{className:"rcc-settings__footer",children:[(0,d.jsx)("button",{type:"button",className:"rcc-settings__btn rcc-settings__btn--secondary",onClick:u,children:S}),(0,d.jsx)("button",{type:"button",className:"rcc-settings__btn rcc-settings__btn--primary",onClick:c,children:C})]})]})})}var m=require("react/jsx-runtime");function K({children:t,settings:e,decline:n,approve:s}){let{isPopupVisible:r,isSettingsVisible:i,toggleSettings:o,theme:c}=l(),{approveAll:u,declineAll:b}=N(),C=H(r);if((0,F.useEffect)(()=>{if(!r)return;function _(T){T.key==="Escape"&&i&&o()}return document.addEventListener("keydown",_),()=>document.removeEventListener("keydown",_)},[r,i,o]),!r)return null;if(i)return(0,m.jsx)("div",{className:"rcc-popup","data-theme":c,ref:C,children:(0,m.jsx)(k,{onClose:o,modal:e?.modal})});let S=e?.label??"Settings",h=n?.label??"Decline",f=s?.label??"Accept All";return(0,m.jsx)("div",{className:"rcc-popup","data-theme":c,ref:C,children:(0,m.jsxs)("div",{className:"rcc-popup__card",role:"dialog","aria-modal":"true","aria-labelledby":"rcc-popup-heading",children:[(0,m.jsx)("div",{id:"rcc-popup-heading",className:"rcc-popup__message",children:t??"This website uses cookies to improve your experience."}),(0,m.jsxs)("div",{className:"rcc-popup__actions",children:[!e?.hidden&&(0,m.jsx)("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--secondary",onClick:o,children:S}),!n?.hidden&&(0,m.jsx)("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--secondary",onClick:b,children:h}),(0,m.jsx)("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--primary",onClick:u,children:f})]})]})})}var a=require("react");function ue(t){let e=5381;for(let n=0;n<t.length;n++)e=e*33^t.charCodeAt(n);return(e>>>0).toString(16)}function B(t){let e=JSON.stringify(t.map(n=>({id:n.id,name:n.name})));return ue(e)}var R="rcc-popup",I="rcc-popup-consent";function w(t){if(typeof window>"u")return{consent:[],isValid:!1};let e=localStorage.getItem(I);if(!e)return{consent:[],isValid:!1};try{let n=JSON.parse(e);return n.hash!==t?{consent:[],isValid:!1}:{consent:n.consent,isValid:!0}}catch{return{consent:[],isValid:!1}}}function G(t){return"src"in t}function J(t){return"code"in t}function X(t,e){if(typeof document>"u")return;let n=document.createElement("script");n.id=t,n.src=e,n.async=!0,document.body.appendChild(n)}function Y(t,e){if(typeof document>"u")return;let n=document.createElement("script");n.id=t,n.innerHTML=e,document.body.appendChild(n)}function fe(t,e){return`${R}-${t}-${e}`}function q(t,e){if(!(typeof document>"u"))for(let n of e){let s=fe(t,n.id);document.getElementById(s)||(G(n)?X(s,n.src):J(n)&&Y(s,n.code))}}function L(t){for(let e of t)e.scripts?.length&&q(e.id,e.scripts)}function U(t,e){if(!(typeof document>"u"))for(let n of e){let s=`${R}-${t}-${n.id}`,r=document.getElementById(s);r&&r.remove()}}function ge(){return typeof document>"u"?[]:document.cookie.split(";").map(t=>t.trim().split("=")[0]).filter(Boolean)}function z(t){typeof document>"u"||(document.cookie=`${t}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`)}function Q(t){let e=ge();for(let{pattern:n}of t)if(typeof n=="string")z(n);else for(let s of e)n.test(s)&&z(s)}function W(t){if(!(typeof window>"u"))for(let e of t)localStorage.removeItem(e)}function Z(t){if(!(typeof window>"u"))for(let e of t)sessionStorage.removeItem(e)}function j(t){for(let e of t)e.scripts?.length&&U(e.id,e.scripts),e.cookies?.length&&Q(e.cookies),e.localStorage?.length&&W(e.localStorage),e.sessionStorage?.length&&Z(e.sessionStorage)}function ee(t,e){if(typeof window>"u")return;let n={consent:t,hash:e,timestamp:Date.now()};localStorage.setItem(I,JSON.stringify(n))}function te(t,e,n,s){let r=t.filter(o=>n.includes(o.id)&&!e.includes(o.id)),i=t.filter(o=>!n.includes(o.id)&&e.includes(o.id));j(i),L(r),ee(n,s)}function Ce(t){return t.customHash?t.customHash:B(t.services)}function ne(t){let e=Ce(t),{services:n,onConsentChange:s}=t,[r,i]=(0,a.useState)(()=>{let{consent:p,isValid:y}=w(e);return y?p:[]}),[o,c]=(0,a.useState)(()=>{let{isValid:p}=w(e);return!p}),[u,b]=(0,a.useState)(!1),C=(0,a.useRef)(r),S=(0,a.useRef)(s);S.current=s,(0,a.useEffect)(()=>{let p=n.filter(y=>r.includes(y.id));L(p)},[]);let h=(0,a.useCallback)(p=>{let y=C.current;te(n,y,p,e),i(p),C.current=p,S.current?.(p)},[n,e]),f=(0,a.useCallback)(p=>r.includes(p),[r]),_=(0,a.useCallback)(()=>c(!0),[]),T=(0,a.useCallback)(()=>c(!1),[]),re=(0,a.useCallback)(()=>b(p=>!p),[]);return{consent:r,services:n,theme:t.theme??"light",isPopupVisible:o,isSettingsVisible:u,setConsent:h,hasConsent:f,showPopup:_,hidePopup:T,toggleSettings:re}}var se=require("react/jsx-runtime");function oe({options:t,children:e}){let n=ne(t);return(0,se.jsx)(v.Provider,{value:n,children:e})}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{useContext as
|
|
1
|
+
import{useEffect as pe}from"react";import{useContext as re}from"react";import{createContext as oe}from"react";var se={consent:[],services:[],theme:"light",isPopupVisible:!0,isSettingsVisible:!1,setConsent:()=>{},hasConsent:()=>!1,showPopup:()=>{},hidePopup:()=>{},toggleSettings:()=>{}},b=oe(se);function p(){return re(b)}import{useCallback as R}from"react";function x(){let{services:t,setConsent:e,hidePopup:n}=p(),s=R(()=>{e(t.map(o=>o.id)),n()},[t,e,n]),r=R(o=>{e(o),n()},[e,n]),i=R(()=>{let o=t.filter(c=>c.mandatory).map(c=>c.id);e(o),n()},[t,e,n]);return{approveAll:s,approveSelected:r,declineAll:i}}import{useEffect as ie,useRef as A}from"react";var M='a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';function O(t){let e=A(null),n=A(null);return ie(()=>{if(!t||typeof document>"u")return;n.current=document.activeElement;let s=e.current;s&&s.querySelector(M)?.focus();function r(i){if(i.key!=="Tab"||!s)return;let o=s.querySelectorAll(M);if(o.length===0)return;let c=o[0],l=o[o.length-1];i.shiftKey?document.activeElement===c&&(i.preventDefault(),l.focus()):document.activeElement===l&&(i.preventDefault(),c.focus())}return document.addEventListener("keydown",r),()=>{document.removeEventListener("keydown",r),n.current?.focus()}},[t]),e}import{useState as ce,useCallback as ae}from"react";function H(){let{consent:t,services:e}=p(),[n,s]=ce(()=>{let i=e.filter(o=>o.mandatory).map(o=>o.id);return[...new Set([...t,...i])]}),r=ae((i,o)=>{s(c=>o?[...c,i]:c.filter(l=>l!==i))},[]);return{selectedIds:n,toggleService:r}}import{jsx as _,jsxs as I}from"react/jsx-runtime";function D({serviceId:t,name:e,description:n,mandatory:s,onChange:r}){let{hasConsent:i}=p(),o=c=>{r(t,c.target.checked)};return I("div",{className:"rcc-settings__item",children:[I("div",{className:"rcc-settings__item__info",children:[_("label",{htmlFor:`rcc-toggle-${t}`,className:"rcc-settings__item__name",children:e}),n&&_("p",{className:"rcc-settings__item__description",children:n})]}),I("label",{className:"rcc-toggle","aria-label":`Toggle ${e}`,children:[_("input",{type:"checkbox",id:`rcc-toggle-${t}`,className:"rcc-toggle__input",defaultChecked:s||i(t),disabled:s,onChange:o}),_("span",{className:"rcc-toggle__track"})]})]})}import{jsx as u,jsxs as L}from"react/jsx-runtime";function T({onClose:t,modal:e}){let{services:n}=p(),{approveSelected:s,declineAll:r}=x(),{selectedIds:i,toggleService:o}=H(),c=()=>{s(i),t()},l=()=>{r(),t()},C=e?.title??"Cookie Settings",m=e?.approve??"Save Selection",g=e?.decline??"Decline All",S=e?.close??"Close";return u("div",{className:"rcc-settings",role:"dialog","aria-modal":"true","aria-labelledby":"rcc-settings-title",children:L("div",{className:"rcc-settings__content",children:[L("header",{className:"rcc-settings__header",children:[u("h2",{id:"rcc-settings-title",className:"rcc-settings__title",children:C}),u("button",{type:"button",className:"rcc-settings__close",onClick:t,"aria-label":"Close settings",children:S})]}),u("main",{className:"rcc-settings__body",children:n.map(d=>u(D,{serviceId:d.id,name:d.name,description:d.description,mandatory:d.mandatory,onChange:o},d.id))}),L("footer",{className:"rcc-settings__footer",children:[u("button",{type:"button",className:"rcc-settings__btn rcc-settings__btn--secondary",onClick:l,children:g}),u("button",{type:"button",className:"rcc-settings__btn rcc-settings__btn--primary",onClick:c,children:m})]})]})})}import{jsx as f,jsxs as $}from"react/jsx-runtime";function le({children:t,settings:e,decline:n,approve:s}){let{isPopupVisible:r,isSettingsVisible:i,toggleSettings:o,theme:c}=p(),{approveAll:l,declineAll:C}=x(),m=O(r);if(pe(()=>{if(!r)return;function y(k){k.key==="Escape"&&i&&o()}return document.addEventListener("keydown",y),()=>document.removeEventListener("keydown",y)},[r,i,o]),!r)return null;if(i)return f("div",{className:"rcc-popup","data-theme":c,ref:m,children:f(T,{onClose:o,modal:e?.modal})});let g=e?.label??"Settings",S=n?.label??"Decline",d=s?.label??"Accept All";return f("div",{className:"rcc-popup","data-theme":c,ref:m,children:$("div",{className:"rcc-popup__card",role:"dialog","aria-modal":"true","aria-labelledby":"rcc-popup-heading",children:[f("div",{id:"rcc-popup-heading",className:"rcc-popup__message",children:t??"This website uses cookies to improve your experience."}),$("div",{className:"rcc-popup__actions",children:[!e?.hidden&&f("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--secondary",onClick:o,children:g}),!n?.hidden&&f("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--secondary",onClick:C,children:S}),f("button",{type:"button",className:"rcc-popup__btn rcc-popup__btn--primary",onClick:l,children:d})]})]})})}import{useState as w,useCallback as h,useEffect as fe,useRef as ee}from"react";function de(t){let e=5381;for(let n=0;n<t.length;n++)e=e*33^t.charCodeAt(n);return(e>>>0).toString(16)}function F(t){let e=JSON.stringify(t.map(n=>({id:n.id,name:n.name})));return de(e)}var E="rcc-popup",N="rcc-popup-consent";function V(t){if(typeof window>"u")return{consent:[],isValid:!1};let e=localStorage.getItem(N);if(!e)return{consent:[],isValid:!1};try{let n=JSON.parse(e);return n.hash!==t?{consent:[],isValid:!1}:{consent:n.consent,isValid:!0}}catch{return{consent:[],isValid:!1}}}function K(t){return"src"in t}function B(t){return"code"in t}function G(t,e){if(typeof document>"u")return;let n=document.createElement("script");n.id=t,n.src=e,n.async=!0,document.body.appendChild(n)}function J(t,e){if(typeof document>"u")return;let n=document.createElement("script");n.id=t,n.innerHTML=e,document.body.appendChild(n)}function me(t,e){return`${E}-${t}-${e}`}function X(t,e){if(!(typeof document>"u"))for(let n of e){let s=me(t,n.id);document.getElementById(s)||(K(n)?G(s,n.src):B(n)&&J(s,n.code))}}function P(t){for(let e of t)e.scripts?.length&&X(e.id,e.scripts)}function Y(t,e){if(!(typeof document>"u"))for(let n of e){let s=`${E}-${t}-${n.id}`,r=document.getElementById(s);r&&r.remove()}}function ue(){return typeof document>"u"?[]:document.cookie.split(";").map(t=>t.trim().split("=")[0]).filter(Boolean)}function q(t){typeof document>"u"||(document.cookie=`${t}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`)}function U(t){let e=ue();for(let{pattern:n}of t)if(typeof n=="string")q(n);else for(let s of e)n.test(s)&&q(s)}function z(t){if(!(typeof window>"u"))for(let e of t)localStorage.removeItem(e)}function Q(t){if(!(typeof window>"u"))for(let e of t)sessionStorage.removeItem(e)}function W(t){for(let e of t)e.scripts?.length&&Y(e.id,e.scripts),e.cookies?.length&&U(e.cookies),e.localStorage?.length&&z(e.localStorage),e.sessionStorage?.length&&Q(e.sessionStorage)}function Z(t,e){if(typeof window>"u")return;let n={consent:t,hash:e,timestamp:Date.now()};localStorage.setItem(N,JSON.stringify(n))}function j(t,e,n,s){let r=t.filter(o=>n.includes(o.id)&&!e.includes(o.id)),i=t.filter(o=>!n.includes(o.id)&&e.includes(o.id));W(i),P(r),Z(n,s)}function ge(t){return t.customHash?t.customHash:F(t.services)}function te(t){let e=ge(t),{services:n,onConsentChange:s}=t,[r,i]=w(()=>{let{consent:a,isValid:v}=V(e);return v?a:[]}),[o,c]=w(()=>{let{isValid:a}=V(e);return!a}),[l,C]=w(!1),m=ee(r),g=ee(s);g.current=s,fe(()=>{let a=n.filter(v=>r.includes(v.id));P(a)},[]);let S=h(a=>{let v=m.current;j(n,v,a,e),i(a),m.current=a,g.current?.(a)},[n,e]),d=h(a=>r.includes(a),[r]),y=h(()=>c(!0),[]),k=h(()=>c(!1),[]),ne=h(()=>C(a=>!a),[]);return{consent:r,services:n,theme:t.theme??"light",isPopupVisible:o,isSettingsVisible:l,setConsent:S,hasConsent:d,showPopup:y,hidePopup:k,toggleSettings:ne}}import{jsx as Se}from"react/jsx-runtime";function Ce({options:t,children:e}){let n=te(t);return Se(b.Provider,{value:n,children:e})}export{b as ConsentContext,le as ConsentPopup,Ce as ConsentProvider,T as ConsentSettings,p as useConsent};
|
package/dist/styles/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
[data-theme=light]{--rcc-text: #1a1a2e;--rcc-bg: #ffffff;--rcc-backdrop: rgb(0 0 0 / 45%);--rcc-border: #e0e0e0;--rcc-btn-primary-bg: #2563eb;--rcc-btn-primary-text: #ffffff;--rcc-btn-secondary-bg: transparent;--rcc-btn-secondary-text: #2563eb;--rcc-btn-secondary-border: #2563eb;--rcc-scrollbar-track: #f0f0f0;--rcc-scrollbar-thumb: #c0c0c0}[data-theme=dark]{--rcc-text: #e8e8e8;--rcc-bg: #1e1e2e;--rcc-backdrop: rgb(0 0 0 / 60%);--rcc-border: #3a3a4a;--rcc-btn-primary-bg: #3b82f6;--rcc-btn-primary-text: #ffffff;--rcc-btn-secondary-bg: transparent;--rcc-btn-secondary-text: #93b4f6;--rcc-btn-secondary-border: #93b4f6;--rcc-scrollbar-track: #2a2a3a;--rcc-scrollbar-thumb: #555570}.rcc-settings__close,.rcc-settings__btn--secondary,.rcc-popup__btn--secondary,.rcc-settings__btn--primary,.rcc-popup__btn--primary{display:inline-flex;align-items:center;justify-content:center;padding:.625rem 1.25rem;border-radius:.375rem;font-family:inherit;font-size:.875rem;font-weight:600;line-height:1.4;cursor:pointer;transition:background-color .2s ease,border-color .2s ease,color .2s ease}.rcc-settings__btn--primary,.rcc-popup__btn--primary{border:2px solid var(--rcc-btn-primary-bg);background-color:var(--rcc-btn-primary-bg);color:var(--rcc-btn-primary-text)}.rcc-settings__btn--primary:hover,.rcc-popup__btn--primary:hover{filter:brightness(1.1)}.rcc-settings__btn--secondary,.rcc-popup__btn--secondary{border:2px solid var(--rcc-btn-secondary-border);background-color:var(--rcc-btn-secondary-bg);color:var(--rcc-btn-secondary-text)}.rcc-settings__btn--secondary:hover,.rcc-popup__btn--secondary:hover{background-color:var(--rcc-btn-secondary-border);color:var(--rcc-btn-primary-text)}.rcc-toggle{position:relative;display:inline-flex;flex-shrink:0;cursor:pointer}.rcc-toggle__input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;border:0;clip:rect(0 0 0 0);white-space:nowrap}.rcc-toggle__track{display:inline-block;width:2.875rem;height:1.5rem;border-radius:.75rem;background-color:var(--rcc-border);transition:background-color .2s ease}.rcc-toggle__track:after{content:"";display:block;width:1.125rem;height:1.125rem;margin:.1875rem;border-radius:50%;background-color:#fff;box-shadow:0 1px 3px #0003;transition:transform .2s ease}.rcc-toggle__input:checked+.rcc-toggle__track{background-color:var(--rcc-btn-primary-bg)}.rcc-toggle__input:checked+.rcc-toggle__track:after{transform:translate(1.375rem)}.rcc-toggle__input:disabled+.rcc-toggle__track{opacity:.5;cursor:not-allowed}.rcc-toggle__input:focus-visible+.rcc-toggle__track{outline:2px solid var(--rcc-btn-primary-bg);outline-offset:2px}.rcc-popup{display:flex;position:fixed;z-index:99999;inset:0;align-items:center;justify-content:center;padding:1rem;background-color:var(--rcc-backdrop);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,sans-serif;color:var(--rcc-text)}.rcc-popup__card{width:100%;max-width:33.75rem;border-radius:.75rem;background-color:var(--rcc-bg);box-shadow:0 1.25rem 3.75rem #0003,0 .25rem .75rem #0000001a;padding:1.75rem 1.5rem}@media(min-width:600px){.rcc-popup__card{padding:2rem}}.rcc-popup__message{margin-bottom:1.5rem;font-size:.9375rem;line-height:1.6}.rcc-popup__actions{display:grid;gap:.5rem;grid-template-columns:1fr}@media(min-width:600px){.rcc-popup__actions{grid-template-columns:repeat(auto-fit,minmax(7.5rem,1fr))}}.rcc-settings{display:grid;width:100%;max-width:35rem;max-height:85vh;border-radius:.75rem;background-color:var(--rcc-bg);box-shadow:0 1.25rem 3.75rem #0003,0 .25rem .75rem #0000001a;grid-template-rows:auto 1fr auto}.rcc-settings__content{display:contents}.rcc-settings__header{display:flex;align-items:center;justify-content:space-between;padding:1.25rem 1.5rem;border-bottom:1px solid var(--rcc-border)}.rcc-settings__title{margin:0;font-size:1.125rem;font-weight:700}.rcc-settings__close{padding:.375rem .75rem;border:1px solid var(--rcc-border);background:transparent;font-size:.8125rem}.rcc-settings__body{overflow-y:auto;padding:1rem 1.5rem;scrollbar-width:thin;scrollbar-color:var(--rcc-scrollbar-thumb) var(--rcc-scrollbar-track)}.rcc-settings__body::-webkit-scrollbar{width:.375rem}.rcc-settings__body::-webkit-scrollbar-track{background:var(--rcc-scrollbar-track)}.rcc-settings__body::-webkit-scrollbar-thumb{border-radius:.1875rem;background:var(--rcc-scrollbar-thumb)}.rcc-settings__item{display:flex;align-items:center;justify-content:space-between;padding:.875rem 0;border-bottom:1px solid var(--rcc-border);gap:1rem}.rcc-settings__item:last-child{border-bottom:none}.rcc-settings__item__info{flex:1}.rcc-settings__item__name{display:block;font-size:.9375rem;font-weight:600;cursor:pointer}.rcc-settings__item__description{margin:.25rem 0 0;font-size:.8125rem;line-height:1.5;opacity:.75}.rcc-settings__footer{display:flex;justify-content:flex-end;padding:1rem 1.5rem;border-top:1px solid var(--rcc-border);gap:.5rem}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-cookie-consent-popup",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A React cookie consent popup component with centered modal dialog, service management, script loading, and light/dark theme support",
|
|
3
|
+
"version": "1.5.1",
|
|
4
|
+
"description": "A zero-dependency React cookie consent popup component with centered modal dialog, service management, script loading, and light/dark theme support",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/Mas-HJ/react-cookie-consent-popup.git"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"import": "./dist/index.mjs",
|
|
20
20
|
"require": "./dist/index.js"
|
|
21
21
|
},
|
|
22
|
-
"./
|
|
22
|
+
"./styles": "./dist/styles/style.css"
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"files": [
|
|
@@ -28,11 +28,13 @@
|
|
|
28
28
|
],
|
|
29
29
|
"scripts": {
|
|
30
30
|
"build": "tsx esbuild.config.ts",
|
|
31
|
-
"postbuild": "tsc --emitDeclarationOnly",
|
|
31
|
+
"postbuild": "tsc --emitDeclarationOnly && node -e \"const fs=require('fs');const rm=p=>{try{fs.rmSync(p,{recursive:true})}catch{}};rm('dist/core');rm('dist/config.d.ts');rm('dist/useConsentState.d.ts');rm('dist/useConsentActions.d.ts');rm('dist/useFocusTrap.d.ts');rm('dist/settings/ServiceItem.d.ts');rm('dist/settings/useSelectedServices.d.ts')\"",
|
|
32
32
|
"dev": "tsx esbuild.config.ts --watch",
|
|
33
33
|
"test": "jest",
|
|
34
34
|
"test:dev": "jest --watch",
|
|
35
|
-
"coverage": "jest --coverage"
|
|
35
|
+
"coverage": "jest --coverage",
|
|
36
|
+
"preversion": "yarn test",
|
|
37
|
+
"prepublishOnly": "yarn test && yarn build"
|
|
36
38
|
},
|
|
37
39
|
"keywords": [
|
|
38
40
|
"react",
|
|
@@ -44,17 +46,14 @@
|
|
|
44
46
|
"privacy",
|
|
45
47
|
"cookies",
|
|
46
48
|
"cookie-consent",
|
|
47
|
-
"cookie-banner"
|
|
49
|
+
"cookie-banner",
|
|
50
|
+
"zero-dependency"
|
|
48
51
|
],
|
|
49
52
|
"license": "MIT",
|
|
50
53
|
"peerDependencies": {
|
|
51
54
|
"react": ">=16.8.0",
|
|
52
55
|
"react-dom": ">=16.8.0"
|
|
53
56
|
},
|
|
54
|
-
"dependencies": {
|
|
55
|
-
"object-hash": "^3.0.0",
|
|
56
|
-
"react-toggle": "^4.1.3"
|
|
57
|
-
},
|
|
58
57
|
"devDependencies": {
|
|
59
58
|
"@babel/preset-env": "^7.23.9",
|
|
60
59
|
"@babel/preset-react": "^7.23.3",
|
|
@@ -62,10 +61,8 @@
|
|
|
62
61
|
"@testing-library/jest-dom": "^6.4.2",
|
|
63
62
|
"@testing-library/react": "^14.2.1",
|
|
64
63
|
"@types/jest": "^29.5.12",
|
|
65
|
-
"@types/object-hash": "^3.0.6",
|
|
66
64
|
"@types/react": "^18.2.55",
|
|
67
65
|
"@types/react-dom": "^18.2.19",
|
|
68
|
-
"@types/react-toggle": "^4.0.5",
|
|
69
66
|
"@typescript-eslint/eslint-plugin": "^7.0.1",
|
|
70
67
|
"@typescript-eslint/parser": "^7.0.1",
|
|
71
68
|
"esbuild": "^0.25.0",
|
package/dist/config.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function clearLocalStorageItems(keys: string[]): void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function loadExternalScript(elementId: string, src: string): void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function loadInlineScript(elementId: string, code: string): void;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function clearSessionStorageItems(keys: string[]): void;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
interface ServiceItemProps {
|
|
2
|
-
serviceId: string;
|
|
3
|
-
name: string;
|
|
4
|
-
description?: string;
|
|
5
|
-
mandatory?: boolean;
|
|
6
|
-
onChange: (serviceId: string, enabled: boolean) => void;
|
|
7
|
-
}
|
|
8
|
-
export declare function ServiceItem({ serviceId, name, description, mandatory, onChange }: ServiceItemProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
-
export {};
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { ConsentOptions } from './ConsentContext';
|
|
2
|
-
export declare function useConsentState(options: ConsentOptions): {
|
|
3
|
-
consent: string[];
|
|
4
|
-
services: import("./ConsentContext").ConsentService[];
|
|
5
|
-
theme: import("./ConsentContext").Theme;
|
|
6
|
-
isPopupVisible: boolean;
|
|
7
|
-
isSettingsVisible: boolean;
|
|
8
|
-
setConsent: (nextConsent: string[]) => void;
|
|
9
|
-
hasConsent: (serviceId: string) => boolean;
|
|
10
|
-
showPopup: () => void;
|
|
11
|
-
hidePopup: () => void;
|
|
12
|
-
toggleSettings: () => void;
|
|
13
|
-
};
|