@uninspired/cookie-banner 0.0.12 → 0.0.13
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 +202 -0
- package/dist/react/index.cjs +89 -42
- package/dist/react/index.d.ts +11 -15
- package/dist/react/index.js +90 -43
- package/dist/script/index.cjs +16 -16
- package/dist/script/index.d.ts +10 -14
- package/dist/script/index.js +1154 -1117
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Cookie Banner
|
|
2
|
+
|
|
3
|
+
Cookie banner for all Uninspired products. Available as React component or HTML embed script.
|
|
4
|
+
|
|
5
|
+
## Theming
|
|
6
|
+
|
|
7
|
+
Default theme:
|
|
8
|
+
|
|
9
|
+
```css
|
|
10
|
+
:root {
|
|
11
|
+
/* Colors */
|
|
12
|
+
/* Base */
|
|
13
|
+
--cb-color-neutral-0: rgb(255, 255, 255);
|
|
14
|
+
--cb-color-neutral-50: rgb(250, 250, 250);
|
|
15
|
+
--cb-color-neutral-100: rgb(245, 245, 245);
|
|
16
|
+
--cb-color-neutral-200: rgb(229, 229, 229);
|
|
17
|
+
--cb-color-neutral-300: rgb(212, 212, 212);
|
|
18
|
+
--cb-color-neutral-400: rgb(163, 163, 163);
|
|
19
|
+
--cb-color-neutral-500: rgb(115, 115, 115);
|
|
20
|
+
--cb-color-neutral-600: rgb(82, 82, 82);
|
|
21
|
+
--cb-color-neutral-700: rgb(64, 64, 64);
|
|
22
|
+
--cb-color-neutral-800: rgb(38, 38, 38);
|
|
23
|
+
--cb-color-neutral-900: rgb(23, 23, 23);
|
|
24
|
+
--cb-color-neutral-950: rgb(10, 10, 10);
|
|
25
|
+
|
|
26
|
+
--cb-color-brand-50: rgb(236, 253, 245);
|
|
27
|
+
--cb-color-brand-100: rgb(209, 250, 229);
|
|
28
|
+
--cb-color-brand-200: rgb(167, 243, 208);
|
|
29
|
+
--cb-color-brand-300: rgb(110, 231, 183);
|
|
30
|
+
--cb-color-brand-400: rgb(52, 211, 153);
|
|
31
|
+
--cb-color-brand-500: rgb(16, 185, 129);
|
|
32
|
+
--cb-color-brand-600: rgb(5, 150, 105);
|
|
33
|
+
--cb-color-brand-700: rgb(4, 120, 87);
|
|
34
|
+
--cb-color-brand-800: rgb(6, 95, 70);
|
|
35
|
+
--cb-color-brand-900: rgb(6, 78, 59);
|
|
36
|
+
|
|
37
|
+
--cb-space-base: 4px;
|
|
38
|
+
--cb-space-xs: calc(var(--cb-space-base) * 1);
|
|
39
|
+
--cb-space-sm: calc(var(--cb-space-base) * 2);
|
|
40
|
+
--cb-space-md: calc(var(--cb-space-base) * 4);
|
|
41
|
+
--cb-space-lg: calc(var(--cb-space-base) * 6);
|
|
42
|
+
--cb-space-xl: calc(var(--cb-space-base) * 12);
|
|
43
|
+
|
|
44
|
+
/* Typography */
|
|
45
|
+
--cb-font-family-body: "Nunito Sans", sans-serif;
|
|
46
|
+
--cb-font-size-body: 14px;
|
|
47
|
+
--cb-font-weight-body-normal: 400;
|
|
48
|
+
--cb-font-weight-body-bold: 500;
|
|
49
|
+
--cb-line-height-body: 20px;
|
|
50
|
+
--cb-letter-spacing-body: 0em;
|
|
51
|
+
|
|
52
|
+
--cb-font-family-caption: "Nunito Sans", sans-serif;
|
|
53
|
+
--cb-font-size-caption: 12px;
|
|
54
|
+
--cb-font-weight-caption-normal: 400;
|
|
55
|
+
--cb-font-weight-caption-bold: 500;
|
|
56
|
+
--cb-line-height-caption: 16px;
|
|
57
|
+
--cb-letter-spacing-caption: 0em;
|
|
58
|
+
|
|
59
|
+
/* Radius */
|
|
60
|
+
--cb-radius-base: 6px;
|
|
61
|
+
--cb-radius-sm: calc(var(--cb-radius-base) * 1);
|
|
62
|
+
--cb-radius-md: calc(var(--cb-radius-base) * 2);
|
|
63
|
+
--cb-radius-lg: calc(var(--cb-radius-base) * 3);
|
|
64
|
+
|
|
65
|
+
/* Shadow */
|
|
66
|
+
--cb-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
|
67
|
+
--cb-shadow-md:
|
|
68
|
+
0 4px 16px -2px rgb(0 0 0 / 0.08), 0 2px 4px -1px rgb(0 0 0 / 0.08);
|
|
69
|
+
--cb-shadow-lg:
|
|
70
|
+
0 12px 32px -4px rgb(0 0 0 / 0.08), 0 4px 8px -2px rgb(0 0 0 / 0.08);
|
|
71
|
+
|
|
72
|
+
/* Sizes */
|
|
73
|
+
--cb-size-banner-max-width: 448px;
|
|
74
|
+
--cb-size-settings-max-height: 448px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@media (prefers-color-scheme: dark) {
|
|
78
|
+
:root {
|
|
79
|
+
--cb-color-neutral-0: rgb(10, 10, 10);
|
|
80
|
+
--cb-color-neutral-50: rgb(23, 23, 23);
|
|
81
|
+
--cb-color-neutral-100: rgb(38, 38, 38);
|
|
82
|
+
--cb-color-neutral-200: rgb(64, 64, 64);
|
|
83
|
+
--cb-color-neutral-300: rgb(82, 82, 82);
|
|
84
|
+
--cb-color-neutral-400: rgb(115, 115, 115);
|
|
85
|
+
--cb-color-neutral-500: rgb(163, 163, 163);
|
|
86
|
+
--cb-color-neutral-600: rgb(212, 212, 212);
|
|
87
|
+
--cb-color-neutral-700: rgb(229, 229, 229);
|
|
88
|
+
--cb-color-neutral-800: rgb(245, 245, 245);
|
|
89
|
+
--cb-color-neutral-900: rgb(250, 250, 250);
|
|
90
|
+
--cb-color-neutral-950: rgb(255, 255, 255);
|
|
91
|
+
|
|
92
|
+
--cb-color-brand-50: rgb(15, 41, 30);
|
|
93
|
+
--cb-color-brand-100: rgb(17, 49, 35);
|
|
94
|
+
--cb-color-brand-200: rgb(19, 57, 41);
|
|
95
|
+
--cb-color-brand-300: rgb(22, 68, 48);
|
|
96
|
+
--cb-color-brand-400: rgb(27, 84, 58);
|
|
97
|
+
--cb-color-brand-500: rgb(35, 110, 74);
|
|
98
|
+
--cb-color-brand-600: rgb(48, 164, 108);
|
|
99
|
+
--cb-color-brand-700: rgb(60, 177, 121);
|
|
100
|
+
--cb-color-brand-800: rgb(76, 195, 138);
|
|
101
|
+
--cb-color-brand-900: rgb(229, 251, 235);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## React component
|
|
107
|
+
|
|
108
|
+
Install
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install @uninspired/cookie-banner
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import "@uninspired/cookie-banner/react/style.css";
|
|
118
|
+
import "@uninspired/cookie-banner/react/theme.css";
|
|
119
|
+
import { Banner } from "@uninspired/cookie-banner/react";
|
|
120
|
+
|
|
121
|
+
function App() {
|
|
122
|
+
return <Banner noTarget items={[]} />;
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Embed script
|
|
127
|
+
|
|
128
|
+
Add this to the document head:
|
|
129
|
+
|
|
130
|
+
```html
|
|
131
|
+
<link
|
|
132
|
+
rel="stylesheet"
|
|
133
|
+
href="https://esm.sh/@uninspired/cookie-banner/script/theme.css"
|
|
134
|
+
/>
|
|
135
|
+
<link
|
|
136
|
+
rel="stylesheet"
|
|
137
|
+
href="https://esm.sh/@uninspired/cookie-banner/script/style.css"
|
|
138
|
+
/>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Add this to the end of the document body:
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<script type="module" async defer>
|
|
145
|
+
import { mountBanner } from "https://esm.sh/@uninspired/cookie-banner/script/index.js";
|
|
146
|
+
mountBanner({
|
|
147
|
+
cookie: {
|
|
148
|
+
domain: ".uninspired.studio",
|
|
149
|
+
sameSite: "strict",
|
|
150
|
+
secure: true,
|
|
151
|
+
},
|
|
152
|
+
items: [
|
|
153
|
+
{
|
|
154
|
+
value: "functional",
|
|
155
|
+
label: "Functional",
|
|
156
|
+
sublabel: "The site would break if you disabled these.",
|
|
157
|
+
required: true,
|
|
158
|
+
description: `<b>Authentication</b><br/>
|
|
159
|
+
<p>Keeps you logged into your account across sessions. These cookies are essential for the app to work correctly and cannot be disabled.</p>
|
|
160
|
+
<b>Payments</b><br/>
|
|
161
|
+
<p>Required to process purchases and manage your subscription. Used by Paddle to handle transactions, tax calculation, and order management. These cannot be disabled as they're essential to completing and maintaining purchases.</p><b>Referral Tracking</b><br/>
|
|
162
|
+
<p>Ensures visits and sign-ups via our referral program are attributed correctly. Used by Tolt to credit partners or affiliates when you arrive through a referral link.</p>`,
|
|
163
|
+
scripts: [],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
value: "analytics",
|
|
167
|
+
label: "Analytics",
|
|
168
|
+
sublabel: "Help us make our products better.",
|
|
169
|
+
defaultSelected: true,
|
|
170
|
+
description: `<b>Web & Product Analytics</b><br/>
|
|
171
|
+
<p>Helps us understand how visitors navigate the site and how customers interact with the product — including page views, feature usage, and session flows. Used by PostHog to improve the experience and inform what gets built next.</p>
|
|
172
|
+
<b>Error Tracking</b><br/>
|
|
173
|
+
<p>Captures technical errors and crashes to help us diagnose and resolve bugs quickly. Used by PostHog to collect error context without storing personally identifiable information.</p>`,
|
|
174
|
+
scripts: [
|
|
175
|
+
{
|
|
176
|
+
id: "log",
|
|
177
|
+
variant: "inline",
|
|
178
|
+
type: "text/javascript",
|
|
179
|
+
content: "console.log('analytics');",
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
value: "marketing",
|
|
185
|
+
label: "Marketing",
|
|
186
|
+
defaultSelected: true,
|
|
187
|
+
sublabel: "Oops, something broke!",
|
|
188
|
+
description: `<b>Conversion Tracking</b><br/>
|
|
189
|
+
<p>Tracks actions taken after interacting with our ads, such as visiting a page or starting a trial. Used by Meta Pixel to measure campaign effectiveness and avoid spending budget on underperforming ads.</p>`,
|
|
190
|
+
scripts: [
|
|
191
|
+
{
|
|
192
|
+
id: "log",
|
|
193
|
+
variant: "inline",
|
|
194
|
+
type: "text/javascript",
|
|
195
|
+
content: "console.log('marketing');",
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
});
|
|
201
|
+
</script>
|
|
202
|
+
```
|
package/dist/react/index.cjs
CHANGED
|
@@ -1951,6 +1951,7 @@ function optional(innerType) {
|
|
|
1951
1951
|
});
|
|
1952
1952
|
}
|
|
1953
1953
|
const remoteScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
1954
|
+
id: /* @__PURE__ */ string(),
|
|
1954
1955
|
variant: /* @__PURE__ */ literal("remote"),
|
|
1955
1956
|
type: /* @__PURE__ */ optional(/* @__PURE__ */ string()),
|
|
1956
1957
|
src: /* @__PURE__ */ string(),
|
|
@@ -1960,6 +1961,7 @@ const remoteScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
|
1960
1961
|
integrity: /* @__PURE__ */ optional(/* @__PURE__ */ string())
|
|
1961
1962
|
});
|
|
1962
1963
|
const inlineScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
1964
|
+
id: /* @__PURE__ */ string(),
|
|
1963
1965
|
variant: /* @__PURE__ */ literal("inline"),
|
|
1964
1966
|
type: /* @__PURE__ */ optional(/* @__PURE__ */ string()),
|
|
1965
1967
|
async: /* @__PURE__ */ optional(/* @__PURE__ */ boolean()),
|
|
@@ -1984,14 +1986,59 @@ const Switch = ({ label: label2, id, ...rest }) => {
|
|
|
1984
1986
|
] });
|
|
1985
1987
|
};
|
|
1986
1988
|
const selectionSchema = /* @__PURE__ */ record(/* @__PURE__ */ string(), /* @__PURE__ */ boolean());
|
|
1989
|
+
class Cookies {
|
|
1990
|
+
#domain;
|
|
1991
|
+
#prefix;
|
|
1992
|
+
#sameSite;
|
|
1993
|
+
#secure;
|
|
1994
|
+
constructor(options) {
|
|
1995
|
+
this.#domain = options.domain;
|
|
1996
|
+
this.#prefix = options.prefix ?? "us-cookie-banner-";
|
|
1997
|
+
this.#sameSite = options.sameSite ?? "lax";
|
|
1998
|
+
this.#secure = options.secure ?? true;
|
|
1999
|
+
}
|
|
2000
|
+
#serialize = (data) => {
|
|
2001
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(data)));
|
|
2002
|
+
};
|
|
2003
|
+
#deserialize = (data) => {
|
|
2004
|
+
return new TextDecoder().decode(
|
|
2005
|
+
Uint8Array.from(atob(data), (c) => c.charCodeAt(0))
|
|
2006
|
+
);
|
|
2007
|
+
};
|
|
2008
|
+
set = (key, value, ttl = 365) => {
|
|
2009
|
+
const expires = new Date(Date.now() + ttl * 24 * 60 * 60 * 1e3);
|
|
2010
|
+
const cookie = `${this.#prefix}${key}=${this.#serialize(value)}; expires=${expires}; path=/; domain=${this.#domain}; SameSite=${this.#sameSite}; ${this.#secure ? "secure" : ""}`;
|
|
2011
|
+
document.cookie = cookie;
|
|
2012
|
+
};
|
|
2013
|
+
get = (key) => {
|
|
2014
|
+
const match = document.cookie.match(
|
|
2015
|
+
new RegExp(`(?:^|; )${this.#prefix}${key}=([^;]*)`)
|
|
2016
|
+
);
|
|
2017
|
+
if (!match) return null;
|
|
2018
|
+
const [_, serializedValue] = match;
|
|
2019
|
+
if (!serializedValue) return null;
|
|
2020
|
+
return this.#deserialize(serializedValue);
|
|
2021
|
+
};
|
|
2022
|
+
remove = (key) => {
|
|
2023
|
+
this.set(key, "", -1);
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
const SELECTION_KEY = "selection";
|
|
1987
2027
|
const SelectionContext = require$$0.createContext(null);
|
|
1988
2028
|
const SelectionProvider = ({
|
|
1989
2029
|
children,
|
|
1990
2030
|
items,
|
|
1991
|
-
|
|
2031
|
+
cookie
|
|
1992
2032
|
}) => {
|
|
1993
|
-
|
|
1994
|
-
|
|
2033
|
+
const { domain, prefix, sameSite, secure } = cookie;
|
|
2034
|
+
const [cookies, setCookies] = require$$0.useState(
|
|
2035
|
+
new Cookies({ domain, prefix, sameSite, secure })
|
|
2036
|
+
);
|
|
2037
|
+
require$$0.useEffect(() => {
|
|
2038
|
+
setCookies(new Cookies({ domain, prefix, sameSite, secure }));
|
|
2039
|
+
}, [domain, prefix, sameSite, secure]);
|
|
2040
|
+
function getCookieSelection() {
|
|
2041
|
+
const storedSelection = cookies.get(SELECTION_KEY);
|
|
1995
2042
|
if (!storedSelection) return null;
|
|
1996
2043
|
const { success, data } = selectionSchema.safeParse(
|
|
1997
2044
|
JSON.parse(storedSelection)
|
|
@@ -2006,10 +2053,10 @@ const SelectionProvider = ({
|
|
|
2006
2053
|
[items]
|
|
2007
2054
|
);
|
|
2008
2055
|
const [selection, setSelection] = require$$0.useState(
|
|
2009
|
-
|
|
2056
|
+
getCookieSelection() ?? defaultSelection
|
|
2010
2057
|
);
|
|
2011
2058
|
const [savedSelection, setSavedSelection] = require$$0.useState(
|
|
2012
|
-
|
|
2059
|
+
getCookieSelection()
|
|
2013
2060
|
);
|
|
2014
2061
|
function toggleSelection(key, value) {
|
|
2015
2062
|
const newSelection = selection ? { ...selection, [key]: value } : { [key]: value };
|
|
@@ -2024,47 +2071,51 @@ const SelectionProvider = ({
|
|
|
2024
2071
|
(item) => selectedValues.includes(item.value)
|
|
2025
2072
|
);
|
|
2026
2073
|
for (const item of items) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2074
|
+
if (!item.scripts) continue;
|
|
2075
|
+
for (const script of item.scripts) {
|
|
2076
|
+
const elem = document.head.querySelector(`#${item.value}-${script.id}`);
|
|
2077
|
+
if (!elem) continue;
|
|
2078
|
+
elem.remove();
|
|
2079
|
+
}
|
|
2030
2080
|
}
|
|
2031
2081
|
for (const item of mountedItems) {
|
|
2032
|
-
|
|
2033
|
-
elem.id = item.value;
|
|
2034
|
-
if (!item.script) {
|
|
2082
|
+
if (!item.scripts) {
|
|
2035
2083
|
if (!item.required)
|
|
2036
2084
|
console.warn("CookieBanner: Missing script for", item.value);
|
|
2037
2085
|
continue;
|
|
2038
2086
|
}
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2087
|
+
for (const script of item.scripts) {
|
|
2088
|
+
if (!script) continue;
|
|
2089
|
+
const elem = document.createElement("script");
|
|
2090
|
+
elem.id = `${item.value}-${script.id}`;
|
|
2091
|
+
const { success, data } = scriptDefinitionSchema.safeParse(script);
|
|
2092
|
+
if (!success) {
|
|
2093
|
+
console.error(
|
|
2094
|
+
"CookieBanner: Invalid script definition for",
|
|
2095
|
+
item.value
|
|
2096
|
+
);
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
elem.type = data.type ?? "text/javascript";
|
|
2100
|
+
elem.async = data.async ?? false;
|
|
2101
|
+
elem.defer = data.defer ?? false;
|
|
2102
|
+
switch (data.variant) {
|
|
2103
|
+
case "inline":
|
|
2104
|
+
elem.textContent = data.content;
|
|
2105
|
+
break;
|
|
2106
|
+
case "remote":
|
|
2107
|
+
elem.src = data.src;
|
|
2108
|
+
elem.crossOrigin = data.crossorigin ?? null;
|
|
2109
|
+
elem.integrity = data.integrity ?? "";
|
|
2110
|
+
break;
|
|
2111
|
+
}
|
|
2112
|
+
document.head.appendChild(elem);
|
|
2048
2113
|
}
|
|
2049
|
-
elem.type = script.type ?? "text/javascript";
|
|
2050
|
-
elem.async = script.async ?? false;
|
|
2051
|
-
elem.defer = script.defer ?? false;
|
|
2052
|
-
switch (script.variant) {
|
|
2053
|
-
case "inline":
|
|
2054
|
-
elem.textContent = script.content;
|
|
2055
|
-
break;
|
|
2056
|
-
case "remote":
|
|
2057
|
-
elem.src = script.src;
|
|
2058
|
-
elem.crossOrigin = script.crossorigin ?? null;
|
|
2059
|
-
elem.integrity = script.integrity ?? "";
|
|
2060
|
-
break;
|
|
2061
|
-
}
|
|
2062
|
-
document.head.appendChild(elem);
|
|
2063
2114
|
}
|
|
2064
2115
|
}, [savedSelection, items]);
|
|
2065
2116
|
function saveSelection(newSelection) {
|
|
2066
2117
|
setSavedSelection(newSelection);
|
|
2067
|
-
|
|
2118
|
+
cookies.set(SELECTION_KEY, JSON.stringify(newSelection));
|
|
2068
2119
|
}
|
|
2069
2120
|
const onDeclineAll = require$$0.useCallback(() => {
|
|
2070
2121
|
const newSelection = Object.fromEntries(
|
|
@@ -2246,20 +2297,16 @@ const BannerContent = ({
|
|
|
2246
2297
|
}
|
|
2247
2298
|
) });
|
|
2248
2299
|
};
|
|
2249
|
-
const Banner = ({
|
|
2250
|
-
localStorageKey = "cb-selection",
|
|
2251
|
-
items,
|
|
2252
|
-
...rest
|
|
2253
|
-
}) => {
|
|
2300
|
+
const Banner = ({ cookie, items, ...rest }) => {
|
|
2254
2301
|
const selectionItems = require$$0.useMemo(
|
|
2255
2302
|
() => items.filter((item) => !item.required).map((item) => ({
|
|
2256
2303
|
value: item.value,
|
|
2257
|
-
|
|
2304
|
+
scripts: item.scripts,
|
|
2258
2305
|
defaultSelected: item.defaultSelected,
|
|
2259
2306
|
required: item.required
|
|
2260
2307
|
})),
|
|
2261
2308
|
[items]
|
|
2262
2309
|
);
|
|
2263
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionProvider, { items: selectionItems,
|
|
2310
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionProvider, { items: selectionItems, cookie, children: /* @__PURE__ */ jsxRuntimeExports.jsx(BannerContent, { items, cookie, ...rest }) });
|
|
2264
2311
|
};
|
|
2265
2312
|
exports.Banner = Banner;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,31 +1,25 @@
|
|
|
1
1
|
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
|
2
2
|
import * as z from 'zod/mini';
|
|
3
3
|
|
|
4
|
-
export declare const Banner: ({
|
|
4
|
+
export declare const Banner: ({ cookie, items, ...rest }: BannerProps) => JSX_2.Element;
|
|
5
5
|
|
|
6
|
-
declare
|
|
7
|
-
|
|
8
|
-
declare interface BannerItemOptionsBase {
|
|
6
|
+
declare interface BannerItemOptions {
|
|
9
7
|
value: string;
|
|
10
8
|
label: string;
|
|
11
9
|
defaultSelected?: boolean;
|
|
12
10
|
sublabel?: string;
|
|
13
11
|
required?: boolean;
|
|
14
12
|
description?: string;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
declare interface BannerItemOptionsOptional extends BannerItemOptionsBase {
|
|
18
|
-
required: false;
|
|
19
|
-
script: ScriptDefinition;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
declare interface BannerItemOptionsRequired extends BannerItemOptionsBase {
|
|
23
|
-
required: true;
|
|
24
|
-
script: undefined;
|
|
13
|
+
scripts: ScriptDefinition[];
|
|
25
14
|
}
|
|
26
15
|
|
|
27
16
|
declare interface BannerOptions {
|
|
28
|
-
|
|
17
|
+
cookie: {
|
|
18
|
+
domain: string;
|
|
19
|
+
prefix?: string;
|
|
20
|
+
sameSite?: "lax" | "strict" | "none";
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
};
|
|
29
23
|
heading?: string;
|
|
30
24
|
subheading?: string;
|
|
31
25
|
selectLabel?: string;
|
|
@@ -47,6 +41,7 @@ export declare interface BannerProps extends BannerOptions {
|
|
|
47
41
|
declare type ScriptDefinition = z.infer<typeof scriptDefinitionSchema>;
|
|
48
42
|
|
|
49
43
|
declare const scriptDefinitionSchema: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObject<{
|
|
44
|
+
id: z.ZodMiniString<string>;
|
|
50
45
|
variant: z.ZodMiniLiteral<"remote">;
|
|
51
46
|
type: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
52
47
|
src: z.ZodMiniString<string>;
|
|
@@ -55,6 +50,7 @@ declare const scriptDefinitionSchema: z.ZodMiniDiscriminatedUnion<[z.ZodMiniObje
|
|
|
55
50
|
crossorigin: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
56
51
|
integrity: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
57
52
|
}, z.core.$strip>, z.ZodMiniObject<{
|
|
53
|
+
id: z.ZodMiniString<string>;
|
|
58
54
|
variant: z.ZodMiniLiteral<"inline">;
|
|
59
55
|
type: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
60
56
|
async: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
package/dist/react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import require$$0, {
|
|
1
|
+
import require$$0, { useState, useEffect, useMemo, useCallback, createContext, useContext } from "react";
|
|
2
2
|
import { Item, Trigger, Content, Root as Root$3 } from "@radix-ui/react-accordion";
|
|
3
3
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
4
4
|
import { Root as Root$1, Content as Content$1, Title, Description, Close } from "@radix-ui/react-dialog";
|
|
@@ -1949,6 +1949,7 @@ function optional(innerType) {
|
|
|
1949
1949
|
});
|
|
1950
1950
|
}
|
|
1951
1951
|
const remoteScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
1952
|
+
id: /* @__PURE__ */ string(),
|
|
1952
1953
|
variant: /* @__PURE__ */ literal("remote"),
|
|
1953
1954
|
type: /* @__PURE__ */ optional(/* @__PURE__ */ string()),
|
|
1954
1955
|
src: /* @__PURE__ */ string(),
|
|
@@ -1958,6 +1959,7 @@ const remoteScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
|
1958
1959
|
integrity: /* @__PURE__ */ optional(/* @__PURE__ */ string())
|
|
1959
1960
|
});
|
|
1960
1961
|
const inlineScriptDefinitionSchema = /* @__PURE__ */ object({
|
|
1962
|
+
id: /* @__PURE__ */ string(),
|
|
1961
1963
|
variant: /* @__PURE__ */ literal("inline"),
|
|
1962
1964
|
type: /* @__PURE__ */ optional(/* @__PURE__ */ string()),
|
|
1963
1965
|
async: /* @__PURE__ */ optional(/* @__PURE__ */ boolean()),
|
|
@@ -1982,14 +1984,59 @@ const Switch = ({ label: label2, id, ...rest }) => {
|
|
|
1982
1984
|
] });
|
|
1983
1985
|
};
|
|
1984
1986
|
const selectionSchema = /* @__PURE__ */ record(/* @__PURE__ */ string(), /* @__PURE__ */ boolean());
|
|
1987
|
+
class Cookies {
|
|
1988
|
+
#domain;
|
|
1989
|
+
#prefix;
|
|
1990
|
+
#sameSite;
|
|
1991
|
+
#secure;
|
|
1992
|
+
constructor(options) {
|
|
1993
|
+
this.#domain = options.domain;
|
|
1994
|
+
this.#prefix = options.prefix ?? "us-cookie-banner-";
|
|
1995
|
+
this.#sameSite = options.sameSite ?? "lax";
|
|
1996
|
+
this.#secure = options.secure ?? true;
|
|
1997
|
+
}
|
|
1998
|
+
#serialize = (data) => {
|
|
1999
|
+
return btoa(String.fromCharCode(...new TextEncoder().encode(data)));
|
|
2000
|
+
};
|
|
2001
|
+
#deserialize = (data) => {
|
|
2002
|
+
return new TextDecoder().decode(
|
|
2003
|
+
Uint8Array.from(atob(data), (c) => c.charCodeAt(0))
|
|
2004
|
+
);
|
|
2005
|
+
};
|
|
2006
|
+
set = (key, value, ttl = 365) => {
|
|
2007
|
+
const expires = new Date(Date.now() + ttl * 24 * 60 * 60 * 1e3);
|
|
2008
|
+
const cookie = `${this.#prefix}${key}=${this.#serialize(value)}; expires=${expires}; path=/; domain=${this.#domain}; SameSite=${this.#sameSite}; ${this.#secure ? "secure" : ""}`;
|
|
2009
|
+
document.cookie = cookie;
|
|
2010
|
+
};
|
|
2011
|
+
get = (key) => {
|
|
2012
|
+
const match = document.cookie.match(
|
|
2013
|
+
new RegExp(`(?:^|; )${this.#prefix}${key}=([^;]*)`)
|
|
2014
|
+
);
|
|
2015
|
+
if (!match) return null;
|
|
2016
|
+
const [_, serializedValue] = match;
|
|
2017
|
+
if (!serializedValue) return null;
|
|
2018
|
+
return this.#deserialize(serializedValue);
|
|
2019
|
+
};
|
|
2020
|
+
remove = (key) => {
|
|
2021
|
+
this.set(key, "", -1);
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
const SELECTION_KEY = "selection";
|
|
1985
2025
|
const SelectionContext = createContext(null);
|
|
1986
2026
|
const SelectionProvider = ({
|
|
1987
2027
|
children,
|
|
1988
2028
|
items,
|
|
1989
|
-
|
|
2029
|
+
cookie
|
|
1990
2030
|
}) => {
|
|
1991
|
-
|
|
1992
|
-
|
|
2031
|
+
const { domain, prefix, sameSite, secure } = cookie;
|
|
2032
|
+
const [cookies, setCookies] = useState(
|
|
2033
|
+
new Cookies({ domain, prefix, sameSite, secure })
|
|
2034
|
+
);
|
|
2035
|
+
useEffect(() => {
|
|
2036
|
+
setCookies(new Cookies({ domain, prefix, sameSite, secure }));
|
|
2037
|
+
}, [domain, prefix, sameSite, secure]);
|
|
2038
|
+
function getCookieSelection() {
|
|
2039
|
+
const storedSelection = cookies.get(SELECTION_KEY);
|
|
1993
2040
|
if (!storedSelection) return null;
|
|
1994
2041
|
const { success, data } = selectionSchema.safeParse(
|
|
1995
2042
|
JSON.parse(storedSelection)
|
|
@@ -2004,10 +2051,10 @@ const SelectionProvider = ({
|
|
|
2004
2051
|
[items]
|
|
2005
2052
|
);
|
|
2006
2053
|
const [selection, setSelection] = useState(
|
|
2007
|
-
|
|
2054
|
+
getCookieSelection() ?? defaultSelection
|
|
2008
2055
|
);
|
|
2009
2056
|
const [savedSelection, setSavedSelection] = useState(
|
|
2010
|
-
|
|
2057
|
+
getCookieSelection()
|
|
2011
2058
|
);
|
|
2012
2059
|
function toggleSelection(key, value) {
|
|
2013
2060
|
const newSelection = selection ? { ...selection, [key]: value } : { [key]: value };
|
|
@@ -2022,47 +2069,51 @@ const SelectionProvider = ({
|
|
|
2022
2069
|
(item) => selectedValues.includes(item.value)
|
|
2023
2070
|
);
|
|
2024
2071
|
for (const item of items) {
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2072
|
+
if (!item.scripts) continue;
|
|
2073
|
+
for (const script of item.scripts) {
|
|
2074
|
+
const elem = document.head.querySelector(`#${item.value}-${script.id}`);
|
|
2075
|
+
if (!elem) continue;
|
|
2076
|
+
elem.remove();
|
|
2077
|
+
}
|
|
2028
2078
|
}
|
|
2029
2079
|
for (const item of mountedItems) {
|
|
2030
|
-
|
|
2031
|
-
elem.id = item.value;
|
|
2032
|
-
if (!item.script) {
|
|
2080
|
+
if (!item.scripts) {
|
|
2033
2081
|
if (!item.required)
|
|
2034
2082
|
console.warn("CookieBanner: Missing script for", item.value);
|
|
2035
2083
|
continue;
|
|
2036
2084
|
}
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2085
|
+
for (const script of item.scripts) {
|
|
2086
|
+
if (!script) continue;
|
|
2087
|
+
const elem = document.createElement("script");
|
|
2088
|
+
elem.id = `${item.value}-${script.id}`;
|
|
2089
|
+
const { success, data } = scriptDefinitionSchema.safeParse(script);
|
|
2090
|
+
if (!success) {
|
|
2091
|
+
console.error(
|
|
2092
|
+
"CookieBanner: Invalid script definition for",
|
|
2093
|
+
item.value
|
|
2094
|
+
);
|
|
2095
|
+
continue;
|
|
2096
|
+
}
|
|
2097
|
+
elem.type = data.type ?? "text/javascript";
|
|
2098
|
+
elem.async = data.async ?? false;
|
|
2099
|
+
elem.defer = data.defer ?? false;
|
|
2100
|
+
switch (data.variant) {
|
|
2101
|
+
case "inline":
|
|
2102
|
+
elem.textContent = data.content;
|
|
2103
|
+
break;
|
|
2104
|
+
case "remote":
|
|
2105
|
+
elem.src = data.src;
|
|
2106
|
+
elem.crossOrigin = data.crossorigin ?? null;
|
|
2107
|
+
elem.integrity = data.integrity ?? "";
|
|
2108
|
+
break;
|
|
2109
|
+
}
|
|
2110
|
+
document.head.appendChild(elem);
|
|
2046
2111
|
}
|
|
2047
|
-
elem.type = script.type ?? "text/javascript";
|
|
2048
|
-
elem.async = script.async ?? false;
|
|
2049
|
-
elem.defer = script.defer ?? false;
|
|
2050
|
-
switch (script.variant) {
|
|
2051
|
-
case "inline":
|
|
2052
|
-
elem.textContent = script.content;
|
|
2053
|
-
break;
|
|
2054
|
-
case "remote":
|
|
2055
|
-
elem.src = script.src;
|
|
2056
|
-
elem.crossOrigin = script.crossorigin ?? null;
|
|
2057
|
-
elem.integrity = script.integrity ?? "";
|
|
2058
|
-
break;
|
|
2059
|
-
}
|
|
2060
|
-
document.head.appendChild(elem);
|
|
2061
2112
|
}
|
|
2062
2113
|
}, [savedSelection, items]);
|
|
2063
2114
|
function saveSelection(newSelection) {
|
|
2064
2115
|
setSavedSelection(newSelection);
|
|
2065
|
-
|
|
2116
|
+
cookies.set(SELECTION_KEY, JSON.stringify(newSelection));
|
|
2066
2117
|
}
|
|
2067
2118
|
const onDeclineAll = useCallback(() => {
|
|
2068
2119
|
const newSelection = Object.fromEntries(
|
|
@@ -2244,21 +2295,17 @@ const BannerContent = ({
|
|
|
2244
2295
|
}
|
|
2245
2296
|
) });
|
|
2246
2297
|
};
|
|
2247
|
-
const Banner = ({
|
|
2248
|
-
localStorageKey = "cb-selection",
|
|
2249
|
-
items,
|
|
2250
|
-
...rest
|
|
2251
|
-
}) => {
|
|
2298
|
+
const Banner = ({ cookie, items, ...rest }) => {
|
|
2252
2299
|
const selectionItems = useMemo(
|
|
2253
2300
|
() => items.filter((item) => !item.required).map((item) => ({
|
|
2254
2301
|
value: item.value,
|
|
2255
|
-
|
|
2302
|
+
scripts: item.scripts,
|
|
2256
2303
|
defaultSelected: item.defaultSelected,
|
|
2257
2304
|
required: item.required
|
|
2258
2305
|
})),
|
|
2259
2306
|
[items]
|
|
2260
2307
|
);
|
|
2261
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionProvider, { items: selectionItems,
|
|
2308
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(SelectionProvider, { items: selectionItems, cookie, children: /* @__PURE__ */ jsxRuntimeExports.jsx(BannerContent, { items, cookie, ...rest }) });
|
|
2262
2309
|
};
|
|
2263
2310
|
export {
|
|
2264
2311
|
Banner
|