nextjs-cookie-consent 1.0.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.
@@ -0,0 +1,4 @@
1
+ export { default as CookieBanner } from './src/CookieBanner';
2
+ export * from './src/types';
3
+ export * from './src/useConsent';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC7D,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as CookieBanner } from './src/CookieBanner';
2
+ export * from './src/types';
3
+ export * from './src/useConsent';
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import { CookieBannerProps } from './types';
3
+ declare const CookieBanner: React.FC<CookieBannerProps>;
4
+ export default CookieBanner;
5
+ //# sourceMappingURL=CookieBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CookieBanner.d.ts","sourceRoot":"","sources":["../../src/CookieBanner.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAmB,MAAM,OAAO,CAAC;AACxC,OAAO,EAAgC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAuD1E,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAsH7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from 'react';
4
+ import { useConsent } from './useConsent';
5
+ const styles = {
6
+ cookieBanner: {
7
+ position: 'fixed',
8
+ bottom: 0,
9
+ left: 0,
10
+ right: 0,
11
+ background: 'white',
12
+ padding: '1rem',
13
+ boxShadow: '0 -2px 8px rgba(0, 0, 0, 0.1)',
14
+ zIndex: 1000,
15
+ fontFamily: 'sans-serif'
16
+ },
17
+ cookieBannerBasic: {
18
+ display: 'flex',
19
+ flexDirection: 'column',
20
+ gap: '0.75rem'
21
+ },
22
+ cookieBannerSettings: {
23
+ display: 'flex',
24
+ flexDirection: 'column',
25
+ gap: '0.75rem'
26
+ },
27
+ cookieBannerSettingsLabel: {
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ gap: '0.5rem'
31
+ },
32
+ cookieBannerButtons: {
33
+ display: 'flex',
34
+ gap: '1rem',
35
+ flexWrap: 'wrap'
36
+ },
37
+ cookieBannerButton: {
38
+ backgroundColor: '#111',
39
+ color: 'white',
40
+ border: 'none',
41
+ padding: '0.5rem 1rem',
42
+ cursor: 'pointer',
43
+ fontSize: '0.9rem',
44
+ borderRadius: '4px'
45
+ },
46
+ cookieBannerText: {
47
+ fontSize: '0.95rem',
48
+ lineHeight: 1.5,
49
+ marginBottom: '0.5rem'
50
+ },
51
+ cookieBannerTextLink: {
52
+ color: '#0070f3',
53
+ textDecoration: 'underline'
54
+ }
55
+ };
56
+ const CookieBanner = ({ categories, content, storageStrategy }) => {
57
+ const categoryKeys = categories.map((c) => c.key);
58
+ const { consent, isSet, saveConsent } = useConsent(categoryKeys, storageStrategy);
59
+ const [showSettings, setShowSettings] = useState(false);
60
+ const [tempConsent, setTempConsent] = useState(consent);
61
+ if (isSet)
62
+ return null;
63
+ const updateTemp = (key, value) => {
64
+ setTempConsent((prev) => (Object.assign(Object.assign({}, prev), { [key]: value })));
65
+ };
66
+ const acceptAll = () => {
67
+ const allTrue = {};
68
+ categoryKeys.forEach((key) => {
69
+ allTrue[key] = true;
70
+ });
71
+ saveConsent(Object.assign({ necessary: true }, allTrue));
72
+ };
73
+ const saveSelected = () => {
74
+ saveConsent(Object.assign({ necessary: true }, tempConsent));
75
+ };
76
+ const handleButtonHover = (e) => {
77
+ e.currentTarget.style.backgroundColor = '#333';
78
+ };
79
+ const handleButtonLeave = (e) => {
80
+ e.currentTarget.style.backgroundColor = '#111';
81
+ };
82
+ return (_jsx("div", { style: styles.cookieBanner, className: "cookie-banner-container", children: !showSettings ? (_jsxs("div", { style: styles.cookieBannerBasic, className: "cookie-banner-default", children: [_jsx("div", { style: styles.cookieBannerText, children: content || (_jsxs("p", { children: ["We use cookies to improve your experience.", ' ', _jsx("a", { href: "/privacy-policy", target: "_blank", rel: "noopener noreferrer", style: styles.cookieBannerTextLink, children: "Learn more" }), "."] })) }), _jsxs("div", { style: styles.cookieBannerButtons, className: "cookie-banner-buttons", children: [_jsx("button", { className: "cookie-banner-button-necessary", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => saveConsent({ necessary: true }), children: "Only necessary" }), _jsx("button", { className: "cookie-banner-button-settings", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(true), children: "Settings" }), _jsx("button", { className: "cookie-banner-button-all", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: acceptAll, children: "Accept all" })] })] })) : (_jsxs("div", { style: styles.cookieBannerSettings, className: "cookie-banner-settings", children: [_jsx("h4", { className: "cookie-banner-settings-heading", children: "Cookie Settings" }), _jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: true, disabled: true }), "Necessary (always active)"] }), categories.map((cat) => (_jsxs("label", { style: styles.cookieBannerSettingsLabel, className: "cookie-banner-category", children: [_jsx("input", { type: "checkbox", checked: tempConsent[cat.key] || false, onChange: (e) => updateTemp(cat.key, e.target.checked) }), cat.label] }, cat.key))), _jsxs("div", { style: styles.cookieBannerButtons, className: "cookie-banner-buttons", children: [_jsx("button", { className: "cookie-banner-button-save", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: saveSelected, children: "Save" }), _jsx("button", { className: "cookie-banner-button-back", style: styles.cookieBannerButton, onMouseEnter: handleButtonHover, onMouseLeave: handleButtonLeave, onClick: () => setShowSettings(false), children: "Back" })] })] })) }));
83
+ };
84
+ export default CookieBanner;
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ export interface CookieCategory {
3
+ key: string;
4
+ label: string;
5
+ }
6
+ export type ConsentState = Record<string, boolean>;
7
+ export type StorageStrategy = 'localStorage' | 'cookie';
8
+ export interface CookieBannerProps {
9
+ categories: CookieCategory[];
10
+ content?: ReactNode;
11
+ storageStrategy?: StorageStrategy;
12
+ }
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnD,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,QAAQ,CAAC;AAExD,MAAM,WAAW,iBAAiB;IAC9B,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,eAAe,CAAC,EAAE,eAAe,CAAC;CACrC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { ConsentState, StorageStrategy } from './types';
2
+ export declare function useConsent(categoryKeys: string[], storageStrategy?: StorageStrategy): {
3
+ consent: ConsentState;
4
+ isSet: boolean;
5
+ saveConsent: (newConsent: ConsentState) => void;
6
+ };
7
+ //# sourceMappingURL=useConsent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConsent.d.ts","sourceRoot":"","sources":["../../src/useConsent.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAExD,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,eAAe,GAAE,eAAgC;;;8BA6C/D,YAAY;EAOhD"}
@@ -0,0 +1,47 @@
1
+ import { useState, useEffect } from 'react';
2
+ export function useConsent(categoryKeys, storageStrategy = 'localStorage') {
3
+ const storageKey = 'cookie-consent';
4
+ const defaultConsent = Object.assign({ necessary: true }, categoryKeys.reduce((acc, key) => {
5
+ acc[key] = false;
6
+ return acc;
7
+ }, {}));
8
+ const getStoredConsent = () => {
9
+ try {
10
+ if (storageStrategy === 'localStorage') {
11
+ const stored = localStorage.getItem(storageKey);
12
+ return stored ? JSON.parse(stored) : null;
13
+ }
14
+ else {
15
+ const match = document.cookie.match(new RegExp('(^| )' + storageKey + '=([^;]+)'));
16
+ return match ? JSON.parse(decodeURIComponent(match[2])) : null;
17
+ }
18
+ }
19
+ catch (_a) {
20
+ return null;
21
+ }
22
+ };
23
+ const storeConsent = (consent) => {
24
+ const encoded = JSON.stringify(consent);
25
+ if (storageStrategy === 'localStorage') {
26
+ localStorage.setItem(storageKey, encoded);
27
+ }
28
+ else {
29
+ document.cookie = `${storageKey}=${encodeURIComponent(encoded)}; path=/; SameSite=Lax`;
30
+ }
31
+ };
32
+ const [consent, setConsent] = useState(defaultConsent);
33
+ const [isSet, setIsSet] = useState(false);
34
+ useEffect(() => {
35
+ const stored = getStoredConsent();
36
+ if (stored) {
37
+ setConsent(stored);
38
+ setIsSet(true);
39
+ }
40
+ }, []);
41
+ const saveConsent = (newConsent) => {
42
+ storeConsent(newConsent);
43
+ setConsent(newConsent);
44
+ setIsSet(true);
45
+ };
46
+ return { consent, isSet, saveConsent };
47
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "nextjs-cookie-consent",
3
+ "version": "1.0.0",
4
+ "description": "GDPR-compliant cookie consent banner with fully customizable categories for Next.js",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsc"
19
+ },
20
+ "keywords": [
21
+ "cookie",
22
+ "banner",
23
+ "nextjs",
24
+ "consent",
25
+ "gdpr",
26
+ "cookie-consent"
27
+ ],
28
+ "author": "Lukas Schweiger",
29
+ "license": "MIT",
30
+ "peerDependencies": {
31
+ "next": "^15.0.0",
32
+ "react": "^18.2.0 || ^19.0.0",
33
+ "react-dom": "^18.2.0 || ^19.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^18.2.0",
37
+ "@types/react-dom": "^18.2.0",
38
+ "react": "^18.2.0",
39
+ "react-dom": "^18.2.0",
40
+ "typescript": "^5.4.0"
41
+ }
42
+ }
package/readme.md ADDED
@@ -0,0 +1,211 @@
1
+ # ๐Ÿช nextjs-cookie-consent
2
+
3
+ A ๐Ÿ›ก๏ธ **GDPR / DSGVO-compliant cookie consent banner for Next.js** with fully customizable categories like `analytics`, `marketing`, or `preferences`. Built for modern apps with easy styling, flexible API, and localStorage- or cookie-based consent handling.
4
+
5
+ ---
6
+
7
+ ## โœจ Features
8
+
9
+ - โœ… GDPR / DSGVO-compliant: Only essential cookies are enabled by default
10
+ - ๐Ÿง  Fully configurable categories (e.g. Necessary, Analytics, Marketing, ...)
11
+ - ๐Ÿ’ฌ Custom text with links to your privacy policy
12
+ - ๐Ÿ’พ Consent stored in `localStorage` *(or cookies โ€“ configurable!)*
13
+ - ๐Ÿ”„ Dynamic access to user consent via `useConsent()` hook
14
+ - ๐Ÿงฑ Compatible with both **App Router** and **Pages Router**
15
+ - ๐ŸŽจ Minimal styling (fully customizable)
16
+
17
+ ---
18
+
19
+ ## ๐Ÿš€ Installation
20
+
21
+ ```bash
22
+ npm install nextjs-cookie-consent
23
+ ```
24
+
25
+ or with Yarn:
26
+
27
+ ```bash
28
+ yarn add nextjs-cookie-consent
29
+ ```
30
+
31
+ ---
32
+
33
+ ## โš™๏ธ Usage
34
+
35
+ ### โž• Import the component
36
+
37
+ Import the component and CSS in your root layout or `_app.tsx`.
38
+
39
+ #### App Router (`app/layout.tsx`):
40
+
41
+ ```tsx
42
+ import { CookieBanner } from 'nextjs-cookie-consent';
43
+
44
+ export default function RootLayout({ children }) {
45
+ return (
46
+ <html lang="en">
47
+ <body>
48
+ {children}
49
+ <CookieBanner
50
+ categories={[
51
+ { key: 'analytics', label: 'Analytics' },
52
+ { key: 'marketing', label: 'Marketing' },
53
+ ]}
54
+ storageStrategy="cookie" // โ† optional: "cookie" or "localStorage" (default)
55
+ content={
56
+ <p>
57
+ We use cookies to improve your experience.{' '}
58
+ <a href="/privacy-policy" target="_blank" rel="noopener noreferrer">
59
+ Learn more
60
+ </a>
61
+ .
62
+ </p>
63
+ }
64
+ />
65
+ </body>
66
+ </html>
67
+ );
68
+ }
69
+ ```
70
+
71
+ ---
72
+
73
+ ### ๐Ÿ”„ Switch storage to cookies
74
+
75
+ By default, consent is saved to `localStorage`.
76
+ You can enable cookie-based storage like this:
77
+
78
+ ```tsx
79
+ <CookieBanner
80
+ categories={[
81
+ { key: 'analytics', label: 'Analytics' },
82
+ { key: 'marketing', label: 'Marketing' },
83
+ ]}
84
+ storageStrategy="cookie"
85
+ />
86
+ ```
87
+
88
+ Cookies are set with `SameSite=Lax` and `path=/`.
89
+
90
+ ---
91
+
92
+ ## ๐Ÿ“Š Accessing Consent State
93
+
94
+ Use the `useConsent()` hook anywhere in your app to check if a user has accepted a specific category:
95
+
96
+ ```tsx
97
+ 'use client';
98
+
99
+ import { useConsent } from 'nextjs-cookie-consent';
100
+
101
+ export default function AnalyticsLoader() {
102
+ const { consent } = useConsent(['analytics', 'marketing']);
103
+
104
+ return (
105
+ <>
106
+ {consent.analytics && <p>๐Ÿ“ˆ Analytics enabled</p>}
107
+ {!consent.analytics && <p>โŒ Analytics disabled</p>}
108
+ </>
109
+ );
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## ๐Ÿงช Example: Load Google Analytics only after consent
116
+
117
+ ```tsx
118
+ 'use client';
119
+
120
+ import Script from 'next/script';
121
+ import { useConsent } from 'nextjs-cookie-consent';
122
+
123
+ export default function GoogleAnalytics() {
124
+ const { consent } = useConsent(['analytics']);
125
+
126
+ if (!consent.analytics) return null;
127
+
128
+ return (
129
+ <>
130
+ <Script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX" strategy="afterInteractive" />
131
+ <Script id="ga-init" strategy="afterInteractive">
132
+ {`window.dataLayer = window.dataLayer || [];
133
+ function gtag(){dataLayer.push(arguments);}
134
+ gtag('js', new Date());
135
+ gtag('config', 'G-XXXXXXX');`}
136
+ </Script>
137
+ </>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ---
143
+
144
+ ## โš ๏ธ GDPR / DSGVO Tips
145
+
146
+ - ๐Ÿ‘‰ Always link to your cookie/privacy policy using the `content` property
147
+ - ๐Ÿšซ Never load external scripts (e.g., Google, Meta) **before consent**
148
+ - ๐Ÿ” Use cookies if `localStorage` is not allowed/possible in your compliance context
149
+
150
+ ---
151
+
152
+ ## ๐Ÿงฐ Props & API
153
+
154
+ | Prop | Type | Description |
155
+ |------------------|-------------------------------------|----------------------------------------------------------------------------------------|
156
+ | `categories` | `CookieCategory[]` | An array of consent categories to configure |
157
+ | `content` | `React.ReactNode` | *(optional)* Custom JSX/HTML content shown above the buttons |
158
+ | `storageStrategy`| `'localStorage'` \| `'cookie'` | *(optional)* Where to store consent. Default: `'localStorage'` |
159
+
160
+ ```ts
161
+ interface CookieCategory {
162
+ key: string; // e.g. "analytics"
163
+ label: string; // e.g. "Analytics"
164
+ }
165
+ ```
166
+
167
+ ---
168
+
169
+ ## ๐ŸŽจ Styling
170
+
171
+ You can override the styles and adjust them to your liking.
172
+ Following classes are used:
173
+
174
+ | Element | Class name |
175
+ |---------------------------|-----------------------------------|
176
+ | Whole Banner | `.cookie-banner-container` |
177
+ | Default Banner view | `.cookie-banner-default` |
178
+ | Settings Banner view | `.cookie-banner-settings` |
179
+ | Button group | `.cookie-banner-buttons` |
180
+ | "Only necessary"-button | `.cookie-banner-button-necessary` |
181
+ | "Settings"-button | `.cookie-banner-button-settings` |
182
+ | "Cookie Settings"-heading | `.cookie-banner-settings-heading` |
183
+ | Cookie Category labels | `.cookie-banner-category` |
184
+ | "Save"-button | `.cookie-banner-button-save` |
185
+ | "Back"-button | `.cookie-banner-button-back` |
186
+
187
+ ---
188
+
189
+ ## ๐Ÿ’ก FAQ
190
+
191
+ **Can I use cookies instead of localStorage?**
192
+ > โœ… Yes โ€“ set `storageStrategy="cookie"` in the component props.
193
+
194
+ **Is this compatible with Server Components?**
195
+ > โœ… Yes โ€“ the banner is a client component and works inside any layout or `_app.tsx`.
196
+
197
+ **Is "necessary" always enabled?**
198
+ > โœ… Yes โ€“ the `necessary` flag is always true and cannot be disabled so you can load scripts/cookies which are necessary for your application.
199
+
200
+ ---
201
+
202
+ ## ๐Ÿค Contributing
203
+
204
+ Pull requests and issues are very welcome! This project is open source โ€” feel free to improve it ๐Ÿ’ช
205
+
206
+ ---
207
+
208
+ ## โš–๏ธ License
209
+
210
+ MIT โ€“ free to use for personal and commercial projects.
211
+ Built with โค๏ธ for modern Next.js apps.