consentric 1.0.0 → 2.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/README.md +71 -15
- package/dist/index.cjs +104 -35
- package/dist/index.d.cts +67 -17
- package/dist/index.d.ts +67 -17
- package/dist/index.js +104 -35
- package/package.json +7 -4
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -59,16 +59,21 @@ npm run example # opens http://localhost:5173
|
|
|
59
59
|
- **Google Consent Mode v2** — pushes `default` (all denied) on mount and `update` on
|
|
60
60
|
the visitor's choice, mapping the four categories to the right Consent Mode signals.
|
|
61
61
|
- **Persistent & versioned** — the choice is stored in a cookie for one year and
|
|
62
|
-
restored on the next visit;
|
|
62
|
+
restored on the next visit; the stored value is versioned, so a future release can
|
|
63
|
+
re-prompt everyone after a policy change.
|
|
63
64
|
- **Familiar tabbed UX** — Consent / Details / About tabs, four categories, a per-cookie
|
|
64
65
|
accordion. Recognisable to visitors, nothing for you to design.
|
|
65
66
|
- **10 built-in languages** — English (default), German, French, Spanish, Italian,
|
|
66
67
|
Portuguese, Dutch, Polish, Czech and Slovak, picked with one `locale` prop. Every
|
|
67
68
|
category, cookie row and UI string is also overridable for any other language.
|
|
69
|
+
- **Light & dark** — follows the OS `prefers-color-scheme` by default; ships light and
|
|
70
|
+
dark palettes, or pass your own (one for both themes, or `{ light, dark }`). Borders and
|
|
71
|
+
fills derive from your `text` colour, so a light theme needs no host-CSS overrides.
|
|
68
72
|
- **Accessible** — focus-trapped modal, focus restored on close, arrow-key tab
|
|
69
73
|
navigation, full ARIA wiring, and `prefers-reduced-motion` support.
|
|
70
|
-
- **No dark patterns** — "Allow all" and "Deny" get equal visual weight, the
|
|
71
|
-
regulators expect a consent dialog to behave.
|
|
74
|
+
- **No dark patterns by default** — "Allow all" and "Deny" get equal visual weight, the
|
|
75
|
+
way EU regulators expect a consent dialog to behave. If you want the familiar
|
|
76
|
+
emphasised "Allow all", opt in with `primaryAction="allowAll"`.
|
|
72
77
|
- **Works with Next.js** — ships as a Client Component (`"use client"`), so it drops
|
|
73
78
|
straight into the App Router.
|
|
74
79
|
- **Zero dependencies, zero Tailwind** — styles are scoped and injected; colours come
|
|
@@ -114,9 +119,14 @@ the root of your app.
|
|
|
114
119
|
| `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
120
|
| `defaultOpen` | `false` | Open the dialog on mount even if a choice is stored (for previews/Storybook). |
|
|
116
121
|
| `defaultTab` | `'consent'` | Tab to show first: `consent` / `details` / `about`. |
|
|
117
|
-
| `
|
|
122
|
+
| `theme` | `'auto'` | `'auto'` follows the OS `prefers-color-scheme` (and re-renders on change); `'light'` / `'dark'` pin it. |
|
|
123
|
+
| `colors` | light + dark | One palette (applied to both themes) **or** `{ light, dark }`. See [Light & dark themes](#light--dark-themes). |
|
|
124
|
+
| `logo` | — | Brand mark. A single node, or `{ light, dark }` to swap it by theme. |
|
|
118
125
|
| `categories` | built-in EN | Per-category content overrides — see below. |
|
|
119
126
|
| `labels` | built-in EN | UI string overrides for localisation — see below. |
|
|
127
|
+
| `primaryAction` | `'save'` | Which button gets brand emphasis: `'save'` (only once a category is on — equal-weight otherwise), `'allowAll'` (always), or `'none'`. |
|
|
128
|
+
| `deferOpen` | `true` | Defer the first-visit auto-open past first paint (avoids Lighthouse `NO_LCP`). Ignored when `defaultOpen`. |
|
|
129
|
+
| `fontFamily` | system stack | Override the font stack. |
|
|
120
130
|
| `onChange` | — | `(choices) => void`, fired after the user makes or changes a choice. |
|
|
121
131
|
| `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
132
|
|
|
@@ -191,22 +201,68 @@ language.
|
|
|
191
201
|
/>
|
|
192
202
|
```
|
|
193
203
|
|
|
194
|
-
## Light
|
|
204
|
+
## Light & dark themes
|
|
195
205
|
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
The banner ships **built-in light and dark palettes** and, by default (`theme="auto"`),
|
|
207
|
+
follows the visitor's OS `prefers-color-scheme` — re-rendering if they flip it. So the
|
|
208
|
+
zero-config banner is light on a light OS and dark on a dark one. Pin it with
|
|
209
|
+
`theme="light"` or `theme="dark"`.
|
|
210
|
+
|
|
211
|
+
Borders, toggle tracks, badges and cookie rows are derived from `text` (via `color-mix`),
|
|
212
|
+
so a **light theme works from just `brand` / `surface` / `text`** — no host-CSS overrides,
|
|
213
|
+
nothing washed out on a light surface.
|
|
214
|
+
|
|
215
|
+
### One palette, or one per theme
|
|
216
|
+
|
|
217
|
+
Pass a **single palette** and it applies to both themes (this is the v1 behaviour — if you
|
|
218
|
+
already pass `colors`, nothing changes):
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
<CookieConsent colors={{ brand: '#FAE762', surface: '#050506', text: '#EDEDEF', onBrand: '#0a0a0c' }} />
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Or pass **`{ light, dark }`** and the component switches with the theme:
|
|
198
225
|
|
|
199
226
|
```tsx
|
|
200
227
|
<CookieConsent
|
|
228
|
+
theme="auto" // 'auto' | 'light' | 'dark'
|
|
201
229
|
colors={{
|
|
202
|
-
brand: '#2563eb',
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
230
|
+
light: { brand: '#2563eb', surface: '#ffffff', text: '#0f172a', textMuted: '#64748b' },
|
|
231
|
+
dark: { brand: '#3b82f6', surface: '#0b1220', text: '#e5e7eb', textMuted: '#94a3b8' },
|
|
232
|
+
}}
|
|
233
|
+
/>
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Each palette key is optional and falls back to the built-in default for that theme:
|
|
237
|
+
|
|
238
|
+
| Key | What it colours |
|
|
239
|
+
|---|---|
|
|
240
|
+
| `brand` / `brandDeep` | Primary button, active tab, toggle-on, links; `brandDeep` is the hover. |
|
|
241
|
+
| `surface` / `surfaceAlt` | Card background / the raised category tiles. |
|
|
242
|
+
| `text` / `textMuted` | Primary / secondary text. Overlays (borders, tracks, badges) derive from `text`. |
|
|
243
|
+
| `backdrop` | The dimmed page behind the dialog. |
|
|
244
|
+
| `onBrand` | Text/icon on top of `brand` — primary button label, toggle knob, FAB icon. Set this whenever `brand` is light/saturated. |
|
|
245
|
+
| `border`, `hover`, `trackOff`, `badgeBg`, `cookieBg`, `link` | Fine control of the derived overlay tokens — override only if you need to. |
|
|
246
|
+
|
|
247
|
+
### Following a `data-theme` host
|
|
248
|
+
|
|
249
|
+
Already flip your own CSS variables on `html[data-theme]`? Point `colors` at them and the
|
|
250
|
+
banner follows your toggle with no JS:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
<CookieConsent colors={{ surface: 'var(--bg)', text: 'var(--fg)', textMuted: 'var(--fg-muted)', brand: '#3c3fde', onBrand: '#fff' }} />
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Per-theme logo
|
|
257
|
+
|
|
258
|
+
Pass `{ light, dark }` to `logo` so the mark swaps with the theme (e.g. a dark wordmark on
|
|
259
|
+
light, a light one on dark):
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<CookieConsent
|
|
263
|
+
logo={{
|
|
264
|
+
light: <img src="/logo.svg" alt="Acme" height={20} />,
|
|
265
|
+
dark: <img src="/logo-dark.svg" alt="Acme" height={20} />,
|
|
210
266
|
}}
|
|
211
267
|
/>
|
|
212
268
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -600,11 +600,11 @@ function resolveLocale(locale) {
|
|
|
600
600
|
|
|
601
601
|
// src/styles.ts
|
|
602
602
|
var CSS = `
|
|
603
|
-
.tc-root,.tc-fab{font-family
|
|
603
|
+
.tc-root,.tc-fab{font-family:var(--tc-font,-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif);box-sizing:border-box}
|
|
604
604
|
.tc-root *,.tc-fab *{box-sizing:border-box}
|
|
605
605
|
.tc-root{position:fixed;inset:0;z-index:2147483600;display:flex;align-items:center;justify-content:center;padding:0}
|
|
606
606
|
.tc-backdrop{position:absolute;inset:0;background:var(--tc-backdrop);backdrop-filter:blur(4px)}
|
|
607
|
-
.tc-card{position:relative;display:flex;flex-direction:column;width:100%;height:100dvh;overflow:hidden;background:var(--tc-surface);box-shadow:0 30px 80px -20px rgba(0,0,0,.7);opacity:0;transform:translateY(18px);transition:opacity .26s ease,transform .26s cubic-bezier(.22,1,.36,1)}
|
|
607
|
+
.tc-card{position:relative;display:flex;flex-direction:column;width:100%;min-width:0;height:100dvh;overflow:hidden;background:var(--tc-surface);box-shadow:0 30px 80px -20px rgba(0,0,0,.7);opacity:0;transform:translateY(18px);transition:opacity .26s ease,transform .26s cubic-bezier(.22,1,.36,1)}
|
|
608
608
|
.tc-card:focus{outline:none}
|
|
609
609
|
.tc-card.tc-shown{opacity:1;transform:none}
|
|
610
610
|
.tc-head{display:flex;align-items:baseline;justify-content:space-between;gap:1rem;padding:1.25rem 1.5rem 0}
|
|
@@ -614,7 +614,7 @@ var CSS = `
|
|
|
614
614
|
.tc-tab.tc-active{color:var(--tc-text)}
|
|
615
615
|
.tc-tab.tc-active::after{content:'';position:absolute;left:0;right:0;bottom:-1px;height:2px;border-radius:2px;background:var(--tc-brand)}
|
|
616
616
|
.tc-logo{flex-shrink:0;color:var(--tc-text);font-weight:700;font-size:15px}
|
|
617
|
-
.tc-hr{border:0;border-top:1px solid rgba(
|
|
617
|
+
.tc-hr{border:0;border-top:1px solid var(--tc-line,rgba(128,128,128,.16));margin:0}
|
|
618
618
|
.tc-body{flex:1;min-height:0;overflow-y:auto}
|
|
619
619
|
.tc-panel{padding:1.75rem 1.5rem}
|
|
620
620
|
.tc-h2{margin:0;font-size:1rem;font-weight:600;color:var(--tc-text)}
|
|
@@ -625,18 +625,18 @@ var CSS = `
|
|
|
625
625
|
.tc-cat-desc{margin:.375rem 0 0;flex:1;font-size:11.5px;line-height:1.35;color:var(--tc-text-muted)}
|
|
626
626
|
.tc-switch{position:relative;display:inline-flex;align-items:center;margin-top:1rem;cursor:pointer}
|
|
627
627
|
.tc-switch input{position:absolute;width:1px;height:1px;opacity:0;margin:0}
|
|
628
|
-
.tc-track{width:2.75rem;height:1.5rem;border-radius:999px;background:rgba(
|
|
628
|
+
.tc-track{width:2.75rem;height:1.5rem;border-radius:999px;background:var(--tc-track-off,rgba(128,128,128,.32));transition:background .2s}
|
|
629
629
|
.tc-thumb{position:absolute;left:2px;top:2px;width:1.25rem;height:1.25rem;border-radius:999px;background:var(--tc-text);transition:transform .2s}
|
|
630
630
|
.tc-switch input:checked~.tc-track{background:var(--tc-brand)}
|
|
631
631
|
.tc-switch input:checked~.tc-thumb{transform:translateX(1.25rem);background:var(--tc-on-brand)}
|
|
632
632
|
.tc-switch input:disabled{cursor:default}
|
|
633
633
|
.tc-switch input:focus-visible~.tc-track{outline:2px solid var(--tc-brand);outline-offset:2px}
|
|
634
|
-
.tc-acc{padding:1.25rem 0;border-top:1px solid rgba(
|
|
634
|
+
.tc-acc{padding:1.25rem 0;border-top:1px solid var(--tc-line,rgba(128,128,128,.16))}
|
|
635
635
|
.tc-acc:first-child{border-top:0}
|
|
636
636
|
.tc-acc-head{display:flex;align-items:center;gap:.75rem}
|
|
637
637
|
.tc-acc-btn{display:flex;flex:1;align-items:center;gap:.625rem;text-align:left;background:none;border:0;color:var(--tc-text);cursor:pointer;font:inherit}
|
|
638
638
|
.tc-acc-name{font-size:.875rem;font-weight:600}
|
|
639
|
-
.tc-badge{display:grid;place-items:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:999px;background:rgba(
|
|
639
|
+
.tc-badge{display:grid;place-items:center;min-width:1.25rem;height:1.25rem;padding:0 .375rem;border-radius:999px;background:var(--tc-badge-bg,rgba(128,128,128,.2));font-size:10px;color:var(--tc-text-muted);font-family:ui-monospace,monospace}
|
|
640
640
|
.tc-chev{flex-shrink:0;color:var(--tc-text-muted);transition:transform .2s}
|
|
641
641
|
.tc-acc.tc-open .tc-chev{transform:rotate(180deg)}
|
|
642
642
|
.tc-acc-about{margin:.75rem 0 0;padding-left:1.625rem;font-size:13px;line-height:1.6;color:var(--tc-text-muted)}
|
|
@@ -644,19 +644,19 @@ var CSS = `
|
|
|
644
644
|
.tc-acc.tc-open .tc-acc-panel{grid-template-rows:1fr}
|
|
645
645
|
.tc-acc-inner{overflow:hidden;min-height:0;display:flex;flex-direction:column;gap:.5rem}
|
|
646
646
|
.tc-acc.tc-open .tc-acc-inner{padding-top:.75rem}
|
|
647
|
-
.tc-cookie{border-radius:.75rem;background:rgba(
|
|
647
|
+
.tc-cookie{border-radius:.75rem;background:var(--tc-cookie-bg,rgba(128,128,128,.06));padding:.75rem}
|
|
648
648
|
.tc-cookie-name{margin:0;font-family:ui-monospace,monospace;font-size:12px;color:var(--tc-text)}
|
|
649
649
|
.tc-cookie-purpose{margin:.25rem 0 0;font-size:12px;color:var(--tc-text-muted)}
|
|
650
650
|
.tc-cookie-meta{margin:.25rem 0 0;font-family:ui-monospace,monospace;font-size:10.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--tc-text-muted)}
|
|
651
651
|
.tc-cookie-empty{font-size:12px;font-style:italic;color:var(--tc-text-muted)}
|
|
652
652
|
.tc-about p{margin:0 0 .75rem;font-size:13.5px;line-height:1.6;color:var(--tc-text-muted)}
|
|
653
|
-
.tc-link{color:var(--tc-brand);text-decoration:underline;text-underline-offset:2px}
|
|
653
|
+
.tc-link{color:var(--tc-link,var(--tc-brand));text-decoration:underline;text-underline-offset:2px}
|
|
654
654
|
.tc-actions{display:flex;flex-direction:column;gap:.625rem;padding:1.25rem 1.5rem}
|
|
655
|
-
.tc-btn{flex:1;padding:.8rem 1.25rem;border-radius:999px;font-size:.875rem;font-weight:600;cursor:pointer;border:1px solid rgba(
|
|
656
|
-
.tc-btn:hover{background:rgba(
|
|
655
|
+
.tc-btn{flex:1;padding:.8rem 1.25rem;border-radius:999px;font-size:.875rem;font-weight:600;cursor:pointer;border:1px solid var(--tc-btn-border,rgba(128,128,128,.3));background:transparent;color:var(--tc-text);transition:background .15s,border-color .15s}
|
|
656
|
+
.tc-btn:hover{background:var(--tc-btn-hover,rgba(128,128,128,.12))}
|
|
657
657
|
.tc-btn-primary{border-color:transparent;background:var(--tc-brand);color:var(--tc-on-brand)}
|
|
658
658
|
.tc-btn-primary:hover{background:var(--tc-brand-deep)}
|
|
659
|
-
.tc-fab{position:fixed;bottom:1.25rem;left:1.25rem;z-index:2147483600;display:grid;place-items:center;width:2.75rem;height:2.75rem;border-radius:999px;border:0;cursor:pointer;background:var(--tc-brand);color:var(--tc-
|
|
659
|
+
.tc-fab{position:fixed;bottom:1.25rem;left:1.25rem;z-index:2147483600;display:grid;place-items:center;width:2.75rem;height:2.75rem;border-radius:999px;border:0;cursor:pointer;background:var(--tc-brand);color:var(--tc-on-brand);box-shadow:0 12px 30px -8px rgba(0,0,0,.6);transition:background .15s}
|
|
660
660
|
.tc-fab:hover{background:var(--tc-brand-deep)}
|
|
661
661
|
@media (min-width:640px){
|
|
662
662
|
.tc-root{padding:1rem}
|
|
@@ -675,6 +675,26 @@ var CSS = `
|
|
|
675
675
|
`;
|
|
676
676
|
var TABS = ["consent", "details", "about"];
|
|
677
677
|
var EMPTY = { preferences: false, statistics: false, marketing: false };
|
|
678
|
+
var DARK_PALETTE = {
|
|
679
|
+
brand: "#ff5c3a",
|
|
680
|
+
brandDeep: "#eb5535",
|
|
681
|
+
surface: "#0c0814",
|
|
682
|
+
surfaceAlt: "rgba(255,255,255,0.035)",
|
|
683
|
+
text: "#f4ede3",
|
|
684
|
+
textMuted: "#a89cb8",
|
|
685
|
+
backdrop: "rgba(7,5,13,0.8)",
|
|
686
|
+
onBrand: "#0c0814"
|
|
687
|
+
};
|
|
688
|
+
var LIGHT_PALETTE = {
|
|
689
|
+
brand: "#ff5c3a",
|
|
690
|
+
brandDeep: "#eb5535",
|
|
691
|
+
surface: "#ffffff",
|
|
692
|
+
surfaceAlt: "rgba(18,12,28,0.04)",
|
|
693
|
+
text: "#1a1523",
|
|
694
|
+
textMuted: "#6b6577",
|
|
695
|
+
backdrop: "rgba(18,12,28,0.4)",
|
|
696
|
+
onBrand: "#1a1523"
|
|
697
|
+
};
|
|
678
698
|
var DEFAULT_LABELS = {
|
|
679
699
|
tabConsent: "Consent",
|
|
680
700
|
tabDetails: "Details",
|
|
@@ -775,9 +795,13 @@ function CookieConsent({
|
|
|
775
795
|
locale,
|
|
776
796
|
defaultOpen = false,
|
|
777
797
|
defaultTab,
|
|
798
|
+
theme = "auto",
|
|
778
799
|
colors,
|
|
779
800
|
categories,
|
|
780
801
|
labels,
|
|
802
|
+
primaryAction = "save",
|
|
803
|
+
deferOpen = true,
|
|
804
|
+
fontFamily,
|
|
781
805
|
onChange,
|
|
782
806
|
autoClearCookies = true
|
|
783
807
|
}) {
|
|
@@ -787,6 +811,11 @@ function CookieConsent({
|
|
|
787
811
|
const [tab, setTab] = react.useState(defaultTab ?? "consent");
|
|
788
812
|
const [choices, setChoices] = react.useState(EMPTY);
|
|
789
813
|
const [accOpen, setAccOpen] = react.useState({});
|
|
814
|
+
const [sysDark, setSysDark] = react.useState(() => {
|
|
815
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return true;
|
|
816
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
817
|
+
});
|
|
818
|
+
const resolvedTheme = theme === "light" ? "light" : theme === "dark" ? "dark" : sysDark ? "dark" : "light";
|
|
790
819
|
const cardRef = react.useRef(null);
|
|
791
820
|
const fabRef = react.useRef(null);
|
|
792
821
|
const restoreFocusRef = react.useRef(null);
|
|
@@ -828,7 +857,33 @@ function CookieConsent({
|
|
|
828
857
|
applyConsent(saved);
|
|
829
858
|
setDecided(true);
|
|
830
859
|
}
|
|
831
|
-
if (
|
|
860
|
+
if (defaultOpen) {
|
|
861
|
+
setOpen(true);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (saved) return;
|
|
865
|
+
if (!deferOpen || typeof window === "undefined") {
|
|
866
|
+
setOpen(true);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
let cancel = () => {
|
|
870
|
+
};
|
|
871
|
+
const openNow = () => setOpen(true);
|
|
872
|
+
const schedule = () => {
|
|
873
|
+
const ric = window.requestIdleCallback;
|
|
874
|
+
if (typeof ric === "function") {
|
|
875
|
+
const id = ric(openNow, { timeout: 600 });
|
|
876
|
+
cancel = () => window.cancelIdleCallback?.(id);
|
|
877
|
+
} else {
|
|
878
|
+
const id = window.setTimeout(openNow, 600);
|
|
879
|
+
cancel = () => window.clearTimeout(id);
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
const raf = requestAnimationFrame(() => requestAnimationFrame(schedule));
|
|
883
|
+
return () => {
|
|
884
|
+
cancelAnimationFrame(raf);
|
|
885
|
+
cancel();
|
|
886
|
+
};
|
|
832
887
|
}, []);
|
|
833
888
|
react.useEffect(() => {
|
|
834
889
|
if (!open) {
|
|
@@ -877,6 +932,18 @@ function CookieConsent({
|
|
|
877
932
|
window.addEventListener("keydown", onKey);
|
|
878
933
|
return () => window.removeEventListener("keydown", onKey);
|
|
879
934
|
}, [decided]);
|
|
935
|
+
react.useEffect(() => {
|
|
936
|
+
if (theme !== "auto" || typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
937
|
+
const mq = window.matchMedia("(prefers-color-scheme: dark)");
|
|
938
|
+
const on = () => setSysDark(mq.matches);
|
|
939
|
+
on();
|
|
940
|
+
if (mq.addEventListener) {
|
|
941
|
+
mq.addEventListener("change", on);
|
|
942
|
+
return () => mq.removeEventListener("change", on);
|
|
943
|
+
}
|
|
944
|
+
mq.addListener(on);
|
|
945
|
+
return () => mq.removeListener(on);
|
|
946
|
+
}, [theme]);
|
|
880
947
|
const commit = (ch) => {
|
|
881
948
|
writeCookie(cookieName, ch);
|
|
882
949
|
applyConsent(ch);
|
|
@@ -907,28 +974,32 @@ function CookieConsent({
|
|
|
907
974
|
setTab(id);
|
|
908
975
|
requestAnimationFrame(() => document.getElementById("tc-tab-" + id)?.focus());
|
|
909
976
|
};
|
|
910
|
-
const
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
surface: colors?.surface ?? "#0c0814",
|
|
914
|
-
surfaceAlt: colors?.surfaceAlt ?? "rgba(255,255,255,0.035)",
|
|
915
|
-
text: colors?.text ?? "#f4ede3",
|
|
916
|
-
textMuted: colors?.textMuted ?? "#a89cb8",
|
|
917
|
-
backdrop: colors?.backdrop ?? "rgba(7,5,13,0.8)",
|
|
918
|
-
onBrand: colors?.onBrand ?? colors?.surface ?? "#0c0814"
|
|
919
|
-
};
|
|
977
|
+
const userPalette = colors ? "light" in colors || "dark" in colors ? colors[resolvedTheme] : colors : void 0;
|
|
978
|
+
const P = { ...resolvedTheme === "light" ? LIGHT_PALETTE : DARK_PALETTE, ...userPalette };
|
|
979
|
+
const mix = (c, pct) => `color-mix(in srgb, ${c} ${pct}%, transparent)`;
|
|
920
980
|
const vars = {
|
|
921
|
-
"--tc-brand":
|
|
922
|
-
"--tc-brand-deep":
|
|
923
|
-
"--tc-surface":
|
|
924
|
-
"--tc-surface-alt":
|
|
925
|
-
"--tc-text":
|
|
926
|
-
"--tc-text-muted":
|
|
927
|
-
"--tc-backdrop":
|
|
928
|
-
"--tc-on-brand":
|
|
981
|
+
"--tc-brand": P.brand,
|
|
982
|
+
"--tc-brand-deep": P.brandDeep,
|
|
983
|
+
"--tc-surface": P.surface,
|
|
984
|
+
"--tc-surface-alt": P.surfaceAlt,
|
|
985
|
+
"--tc-text": P.text,
|
|
986
|
+
"--tc-text-muted": P.textMuted,
|
|
987
|
+
"--tc-backdrop": P.backdrop,
|
|
988
|
+
"--tc-on-brand": P.onBrand,
|
|
989
|
+
"--tc-line": P.border ?? mix(P.text, 7),
|
|
990
|
+
"--tc-btn-border": P.border ?? mix(P.text, 14),
|
|
991
|
+
"--tc-btn-hover": P.hover ?? mix(P.text, 6),
|
|
992
|
+
"--tc-track-off": P.trackOff ?? mix(P.text, 15),
|
|
993
|
+
"--tc-badge-bg": P.badgeBg ?? mix(P.text, 10),
|
|
994
|
+
"--tc-cookie-bg": P.cookieBg ?? mix(P.text, 3),
|
|
995
|
+
"--tc-link": P.link ?? P.brand,
|
|
996
|
+
"--tc-font": fontFamily
|
|
929
997
|
};
|
|
998
|
+
const logoNode = logo && typeof logo === "object" && !("$$typeof" in logo) && ("light" in logo || "dark" in logo) ? logo[resolvedTheme] : logo;
|
|
930
999
|
const tabLabel = (t) => t === "consent" ? L.tabConsent : t === "details" ? L.tabDetails : L.tabAbout;
|
|
931
1000
|
const hasSelection = choices.preferences || choices.statistics || choices.marketing;
|
|
1001
|
+
const savePrimary = primaryAction === "save" && hasSelection;
|
|
1002
|
+
const allowPrimary = primaryAction === "allowAll";
|
|
932
1003
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
933
1004
|
/* @__PURE__ */ jsxRuntime.jsx("style", { children: CSS }),
|
|
934
1005
|
open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tc-root", style: vars, role: "dialog", "aria-modal": "true", "aria-label": L.dialogLabel, children: [
|
|
@@ -951,7 +1022,7 @@ function CookieConsent({
|
|
|
951
1022
|
},
|
|
952
1023
|
t
|
|
953
1024
|
)) }),
|
|
954
|
-
|
|
1025
|
+
logoNode ?? (company ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tc-logo", children: company }) : null)
|
|
955
1026
|
] }),
|
|
956
1027
|
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "tc-hr" }),
|
|
957
1028
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tc-body", children: [
|
|
@@ -997,8 +1068,8 @@ function CookieConsent({
|
|
|
997
1068
|
/* @__PURE__ */ jsxRuntime.jsx("hr", { className: "tc-hr" }),
|
|
998
1069
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tc-actions", children: [
|
|
999
1070
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "tc-btn", onClick: denyAll, children: L.deny }),
|
|
1000
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "tc-btn" + (
|
|
1001
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "tc-btn", onClick: acceptAll, children: L.allowAll })
|
|
1071
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "tc-btn" + (savePrimary ? " tc-btn-primary" : ""), onClick: saveChoices, children: L.save }),
|
|
1072
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "tc-btn" + (allowPrimary ? " tc-btn-primary" : ""), onClick: acceptAll, children: L.allowAll })
|
|
1002
1073
|
] })
|
|
1003
1074
|
] })
|
|
1004
1075
|
] }),
|
|
@@ -1015,5 +1086,3 @@ exports.LOCALES = LOCALES;
|
|
|
1015
1086
|
exports.SUPPORTED_LOCALES = SUPPORTED_LOCALES;
|
|
1016
1087
|
exports.default = CookieConsent_default;
|
|
1017
1088
|
exports.resolveLocale = resolveLocale;
|
|
1018
|
-
//# sourceMappingURL=index.cjs.map
|
|
1019
|
-
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.cts
CHANGED
|
@@ -29,6 +29,45 @@ interface CategoryContent {
|
|
|
29
29
|
/** Cookies in this category (the count badge is derived from this list). */
|
|
30
30
|
cookies?: CookieInfo[];
|
|
31
31
|
}
|
|
32
|
+
/** A single theme's colour palette. Every key is optional; anything omitted falls
|
|
33
|
+
* back to the built-in default for the active theme. The last six ("overlay")
|
|
34
|
+
* tokens are derived from `text` via `color-mix` when omitted, so a light theme
|
|
35
|
+
* works from `brand`/`surface`/`text` alone — override them only for fine control. */
|
|
36
|
+
interface Palette {
|
|
37
|
+
brand?: string;
|
|
38
|
+
brandDeep?: string;
|
|
39
|
+
surface?: string;
|
|
40
|
+
surfaceAlt?: string;
|
|
41
|
+
text?: string;
|
|
42
|
+
textMuted?: string;
|
|
43
|
+
backdrop?: string;
|
|
44
|
+
/** Text/icon colour on top of `brand` (primary button, toggle knob, FAB icon). */
|
|
45
|
+
onBrand?: string;
|
|
46
|
+
/** Hairlines and button borders. Default: `color-mix` of `text`. */
|
|
47
|
+
border?: string;
|
|
48
|
+
/** Button hover fill. Default: `color-mix` of `text`. */
|
|
49
|
+
hover?: string;
|
|
50
|
+
/** Toggle track when off. Default: `color-mix` of `text`. */
|
|
51
|
+
trackOff?: string;
|
|
52
|
+
/** Count-badge background. Default: `color-mix` of `text`. */
|
|
53
|
+
badgeBg?: string;
|
|
54
|
+
/** Cookie-row background. Default: `color-mix` of `text`. */
|
|
55
|
+
cookieBg?: string;
|
|
56
|
+
/** Policy-link colour on the About tab. Default: `brand`. */
|
|
57
|
+
link?: string;
|
|
58
|
+
}
|
|
59
|
+
/** The `colors` prop. Either one palette (pinned to both themes — back-compatible
|
|
60
|
+
* with v1) or a `{ light, dark }` pair the component switches between by theme. */
|
|
61
|
+
type ColorsProp = Palette | {
|
|
62
|
+
light?: Palette;
|
|
63
|
+
dark?: Palette;
|
|
64
|
+
};
|
|
65
|
+
/** A brand mark. Either one node (both themes) or a `{ light, dark }` pair the
|
|
66
|
+
* component swaps by the active theme — so the logo follows light/dark too. */
|
|
67
|
+
type LogoProp = ReactNode | {
|
|
68
|
+
light?: ReactNode;
|
|
69
|
+
dark?: ReactNode;
|
|
70
|
+
};
|
|
32
71
|
/** Per-category overrides. The four keys are fixed (they map to Consent Mode signals). */
|
|
33
72
|
interface CategoriesConfig {
|
|
34
73
|
necessary?: CategoryContent;
|
|
@@ -69,8 +108,9 @@ interface CookieConsentProps {
|
|
|
69
108
|
cookieName?: string;
|
|
70
109
|
/** Operator / brand name shown in the header and About panel. */
|
|
71
110
|
company?: string;
|
|
72
|
-
/** Optional brand mark for the header (
|
|
73
|
-
|
|
111
|
+
/** Optional brand mark for the header (an <svg/> or <img/>). Pass a single node,
|
|
112
|
+
* or `{ light, dark }` to swap the mark by theme. */
|
|
113
|
+
logo?: LogoProp;
|
|
74
114
|
privacyUrl?: string;
|
|
75
115
|
termsUrl?: string;
|
|
76
116
|
/** Built-in language pack to use (e.g. 'de', 'pl'). English is the default and
|
|
@@ -82,23 +122,33 @@ interface CookieConsentProps {
|
|
|
82
122
|
defaultOpen?: boolean;
|
|
83
123
|
/** Tab to show first. Default: 'consent'. */
|
|
84
124
|
defaultTab?: 'consent' | 'details' | 'about';
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
backdrop?: string;
|
|
95
|
-
/** Text/icon colour on top of `brand` (primary button). Default: `surface`. */
|
|
96
|
-
onBrand?: string;
|
|
97
|
-
};
|
|
125
|
+
/** Which theme to render. `'auto'` (default) follows the OS `prefers-color-scheme`
|
|
126
|
+
* and re-renders when it changes; `'light'`/`'dark'` pin it. Only affects which
|
|
127
|
+
* half of a `{ light, dark }` `colors`/`logo` is used — a single palette applies
|
|
128
|
+
* to both themes. */
|
|
129
|
+
theme?: 'auto' | 'light' | 'dark';
|
|
130
|
+
/** Brand palette. Either one palette (applied to both themes) or `{ light, dark }`.
|
|
131
|
+
* Built-in defaults cover both themes; a light theme works from `brand`/`surface`/
|
|
132
|
+
* `text` alone (the overlay tokens are derived from `text`). */
|
|
133
|
+
colors?: ColorsProp;
|
|
98
134
|
/** Per-category content overrides (names, descriptions, cookie tables). */
|
|
99
135
|
categories?: CategoriesConfig;
|
|
100
136
|
/** UI string overrides for localisation. */
|
|
101
137
|
labels?: ConsentLabels;
|
|
138
|
+
/** Which action button gets primary (filled/brand) emphasis.
|
|
139
|
+
* - `'save'` (default): "Save choices" is emphasised only once the visitor turns
|
|
140
|
+
* an optional category on — Accept/Deny stay equal-weight otherwise, the
|
|
141
|
+
* GDPR-friendly default (regulators expect no dark patterns).
|
|
142
|
+
* - `'allowAll'`: "Allow all" is always emphasised (familiar consumer look).
|
|
143
|
+
* - `'none'`: no button is ever emphasised. */
|
|
144
|
+
primaryAction?: 'save' | 'allowAll' | 'none';
|
|
145
|
+
/** Defer the first-visit auto-open until the browser is idle (double-rAF +
|
|
146
|
+
* `requestIdleCallback`, capped ~600ms). Keeps the full-screen card from being
|
|
147
|
+
* the only thing painted on first frame, which otherwise starves Lighthouse of
|
|
148
|
+
* an LCP candidate (`NO_LCP`). Ignored when `defaultOpen`. Default: true. */
|
|
149
|
+
deferOpen?: boolean;
|
|
150
|
+
/** Override the font stack. Default: the system UI stack. */
|
|
151
|
+
fontFamily?: string;
|
|
102
152
|
/** Fired after the user makes (or changes) a choice. */
|
|
103
153
|
onChange?: (choices: Choices) => void;
|
|
104
154
|
/** When a category is denied or revoked, delete the cookies declared in its
|
|
@@ -107,7 +157,7 @@ interface CookieConsentProps {
|
|
|
107
157
|
autoClearCookies?: boolean;
|
|
108
158
|
}
|
|
109
159
|
|
|
110
|
-
declare function CookieConsent({ manageDefault, cookieName, company, logo, privacyUrl, termsUrl, locale, defaultOpen, defaultTab, colors, categories, labels, onChange, autoClearCookies, }: CookieConsentProps): react.JSX.Element;
|
|
160
|
+
declare function CookieConsent({ manageDefault, cookieName, company, logo, privacyUrl, termsUrl, locale, defaultOpen, defaultTab, theme, colors, categories, labels, primaryAction, deferOpen, fontFamily, onChange, autoClearCookies, }: CookieConsentProps): react.JSX.Element;
|
|
111
161
|
|
|
112
162
|
/** Localised name + descriptions for one category. */
|
|
113
163
|
interface LocaleCategoryText {
|
|
@@ -154,4 +204,4 @@ declare const SUPPORTED_LOCALES: readonly ["en", "de", "fr", "es", "it", "pt", "
|
|
|
154
204
|
* Case-insensitive and region-tolerant: 'pt-BR' falls back to 'pt'. */
|
|
155
205
|
declare function resolveLocale(locale?: string): LocalePack | undefined;
|
|
156
206
|
|
|
157
|
-
export { type CategoriesConfig, type CategoryContent, type Choices, type ConsentLabels, CookieConsent, type CookieConsentProps, type CookieInfo, LOCALES, type LocaleCategoryText, type LocaleCookieText, type LocalePack, SUPPORTED_LOCALES, CookieConsent as default, resolveLocale };
|
|
207
|
+
export { type CategoriesConfig, type CategoryContent, type Choices, type ColorsProp, type ConsentLabels, CookieConsent, type CookieConsentProps, type CookieInfo, LOCALES, type LocaleCategoryText, type LocaleCookieText, type LocalePack, type LogoProp, type Palette, SUPPORTED_LOCALES, CookieConsent as default, resolveLocale };
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,45 @@ interface CategoryContent {
|
|
|
29
29
|
/** Cookies in this category (the count badge is derived from this list). */
|
|
30
30
|
cookies?: CookieInfo[];
|
|
31
31
|
}
|
|
32
|
+
/** A single theme's colour palette. Every key is optional; anything omitted falls
|
|
33
|
+
* back to the built-in default for the active theme. The last six ("overlay")
|
|
34
|
+
* tokens are derived from `text` via `color-mix` when omitted, so a light theme
|
|
35
|
+
* works from `brand`/`surface`/`text` alone — override them only for fine control. */
|
|
36
|
+
interface Palette {
|
|
37
|
+
brand?: string;
|
|
38
|
+
brandDeep?: string;
|
|
39
|
+
surface?: string;
|
|
40
|
+
surfaceAlt?: string;
|
|
41
|
+
text?: string;
|
|
42
|
+
textMuted?: string;
|
|
43
|
+
backdrop?: string;
|
|
44
|
+
/** Text/icon colour on top of `brand` (primary button, toggle knob, FAB icon). */
|
|
45
|
+
onBrand?: string;
|
|
46
|
+
/** Hairlines and button borders. Default: `color-mix` of `text`. */
|
|
47
|
+
border?: string;
|
|
48
|
+
/** Button hover fill. Default: `color-mix` of `text`. */
|
|
49
|
+
hover?: string;
|
|
50
|
+
/** Toggle track when off. Default: `color-mix` of `text`. */
|
|
51
|
+
trackOff?: string;
|
|
52
|
+
/** Count-badge background. Default: `color-mix` of `text`. */
|
|
53
|
+
badgeBg?: string;
|
|
54
|
+
/** Cookie-row background. Default: `color-mix` of `text`. */
|
|
55
|
+
cookieBg?: string;
|
|
56
|
+
/** Policy-link colour on the About tab. Default: `brand`. */
|
|
57
|
+
link?: string;
|
|
58
|
+
}
|
|
59
|
+
/** The `colors` prop. Either one palette (pinned to both themes — back-compatible
|
|
60
|
+
* with v1) or a `{ light, dark }` pair the component switches between by theme. */
|
|
61
|
+
type ColorsProp = Palette | {
|
|
62
|
+
light?: Palette;
|
|
63
|
+
dark?: Palette;
|
|
64
|
+
};
|
|
65
|
+
/** A brand mark. Either one node (both themes) or a `{ light, dark }` pair the
|
|
66
|
+
* component swaps by the active theme — so the logo follows light/dark too. */
|
|
67
|
+
type LogoProp = ReactNode | {
|
|
68
|
+
light?: ReactNode;
|
|
69
|
+
dark?: ReactNode;
|
|
70
|
+
};
|
|
32
71
|
/** Per-category overrides. The four keys are fixed (they map to Consent Mode signals). */
|
|
33
72
|
interface CategoriesConfig {
|
|
34
73
|
necessary?: CategoryContent;
|
|
@@ -69,8 +108,9 @@ interface CookieConsentProps {
|
|
|
69
108
|
cookieName?: string;
|
|
70
109
|
/** Operator / brand name shown in the header and About panel. */
|
|
71
110
|
company?: string;
|
|
72
|
-
/** Optional brand mark for the header (
|
|
73
|
-
|
|
111
|
+
/** Optional brand mark for the header (an <svg/> or <img/>). Pass a single node,
|
|
112
|
+
* or `{ light, dark }` to swap the mark by theme. */
|
|
113
|
+
logo?: LogoProp;
|
|
74
114
|
privacyUrl?: string;
|
|
75
115
|
termsUrl?: string;
|
|
76
116
|
/** Built-in language pack to use (e.g. 'de', 'pl'). English is the default and
|
|
@@ -82,23 +122,33 @@ interface CookieConsentProps {
|
|
|
82
122
|
defaultOpen?: boolean;
|
|
83
123
|
/** Tab to show first. Default: 'consent'. */
|
|
84
124
|
defaultTab?: 'consent' | 'details' | 'about';
|
|
85
|
-
/**
|
|
86
|
-
*
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
backdrop?: string;
|
|
95
|
-
/** Text/icon colour on top of `brand` (primary button). Default: `surface`. */
|
|
96
|
-
onBrand?: string;
|
|
97
|
-
};
|
|
125
|
+
/** Which theme to render. `'auto'` (default) follows the OS `prefers-color-scheme`
|
|
126
|
+
* and re-renders when it changes; `'light'`/`'dark'` pin it. Only affects which
|
|
127
|
+
* half of a `{ light, dark }` `colors`/`logo` is used — a single palette applies
|
|
128
|
+
* to both themes. */
|
|
129
|
+
theme?: 'auto' | 'light' | 'dark';
|
|
130
|
+
/** Brand palette. Either one palette (applied to both themes) or `{ light, dark }`.
|
|
131
|
+
* Built-in defaults cover both themes; a light theme works from `brand`/`surface`/
|
|
132
|
+
* `text` alone (the overlay tokens are derived from `text`). */
|
|
133
|
+
colors?: ColorsProp;
|
|
98
134
|
/** Per-category content overrides (names, descriptions, cookie tables). */
|
|
99
135
|
categories?: CategoriesConfig;
|
|
100
136
|
/** UI string overrides for localisation. */
|
|
101
137
|
labels?: ConsentLabels;
|
|
138
|
+
/** Which action button gets primary (filled/brand) emphasis.
|
|
139
|
+
* - `'save'` (default): "Save choices" is emphasised only once the visitor turns
|
|
140
|
+
* an optional category on — Accept/Deny stay equal-weight otherwise, the
|
|
141
|
+
* GDPR-friendly default (regulators expect no dark patterns).
|
|
142
|
+
* - `'allowAll'`: "Allow all" is always emphasised (familiar consumer look).
|
|
143
|
+
* - `'none'`: no button is ever emphasised. */
|
|
144
|
+
primaryAction?: 'save' | 'allowAll' | 'none';
|
|
145
|
+
/** Defer the first-visit auto-open until the browser is idle (double-rAF +
|
|
146
|
+
* `requestIdleCallback`, capped ~600ms). Keeps the full-screen card from being
|
|
147
|
+
* the only thing painted on first frame, which otherwise starves Lighthouse of
|
|
148
|
+
* an LCP candidate (`NO_LCP`). Ignored when `defaultOpen`. Default: true. */
|
|
149
|
+
deferOpen?: boolean;
|
|
150
|
+
/** Override the font stack. Default: the system UI stack. */
|
|
151
|
+
fontFamily?: string;
|
|
102
152
|
/** Fired after the user makes (or changes) a choice. */
|
|
103
153
|
onChange?: (choices: Choices) => void;
|
|
104
154
|
/** When a category is denied or revoked, delete the cookies declared in its
|
|
@@ -107,7 +157,7 @@ interface CookieConsentProps {
|
|
|
107
157
|
autoClearCookies?: boolean;
|
|
108
158
|
}
|
|
109
159
|
|
|
110
|
-
declare function CookieConsent({ manageDefault, cookieName, company, logo, privacyUrl, termsUrl, locale, defaultOpen, defaultTab, colors, categories, labels, onChange, autoClearCookies, }: CookieConsentProps): react.JSX.Element;
|
|
160
|
+
declare function CookieConsent({ manageDefault, cookieName, company, logo, privacyUrl, termsUrl, locale, defaultOpen, defaultTab, theme, colors, categories, labels, primaryAction, deferOpen, fontFamily, onChange, autoClearCookies, }: CookieConsentProps): react.JSX.Element;
|
|
111
161
|
|
|
112
162
|
/** Localised name + descriptions for one category. */
|
|
113
163
|
interface LocaleCategoryText {
|
|
@@ -154,4 +204,4 @@ declare const SUPPORTED_LOCALES: readonly ["en", "de", "fr", "es", "it", "pt", "
|
|
|
154
204
|
* Case-insensitive and region-tolerant: 'pt-BR' falls back to 'pt'. */
|
|
155
205
|
declare function resolveLocale(locale?: string): LocalePack | undefined;
|
|
156
206
|
|
|
157
|
-
export { type CategoriesConfig, type CategoryContent, type Choices, type ConsentLabels, CookieConsent, type CookieConsentProps, type CookieInfo, LOCALES, type LocaleCategoryText, type LocaleCookieText, type LocalePack, SUPPORTED_LOCALES, CookieConsent as default, resolveLocale };
|
|
207
|
+
export { type CategoriesConfig, type CategoryContent, type Choices, type ColorsProp, type ConsentLabels, CookieConsent, type CookieConsentProps, type CookieInfo, LOCALES, type LocaleCategoryText, type LocaleCookieText, type LocalePack, type LogoProp, type Palette, SUPPORTED_LOCALES, CookieConsent as default, resolveLocale };
|