consentric 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 salecki.digital
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # consentric
2
+
3
+ [![CI](https://github.com/msalecki/consentric/actions/workflows/ci.yml/badge.svg)](https://github.com/msalecki/consentric/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/consentric.svg)](https://www.npmjs.com/package/consentric)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
+
7
+ **A self-contained, drop-in GDPR cookie consent banner for React — with Google Consent Mode v2 built in.**
8
+
9
+ ![consentric cookie consent banner](assets/preview-consent.png)
10
+
11
+ Most cookie banners are either a paid SaaS widget that injects a third-party script
12
+ into your page, or a barebones snippet you have to wire up yourself. `consentric` is
13
+ neither: it's a single React component you import, style with a few props, and forget
14
+ about. No external scripts, no account, no runtime dependencies, no Tailwind — just one
15
+ component that ships its own styles and talks to Google Consent Mode for you.
16
+
17
+ It uses a familiar tabbed layout (Consent / Details / About, four categories, an
18
+ accordion of per-cookie details) so it feels instantly recognisable to your visitors,
19
+ while every string, colour, category and cookie table stays under your control.
20
+
21
+ ```tsx
22
+ import { CookieConsent } from 'consentric';
23
+
24
+ export default function App() {
25
+ return (
26
+ <>
27
+ {/* your app */}
28
+ <CookieConsent company="salecki.digital" privacyUrl="/privacy" />
29
+ </>
30
+ );
31
+ }
32
+ ```
33
+
34
+ That's the whole integration. On first visit it shows the banner; after a choice it
35
+ persists to a cookie and collapses to a small floating button so visitors can change
36
+ their mind anytime.
37
+
38
+ ## Preview
39
+
40
+ The hero above is the **Consent** tab. The other two:
41
+
42
+ **Details** — every category's cookie table in an accordion.
43
+
44
+ ![consentric — Details tab](assets/preview-details.png)
45
+
46
+ **About** — a plain-language explainer plus your policy links.
47
+
48
+ ![consentric — About tab](assets/preview-about.png)
49
+
50
+ Want to poke at it live? Spin up the playground — language picker plus light/dark themes:
51
+
52
+ ```sh
53
+ npm install
54
+ npm run example # opens http://localhost:5173
55
+ ```
56
+
57
+ ## Features
58
+
59
+ - **Google Consent Mode v2** — pushes `default` (all denied) on mount and `update` on
60
+ the visitor's choice, mapping the four categories to the right Consent Mode signals.
61
+ - **Persistent & versioned** — the choice is stored in a cookie for one year and
62
+ restored on the next visit; bumping the schema version re-prompts everyone.
63
+ - **Familiar tabbed UX** — Consent / Details / About tabs, four categories, a per-cookie
64
+ accordion. Recognisable to visitors, nothing for you to design.
65
+ - **10 built-in languages** — English (default), German, French, Spanish, Italian,
66
+ Portuguese, Dutch, Polish, Czech and Slovak, picked with one `locale` prop. Every
67
+ category, cookie row and UI string is also overridable for any other language.
68
+ - **Accessible** — focus-trapped modal, focus restored on close, arrow-key tab
69
+ navigation, full ARIA wiring, and `prefers-reduced-motion` support.
70
+ - **No dark patterns** — "Allow all" and "Deny" get equal visual weight, the way EU
71
+ regulators expect a consent dialog to behave.
72
+ - **Works with Next.js** — ships as a Client Component (`"use client"`), so it drops
73
+ straight into the App Router.
74
+ - **Zero dependencies, zero Tailwind** — styles are scoped and injected; colours come
75
+ from props via CSS variables, so it drops into any React project and looks identical
76
+ regardless of the host's styling.
77
+ - **Small & self-contained** — one component with all 10 languages, ~15 KB gzipped;
78
+ ships ESM + CJS with types, `sideEffects: false`.
79
+
80
+ ## Install
81
+
82
+ ```sh
83
+ npm install consentric
84
+ ```
85
+
86
+ `react >= 17` is a peer dependency.
87
+
88
+ ## Usage
89
+
90
+ ```tsx
91
+ import { CookieConsent } from 'consentric';
92
+
93
+ <CookieConsent
94
+ company="salecki.digital"
95
+ privacyUrl="/privacy"
96
+ termsUrl="/privacy#cookies"
97
+ colors={{ brand: '#FAE762', surface: '#050506', text: '#EDEDEF', onBrand: '#0a0a0c' }}
98
+ />
99
+ ```
100
+
101
+ With no props it renders the default dark theme and English copy. Render it once, near
102
+ the root of your app.
103
+
104
+ ## Props
105
+
106
+ | Prop | Default | What it does |
107
+ |---|---|---|
108
+ | `manageDefault` | `true` | Push gtag consent `default` (all denied) on mount. Set `false` if you set it in `<head>` before GTM (strict timing — see below). |
109
+ | `cookieName` | `'site_consent'` | Consent cookie name. Must match the head/GTM snippet if you use one. |
110
+ | `company` | — | Operator / brand name shown in the header and About panel. |
111
+ | `logo` | — | Optional brand mark (an `<svg/>` or `<img/>`). |
112
+ | `privacyUrl` | `'/privacy'` | Privacy policy link. |
113
+ | `termsUrl` | `'/privacy#cookies'` | Cookie/terms link. |
114
+ | `locale` | `'en'` | Built-in language pack (`de`, `fr`, `es`, `it`, `pt`, `nl`, `pl`, `cs`, `sk`). English is the default and fallback; `pt-BR` resolves to `pt`. |
115
+ | `defaultOpen` | `false` | Open the dialog on mount even if a choice is stored (for previews/Storybook). |
116
+ | `defaultTab` | `'consent'` | Tab to show first: `consent` / `details` / `about`. |
117
+ | `colors` | dark theme | `{ brand, brandDeep, surface, surfaceAlt, text, textMuted, backdrop, onBrand }`. |
118
+ | `categories` | built-in EN | Per-category content overrides — see below. |
119
+ | `labels` | built-in EN | UI string overrides for localisation — see below. |
120
+ | `onChange` | — | `(choices) => void`, fired after the user makes or changes a choice. |
121
+ | `autoClearCookies` | `true` | When a category is denied or revoked, delete the cookies declared in its table (names support a trailing `*` wildcard). The consent cookie is never touched. |
122
+
123
+ ## Customising categories & cookie tables
124
+
125
+ The four category keys are fixed (they map to Consent Mode signals), but their name,
126
+ descriptions and cookie list are yours. The count badge is derived from the cookie list,
127
+ so it always matches. Anything you omit falls back to the default.
128
+
129
+ ```tsx
130
+ <CookieConsent
131
+ categories={{
132
+ statistics: {
133
+ about: 'We use Plausible, a privacy-friendly analytics tool.',
134
+ cookies: [
135
+ { name: 'plausible_ignore', provider: 'Plausible', purpose: 'Excludes your own visits.', meta: 'localStorage' },
136
+ ],
137
+ },
138
+ // necessary / preferences / marketing keep their defaults
139
+ }}
140
+ />
141
+ ```
142
+
143
+ ## Clearing cookies on withdrawal
144
+
145
+ When a visitor denies or later revokes a category, `consentric` deletes the cookies you
146
+ declared in that category's table — so the data actually leaves the browser, not just the
147
+ Consent Mode signal. Matching is by cookie name and supports a trailing `*` wildcard
148
+ (`_ga_*` clears `_ga_ABC123`), and the component tries the common path/domain combinations
149
+ so cookies set on the registrable domain (e.g. `.example.com`) are caught too.
150
+
151
+ Only cookies you list are ever removed, and the consent cookie itself is always kept. It's
152
+ on by default — pass `autoClearCookies={false}` to manage cleanup yourself.
153
+
154
+ ## Localisation
155
+
156
+ `consentric` ships with **ten built-in languages** — English (the default and the
157
+ fallback for any missing string) plus nine more. Pick one with the `locale` prop and the
158
+ banner, categories and cookie tables are all translated:
159
+
160
+ ```tsx
161
+ <CookieConsent company="salecki.digital" locale="pl" />
162
+ ```
163
+
164
+ | Code | Language | Code | Language |
165
+ |---|---|---|---|
166
+ | `en` | English (default) | `pt` | Portuguese |
167
+ | `de` | German | `nl` | Dutch |
168
+ | `fr` | French | `pl` | Polish |
169
+ | `es` | Spanish | `cs` | Czech |
170
+ | `it` | Italian | `sk` | Slovak |
171
+
172
+ Region subtags are accepted — `locale="pt-BR"` resolves to `pt`. The full list is also
173
+ exported as `SUPPORTED_LOCALES`.
174
+
175
+ ### Custom strings & other languages
176
+
177
+ For a language without a built-in pack — or to reword one — pass your own strings via
178
+ `labels` (and `categories`). They win over the locale pack, which in turn falls back to
179
+ English, so you can override just a key or two. The `operatedBy` and `readMore` templates
180
+ support `{company}`, `{privacy}` and `{cookie}` placeholders so word order works in any
181
+ language.
182
+
183
+ ```tsx
184
+ <CookieConsent
185
+ locale="de"
186
+ labels={{
187
+ // start from the German pack, just reword these two
188
+ heading: 'Wir verwenden Cookies',
189
+ deny: 'Nur notwendige',
190
+ }}
191
+ />
192
+ ```
193
+
194
+ ## Light theme
195
+
196
+ Defaults assume a dark `surface`. For a light theme, also set `onBrand` (the text/icon
197
+ colour on top of the primary button) so it keeps enough contrast.
198
+
199
+ ```tsx
200
+ <CookieConsent
201
+ colors={{
202
+ brand: '#2563eb',
203
+ brandDeep: '#1d4ed8',
204
+ surface: '#ffffff',
205
+ surfaceAlt: 'rgba(0,0,0,0.04)',
206
+ text: '#0f172a',
207
+ textMuted: '#64748b',
208
+ backdrop: 'rgba(15,23,42,0.4)',
209
+ onBrand: '#ffffff',
210
+ }}
211
+ />
212
+ ```
213
+
214
+ ## Strict Consent Mode timing (recommended)
215
+
216
+ For tags to be gated **before** GTM loads, set the default in `<head>` (before the
217
+ GTM snippet) and pass `manageDefault={false}`. The same snippet can go into a GTM
218
+ Custom HTML tag on the **Consent Initialization - All Pages** trigger. The cookie
219
+ name in that snippet must match `cookieName`. Full snippet is in the header of
220
+ [src/CookieConsent.tsx](src/CookieConsent.tsx). If you skip both, leave `manageDefault`
221
+ on — the component pushes the default on mount (fine for simple setups).
222
+
223
+ ## License
224
+
225
+ MIT © [salecki.digital](https://salecki.digital)