@c15t/react 2.0.0-rc.10 → 2.0.0-rc.12
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/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist-types/components/consent-dialog/atoms/card.d.ts +0 -1
- package/dist-types/version.d.ts +1 -1
- package/docs/building-headless-components.md +118 -16
- package/docs/components/consent-banner.md +1 -30
- package/docs/components/consent-widget.md +1 -28
- package/docs/hooks/use-consent-manager/overview.md +1 -1
- package/docs/iab/consent-banner.md +6 -4
- package/docs/iab/consent-dialog.md +6 -4
- package/docs/iab/overview.md +8 -7
- package/docs/iab/use-gvl-data.md +9 -197
- package/docs/optimization.md +35 -1
- package/docs/quickstart.md +11 -8
- package/package.json +2 -2
package/dist/version.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{version:()=>version});const version="2.0.0-rc.
|
|
1
|
+
"use strict";const __rslib_import_meta_url__="u"<typeof document?new(require("url".replace("",""))).URL("file:"+__filename).href:document.currentScript&&document.currentScript.src||new URL("main.js",document.baseURI).href;var __webpack_require__={};__webpack_require__.d=(e,_)=>{for(var r in _)__webpack_require__.o(_,r)&&!__webpack_require__.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:_[r]})},__webpack_require__.o=(e,_)=>Object.prototype.hasOwnProperty.call(e,_),__webpack_require__.r=e=>{"u">typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__={};__webpack_require__.r(__webpack_exports__),__webpack_require__.d(__webpack_exports__,{version:()=>version});const version="2.0.0-rc.12";for(var __rspack_i in exports.version=__webpack_exports__.version,__webpack_exports__)-1===["version"].indexOf(__rspack_i)&&(exports[__rspack_i]=__webpack_exports__[__rspack_i]);Object.defineProperty(exports,"__esModule",{value:!0});
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
let e="2.0.0-rc.
|
|
1
|
+
let e="2.0.0-rc.12";export{e as version};
|
|
@@ -109,4 +109,3 @@ declare const Footer: import("react").ForwardRefExoticComponent<BoxProps & {
|
|
|
109
109
|
'data-testid'?: string;
|
|
110
110
|
} & import("react").RefAttributes<HTMLDivElement>>;
|
|
111
111
|
export { Card, ConsentCustomizationCard, ConsentDialogCard, ConsentDialogContent, ConsentDialogFooter, ConsentDialogHeader, ConsentDialogHeaderDescription, ConsentDialogHeaderTitle, Content, Footer, Header, HeaderDescription, HeaderTitle, };
|
|
112
|
-
ion, HeaderTitle, };
|
package/dist-types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "2.0.0-rc.
|
|
1
|
+
export declare const version = "2.0.0-rc.12";
|
|
@@ -4,11 +4,11 @@ description: Build policy-aware custom consent components in React using the hea
|
|
|
4
4
|
---
|
|
5
5
|
Building custom consent UI is easier now because c15t exposes multiple layers of policy-aware primitives instead of forcing you to reconstruct banner rules by hand.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Think of customization as a ladder:
|
|
8
8
|
|
|
9
9
|
* stock component props for the shortest path
|
|
10
|
-
* `ConsentBanner.PolicyActions` and `ConsentWidget.PolicyActions`
|
|
11
|
-
* `useHeadlessConsentUI()`
|
|
10
|
+
* `ConsentBanner.PolicyActions` and `ConsentWidget.PolicyActions` when you want custom structure but still want c15t to resolve policy-aware actions
|
|
11
|
+
* `useHeadlessConsentUI()` when you need fully manual action rendering, custom controls, or non-standard flow
|
|
12
12
|
|
|
13
13
|
> ⚠️ **Warning:**
|
|
14
14
|
> Headless is the last step in the customization ladder. Use this guide only when pre-built components, tokens, slots, compound components, and noStyle are no longer sufficient.
|
|
@@ -26,6 +26,29 @@ The split is intentional: `@c15t/ui` owns pure policy-action resolution, while t
|
|
|
26
26
|
> ℹ️ **Info:**
|
|
27
27
|
> This guide is about building your own components while still respecting resolved policy-pack behavior. For the general headless overview, see Headless Mode.
|
|
28
28
|
|
|
29
|
+
## Choose the Smallest Layer That Solves the Job
|
|
30
|
+
|
|
31
|
+
Start with the smallest API surface that still gives you the behavior you need:
|
|
32
|
+
|
|
33
|
+
* Stay with stock components when you only need theming, spacing, copy, or legal-link changes
|
|
34
|
+
* Use `ConsentBanner.PolicyActions` or `ConsentWidget.PolicyActions` when you want a custom compound-component layout but still want grouped actions, ordering, and primary emphasis to come from policy
|
|
35
|
+
* Add `renderAction` when the grouping is still correct but you want to remap actions to stock c15t button compounds
|
|
36
|
+
* Reach for `useHeadlessConsentUI()` only when you need custom button elements, need to map `actionGroups` yourself, wire non-button controls, or coordinate the consent UI with a more custom state machine
|
|
37
|
+
|
|
38
|
+
This order matters because every step down the ladder gives you more control, but also makes it easier for your UI to drift away from the resolved policy if you stop using the provided state.
|
|
39
|
+
|
|
40
|
+
## Before You Build Headless UI
|
|
41
|
+
|
|
42
|
+
Do not use headless mode for problems that are still inside the stock component model:
|
|
43
|
+
|
|
44
|
+
* Use `layout`, `direction`, `primaryButton`, and `legalLinks` before you rebuild banner markup
|
|
45
|
+
* Use `theme.consentActions` before you swap out stock actions
|
|
46
|
+
* Use tokens such as `colors.surface` and `colors.surfaceHover` before raw CSS overrides
|
|
47
|
+
* Use slots such as `consentBannerCard`, `consentBannerFooter`, and `consentDialogCard` before compound components
|
|
48
|
+
* Use `ConsentManagerProvider.options.i18n` before rebuilding UI just to change text
|
|
49
|
+
|
|
50
|
+
A good rule: if the stock banner or dialog structure is still correct, you probably do not need headless mode.
|
|
51
|
+
|
|
29
52
|
## What the Headless Tooling Gives You
|
|
30
53
|
|
|
31
54
|
The main win is that your custom UI can stay aligned with policy packs without duplicating policy logic in your components.
|
|
@@ -40,9 +63,63 @@ The main win is that your custom UI can stay aligned with policy packs without d
|
|
|
40
63
|
* UI profile and scroll-lock hints
|
|
41
64
|
* whether the banner or dialog should currently be visible
|
|
42
65
|
|
|
66
|
+
The hook also gives you the policy-aware action helpers you are expected to call:
|
|
67
|
+
|
|
68
|
+
* `performBannerAction('accept' | 'reject')`
|
|
69
|
+
* `performDialogAction('accept' | 'reject')`
|
|
70
|
+
* `saveCustomPreferences()` for the dialog `customize` action
|
|
71
|
+
* `openDialog()`, `openBanner()`, and `closeUI()` for surface visibility
|
|
72
|
+
|
|
43
73
|
That means your component mostly focuses on markup and design-system concerns instead of re-implementing policy interpretation.
|
|
44
74
|
|
|
45
|
-
For most compound-component layouts, start with `ConsentBanner.PolicyActions` or `ConsentWidget.PolicyActions`. They render stock c15t buttons and translations by default, and `renderAction` is only needed when you want to override
|
|
75
|
+
For most compound-component layouts, start with `ConsentBanner.PolicyActions` or `ConsentWidget.PolicyActions`. They render stock c15t buttons and translations by default, and `renderAction` is only needed when you want to override which stock compound renders for each action. Reach for manual `actionGroups` mapping when you need action rendering that no longer fits the stock button compounds.
|
|
76
|
+
|
|
77
|
+
## Policy-Aware Compound Components First
|
|
78
|
+
|
|
79
|
+
If your goal is "custom layout, same policy behavior", start here before dropping to manual `actionGroups` rendering:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { ConsentBanner } from '@c15t/react';
|
|
83
|
+
|
|
84
|
+
export function BannerShell() {
|
|
85
|
+
return (
|
|
86
|
+
<ConsentBanner.Root>
|
|
87
|
+
<ConsentBanner.Card>
|
|
88
|
+
<ConsentBanner.Header>
|
|
89
|
+
<ConsentBanner.Title />
|
|
90
|
+
<ConsentBanner.Description />
|
|
91
|
+
</ConsentBanner.Header>
|
|
92
|
+
<ConsentBanner.PolicyActions />
|
|
93
|
+
</ConsentBanner.Card>
|
|
94
|
+
</ConsentBanner.Root>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `renderAction` only when you want to remap actions to stock button compounds while keeping the same policy-driven grouping and ordering:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { ConsentBanner } from '@c15t/react';
|
|
103
|
+
|
|
104
|
+
export function BannerActionsWithCustomMapping() {
|
|
105
|
+
return (
|
|
106
|
+
<ConsentBanner.PolicyActions
|
|
107
|
+
renderAction={(action, props) => {
|
|
108
|
+
const { key, ...buttonProps } = props;
|
|
109
|
+
|
|
110
|
+
switch (action) {
|
|
111
|
+
case 'accept':
|
|
112
|
+
return <ConsentBanner.AcceptButton key={key} {...buttonProps} />;
|
|
113
|
+
case 'reject':
|
|
114
|
+
return <ConsentBanner.RejectButton key={key} {...buttonProps} />;
|
|
115
|
+
case 'customize':
|
|
116
|
+
return <ConsentBanner.CustomizeButton key={key} {...buttonProps} />;
|
|
117
|
+
}
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
46
123
|
|
|
47
124
|
> ℹ️ **Info:**
|
|
48
125
|
> For custom layouts built from c15t compound components, prefer ConsentBanner.PolicyActions and ConsentWidget.PolicyActions. The examples below intentionally use manual actionGroups mapping to show the fully headless escape hatch.
|
|
@@ -86,9 +163,20 @@ export function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
86
163
|
import { useHeadlessConsentUI, useTranslations } from '@c15t/react/headless';
|
|
87
164
|
|
|
88
165
|
export function CustomConsentBanner() {
|
|
89
|
-
const { banner, openDialog,
|
|
166
|
+
const { banner, openDialog, performBannerAction } = useHeadlessConsentUI();
|
|
90
167
|
const translations = useTranslations();
|
|
91
168
|
|
|
169
|
+
function getActionLabel(action: (typeof banner.allowedActions)[number]) {
|
|
170
|
+
switch (action) {
|
|
171
|
+
case 'accept':
|
|
172
|
+
return translations.common.acceptAll;
|
|
173
|
+
case 'reject':
|
|
174
|
+
return translations.common.rejectAll;
|
|
175
|
+
case 'customize':
|
|
176
|
+
return translations.common.customize;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
92
180
|
if (!banner.isVisible) return null;
|
|
93
181
|
|
|
94
182
|
return (
|
|
@@ -111,14 +199,10 @@ export function CustomConsentBanner() {
|
|
|
111
199
|
openDialog();
|
|
112
200
|
return;
|
|
113
201
|
}
|
|
114
|
-
void
|
|
202
|
+
void performBannerAction(action);
|
|
115
203
|
}}
|
|
116
204
|
>
|
|
117
|
-
{action
|
|
118
|
-
? translations.common.acceptAll
|
|
119
|
-
: action === 'reject'
|
|
120
|
-
? translations.common.rejectAll
|
|
121
|
-
: translations.common.customize}
|
|
205
|
+
{getActionLabel(action)}
|
|
122
206
|
</button>
|
|
123
207
|
))}
|
|
124
208
|
</div>
|
|
@@ -149,6 +233,17 @@ export function CustomConsentDialog() {
|
|
|
149
233
|
} = useConsentManager();
|
|
150
234
|
const translations = useTranslations();
|
|
151
235
|
|
|
236
|
+
function getActionLabel(action: (typeof dialog.allowedActions)[number]) {
|
|
237
|
+
switch (action) {
|
|
238
|
+
case 'accept':
|
|
239
|
+
return translations.common.acceptAll;
|
|
240
|
+
case 'reject':
|
|
241
|
+
return translations.common.rejectAll;
|
|
242
|
+
case 'customize':
|
|
243
|
+
return translations.common.save;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
152
247
|
if (!dialog.isVisible) return null;
|
|
153
248
|
|
|
154
249
|
const displayedTypes = consentTypes.filter(
|
|
@@ -197,11 +292,7 @@ export function CustomConsentDialog() {
|
|
|
197
292
|
void performDialogAction(action);
|
|
198
293
|
}}
|
|
199
294
|
>
|
|
200
|
-
{action
|
|
201
|
-
? translations.common.acceptAll
|
|
202
|
-
: action === 'reject'
|
|
203
|
-
? translations.common.rejectAll
|
|
204
|
-
: translations.common.save}
|
|
295
|
+
{getActionLabel(action)}
|
|
205
296
|
</button>
|
|
206
297
|
))}
|
|
207
298
|
</div>
|
|
@@ -212,6 +303,17 @@ export function CustomConsentDialog() {
|
|
|
212
303
|
}
|
|
213
304
|
```
|
|
214
305
|
|
|
306
|
+
## What Headless Is Not For
|
|
307
|
+
|
|
308
|
+
Headless mode is not the recommended path for:
|
|
309
|
+
|
|
310
|
+
* changing the banner footer background
|
|
311
|
+
* rounding the stock banner card
|
|
312
|
+
* restyling stock banner or dialog buttons
|
|
313
|
+
* changing consent copy
|
|
314
|
+
|
|
315
|
+
Those should stay in the pre-built stack with tokens, slots, `theme.consentActions`, and provider `i18n`.
|
|
316
|
+
|
|
215
317
|
## What a Policy-Aware Headless Component Should Respect
|
|
216
318
|
|
|
217
319
|
When you build custom banner or dialog components, make sure they use:
|
|
@@ -241,36 +241,7 @@ renderAction={(action, props) => {
|
|
|
241
241
|
/>
|
|
242
242
|
```
|
|
243
243
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
```tsx
|
|
247
|
-
import { ConsentBanner, useTranslations } from '@c15t/react';
|
|
248
|
-
|
|
249
|
-
export function CustomBannerActions() {
|
|
250
|
-
const { common } = useTranslations();
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<ConsentBanner.PolicyActions
|
|
254
|
-
renderAction={(action, props) => (
|
|
255
|
-
<button
|
|
256
|
-
key={props.key}
|
|
257
|
-
type="button"
|
|
258
|
-
className={props.isPrimary ? 'btn-primary' : 'btn-secondary'}
|
|
259
|
-
style={props.style}
|
|
260
|
-
>
|
|
261
|
-
{action === 'accept'
|
|
262
|
-
? common.acceptAll
|
|
263
|
-
: action === 'reject'
|
|
264
|
-
? common.rejectAll
|
|
265
|
-
: common.customize}
|
|
266
|
-
</button>
|
|
267
|
-
)}
|
|
268
|
-
/>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
For maximum control, use `useHeadlessConsentUI()` and render `banner.actionGroups` manually.
|
|
244
|
+
`renderAction` is still meant for stock button compounds. If you want completely custom button elements and click handling, use `useHeadlessConsentUI()` and render `banner.actionGroups` manually instead of `ConsentBanner.PolicyActions`.
|
|
274
245
|
|
|
275
246
|
If you only need styling changes, stay with tokens and slots instead of rebuilding the banner layout.
|
|
276
247
|
|
|
@@ -117,34 +117,7 @@ renderAction={(action, props) => {
|
|
|
117
117
|
/>
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```tsx
|
|
123
|
-
import { ConsentWidget, useTranslations } from '@c15t/react';
|
|
124
|
-
|
|
125
|
-
export function CustomWidgetActions() {
|
|
126
|
-
const { common } = useTranslations();
|
|
127
|
-
|
|
128
|
-
return (
|
|
129
|
-
<ConsentWidget.PolicyActions
|
|
130
|
-
renderAction={(action, props) => (
|
|
131
|
-
<button
|
|
132
|
-
key={props.key}
|
|
133
|
-
type="button"
|
|
134
|
-
className={props.isPrimary ? 'btn-primary' : 'btn-secondary'}
|
|
135
|
-
style={props.style}
|
|
136
|
-
>
|
|
137
|
-
{action === 'accept'
|
|
138
|
-
? common.acceptAll
|
|
139
|
-
: action === 'reject'
|
|
140
|
-
? common.rejectAll
|
|
141
|
-
: common.save}
|
|
142
|
-
</button>
|
|
143
|
-
)}
|
|
144
|
-
/>
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
```
|
|
120
|
+
`renderAction` is still meant for stock button compounds. If you want completely custom button elements and handlers, use `useHeadlessConsentUI()` and render `dialog.actionGroups` manually instead of `ConsentWidget.PolicyActions`.
|
|
148
121
|
|
|
149
122
|
For a fixed footer layout, render `ConsentWidget.Footer` and `ConsentWidget.FooterSubGroup` manually instead of using `ConsentWidget.PolicyActions`.
|
|
150
123
|
|
|
@@ -360,7 +360,7 @@ function LoginForm() {
|
|
|
360
360
|
```
|
|
361
361
|
|
|
362
362
|
> ℹ️ **Info:**
|
|
363
|
-
> identifyUser sends the user data to the c15t backend. It
|
|
363
|
+
> identifyUser sends the user data to the c15t backend. It works in hosted mode, including the legacy alias mode: 'c15t'. In mode: 'offline', the call is a no-op.
|
|
364
364
|
|
|
365
365
|
## Key Types
|
|
366
366
|
|
|
@@ -19,6 +19,7 @@ Use this component instead of `ConsentBanner` when you need IAB TCF compliance f
|
|
|
19
19
|
|
|
20
20
|
```tsx
|
|
21
21
|
import { type ReactNode } from 'react';
|
|
22
|
+
import { iab } from '@c15t/iab';
|
|
22
23
|
import { ConsentManagerProvider } from '@c15t/react';
|
|
23
24
|
import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
|
|
24
25
|
|
|
@@ -28,11 +29,12 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
28
29
|
options={{
|
|
29
30
|
mode: 'hosted',
|
|
30
31
|
backendURL: 'https://your-instance.c15t.dev',
|
|
31
|
-
iab: {
|
|
32
|
-
enabled: true,
|
|
33
|
-
cmpId: 123,
|
|
32
|
+
iab: iab({
|
|
34
33
|
vendors: [1, 2, 10, 25],
|
|
35
|
-
|
|
34
|
+
// cmpId is automatically provided by the backend when using consent.io.
|
|
35
|
+
// Only set this if you have your own CMP registration.
|
|
36
|
+
// cmpId: 123,
|
|
37
|
+
}),
|
|
36
38
|
}}
|
|
37
39
|
>
|
|
38
40
|
<IABConsentBanner />
|
|
@@ -13,6 +13,7 @@ Pair it with `IABConsentBanner` inside the provider:
|
|
|
13
13
|
|
|
14
14
|
```tsx
|
|
15
15
|
import { type ReactNode } from 'react';
|
|
16
|
+
import { iab } from '@c15t/iab';
|
|
16
17
|
import { ConsentManagerProvider } from '@c15t/react';
|
|
17
18
|
import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
|
|
18
19
|
|
|
@@ -22,11 +23,12 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
22
23
|
options={{
|
|
23
24
|
mode: 'hosted',
|
|
24
25
|
backendURL: 'https://your-instance.c15t.dev',
|
|
25
|
-
iab: {
|
|
26
|
-
enabled: true,
|
|
27
|
-
cmpId: 123,
|
|
26
|
+
iab: iab({
|
|
28
27
|
vendors: [1, 2, 10, 25],
|
|
29
|
-
|
|
28
|
+
// cmpId is automatically provided by the backend when using consent.io.
|
|
29
|
+
// Only set this if you have your own CMP registration.
|
|
30
|
+
// cmpId: 123,
|
|
31
|
+
}),
|
|
30
32
|
}}
|
|
31
33
|
>
|
|
32
34
|
<IABConsentBanner />
|
package/docs/iab/overview.md
CHANGED
|
@@ -45,6 +45,7 @@ If you use the prebuilt styled IAB UI, import the IAB stylesheet alongside the b
|
|
|
45
45
|
|
|
46
46
|
```tsx
|
|
47
47
|
import { type ReactNode } from 'react';
|
|
48
|
+
import { iab } from '@c15t/iab';
|
|
48
49
|
import { ConsentManagerProvider } from '@c15t/react';
|
|
49
50
|
import { IABConsentBanner, IABConsentDialog } from '@c15t/react/iab';
|
|
50
51
|
|
|
@@ -54,13 +55,12 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
54
55
|
options={{
|
|
55
56
|
mode: 'hosted',
|
|
56
57
|
backendURL: 'https://your-instance.c15t.dev',
|
|
57
|
-
iab: {
|
|
58
|
-
enabled: true,
|
|
58
|
+
iab: iab({
|
|
59
59
|
vendors: [1, 2, 10, 25], // IAB vendor IDs you work with
|
|
60
60
|
// cmpId is automatically provided by the backend (inth.com).
|
|
61
61
|
// Only set this if you have your own CMP registration with IAB Europe.
|
|
62
62
|
// cmpId: 123,
|
|
63
|
-
},
|
|
63
|
+
}),
|
|
64
64
|
}}
|
|
65
65
|
>
|
|
66
66
|
<IABConsentBanner />
|
|
@@ -73,14 +73,13 @@ export default function ConsentManager({ children }: { children: ReactNode }) {
|
|
|
73
73
|
|
|
74
74
|
## IAB Configuration Options
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Configure IAB mode with `iab({ ... })` from `@c15t/iab`. The factory enables the addon and injects the runtime module automatically. The user-facing options are:
|
|
77
77
|
|
|
78
78
|
|Option|Type|Description|
|
|
79
79
|
|--|--|--|
|
|
80
|
-
|`enabled`|`boolean`|Enable IAB TCF mode|
|
|
81
80
|
|`cmpId`|`number`|CMP ID registered with IAB Europe. Automatically provided by the backend when using inth.com. Only set this if you have your own CMP registration.|
|
|
82
81
|
|`vendors`|`number[]`|IAB vendor IDs that your site works with|
|
|
83
|
-
|`
|
|
82
|
+
|`customVendors`|`NonIABVendor[]`|Custom vendors not in the IAB registry|
|
|
84
83
|
|
|
85
84
|
## Key Concepts
|
|
86
85
|
|
|
@@ -122,4 +121,6 @@ Each vendor in the GVL declares which purposes it uses, whether via consent or l
|
|
|
122
121
|
|--|--|
|
|
123
122
|
|[IABConsentBanner](/docs/frameworks/react/iab/consent-banner)|TCF-compliant banner with partner disclosure|
|
|
124
123
|
|[IABConsentDialog](/docs/frameworks/react/iab/consent-dialog)|Tabbed preference center for purposes and vendors|
|
|
125
|
-
|
|
124
|
+
|
|
125
|
+
> ℹ️ **Info:**
|
|
126
|
+
> For lower-level custom IAB flows, use useHeadlessIABConsentUI() from @c15t/react/iab. useGVLData() is currently internal and is not part of the public package surface.
|
package/docs/iab/use-gvl-data.md
CHANGED
|
@@ -1,208 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: useGVLData
|
|
3
|
-
description:
|
|
2
|
+
title: useGVLData (Internal)
|
|
3
|
+
description: Status note for the internal GVL hook used by the built-in IAB dialog.
|
|
4
4
|
---
|
|
5
5
|
> ❌ **Error:**
|
|
6
6
|
> c15t is not yet IAB certified. The IAB TCF components are under active development and should not be used in production. APIs and behavior may change before certification is achieved.
|
|
7
7
|
|
|
8
|
-
`useGVLData()`
|
|
8
|
+
`useGVLData()` currently powers the built-in `IABConsentDialog`, but it is **not part of the public package surface**.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Older docs showed it as a public hook. That is no longer accurate.
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
import { useGVLData } from '@c15t/react/hooks';
|
|
12
|
+
If you need supported customization points today:
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (isLoading) return <p>Loading vendor data...</p>;
|
|
19
|
-
|
|
20
|
-
return (
|
|
21
|
-
<div>
|
|
22
|
-
<p>{totalVendors} partners</p>
|
|
23
|
-
{standalonePurposes.map((purpose) => (
|
|
24
|
-
<div key={purpose.id}>
|
|
25
|
-
<h3>{purpose.name}</h3>
|
|
26
|
-
<p>{purpose.description}</p>
|
|
27
|
-
<p>{purpose.vendors.length} vendors</p>
|
|
28
|
-
</div>
|
|
29
|
-
))}
|
|
30
|
-
</div>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
```
|
|
14
|
+
* Use `IABConsentBanner` and `IABConsentDialog` from `@c15t/react/iab` for the supported prebuilt UI
|
|
15
|
+
* Use `useHeadlessIABConsentUI()` from `@c15t/react/iab` when you need lower-level control over banner/dialog state and actions
|
|
34
16
|
|
|
35
17
|
> ℹ️ **Info:**
|
|
36
|
-
>
|
|
37
|
-
|
|
38
|
-
## Return Value
|
|
39
|
-
|
|
40
|
-
The hook returns a `GVLData` object:
|
|
41
|
-
|
|
42
|
-
### GVLData
|
|
43
|
-
|
|
44
|
-
|Property|Type|Description|Default|Required|
|
|
45
|
-
|:--|:--|:--|:--|:--:|
|
|
46
|
-
|purposes|ProcessedPurpose|-|-|✅ Required|
|
|
47
|
-
|specialPurposes|ProcessedPurpose|-|-|✅ Required|
|
|
48
|
-
|specialFeatures|ProcessedSpecialFeature|-|-|✅ Required|
|
|
49
|
-
|features|ProcessedFeature|-|-|✅ Required|
|
|
50
|
-
|stacks|ProcessedStack|-|-|✅ Required|
|
|
51
|
-
|standalonePurposes|ProcessedPurpose|-|-|✅ Required|
|
|
52
|
-
|totalVendors|number|-|-|✅ Required|
|
|
53
|
-
|isLoading|boolean|-|-|✅ Required|
|
|
54
|
-
|
|
55
|
-
#### `purposes` ProcessedPurpose
|
|
56
|
-
|
|
57
|
-
|Property|Type|Description|Default|Required|
|
|
58
|
-
|:--|:--|:--|:--|:--:|
|
|
59
|
-
|id|number|-|-|✅ Required|
|
|
60
|
-
|name|string|-|-|✅ Required|
|
|
61
|
-
|description|string|-|-|✅ Required|
|
|
62
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
63
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
64
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
65
|
-
|isSpecialPurpose|boolean \|undefined|-|-|Optional|
|
|
66
|
-
|
|
67
|
-
#### `specialPurposes` ProcessedPurpose
|
|
68
|
-
|
|
69
|
-
|Property|Type|Description|Default|Required|
|
|
70
|
-
|:--|:--|:--|:--|:--:|
|
|
71
|
-
|id|number|-|-|✅ Required|
|
|
72
|
-
|name|string|-|-|✅ Required|
|
|
73
|
-
|description|string|-|-|✅ Required|
|
|
74
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
75
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
76
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
77
|
-
|isSpecialPurpose|boolean \|undefined|-|-|Optional|
|
|
78
|
-
|
|
79
|
-
#### `specialFeatures` ProcessedSpecialFeature
|
|
80
|
-
|
|
81
|
-
|Property|Type|Description|Default|Required|
|
|
82
|
-
|:--|:--|:--|:--|:--:|
|
|
83
|
-
|id|number|-|-|✅ Required|
|
|
84
|
-
|name|string|-|-|✅ Required|
|
|
85
|
-
|description|string|-|-|✅ Required|
|
|
86
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
87
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
88
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
89
|
-
|
|
90
|
-
#### `features` ProcessedFeature
|
|
91
|
-
|
|
92
|
-
|Property|Type|Description|Default|Required|
|
|
93
|
-
|:--|:--|:--|:--|:--:|
|
|
94
|
-
|id|number|-|-|✅ Required|
|
|
95
|
-
|name|string|-|-|✅ Required|
|
|
96
|
-
|description|string|-|-|✅ Required|
|
|
97
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
98
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
99
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
100
|
-
|
|
101
|
-
#### `stacks` ProcessedStack
|
|
102
|
-
|
|
103
|
-
|Property|Type|Description|Default|Required|
|
|
104
|
-
|:--|:--|:--|:--|:--:|
|
|
105
|
-
|id|number|-|-|✅ Required|
|
|
106
|
-
|name|string|-|-|✅ Required|
|
|
107
|
-
|description|string|-|-|✅ Required|
|
|
108
|
-
|purposes|ProcessedPurpose|-|-|✅ Required|
|
|
109
|
-
|
|
110
|
-
#### `standalonePurposes` ProcessedPurpose
|
|
111
|
-
|
|
112
|
-
|Property|Type|Description|Default|Required|
|
|
113
|
-
|:--|:--|:--|:--|:--:|
|
|
114
|
-
|id|number|-|-|✅ Required|
|
|
115
|
-
|name|string|-|-|✅ Required|
|
|
116
|
-
|description|string|-|-|✅ Required|
|
|
117
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
118
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
119
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
120
|
-
|isSpecialPurpose|boolean \|undefined|-|-|Optional|
|
|
121
|
-
|
|
122
|
-
## Types
|
|
123
|
-
|
|
124
|
-
### ProcessedPurpose
|
|
125
|
-
|
|
126
|
-
|Property|Type|Description|Default|Required|
|
|
127
|
-
|:--|:--|:--|:--|:--:|
|
|
128
|
-
|id|number|-|-|✅ Required|
|
|
129
|
-
|name|string|-|-|✅ Required|
|
|
130
|
-
|description|string|-|-|✅ Required|
|
|
131
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
132
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
133
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
134
|
-
|isSpecialPurpose|boolean \|undefined|-|-|Optional|
|
|
135
|
-
|
|
136
|
-
#### `vendors` ProcessedVendor
|
|
137
|
-
|
|
138
|
-
|Property|Type|Description|Default|Required|
|
|
139
|
-
|:--|:--|:--|:--|:--:|
|
|
140
|
-
|id|VendorId|-|-|✅ Required|
|
|
141
|
-
|name|string|-|-|✅ Required|
|
|
142
|
-
|policyUrl|string|-|-|✅ Required|
|
|
143
|
-
|usesNonCookieAccess|boolean|-|-|✅ Required|
|
|
144
|
-
|deviceStorageDisclosureUrl|string \|null|-|-|✅ Required|
|
|
145
|
-
|usesCookies|boolean|-|-|✅ Required|
|
|
146
|
-
|cookieMaxAgeSeconds|number \|null|-|-|✅ Required|
|
|
147
|
-
|cookieRefresh|boolean \|undefined|-|-|Optional|
|
|
148
|
-
|specialPurposes|number\[]|-|-|✅ Required|
|
|
149
|
-
|specialFeatures|number\[]|-|-|✅ Required|
|
|
150
|
-
|features|number\[]|-|-|✅ Required|
|
|
151
|
-
|purposes|number\[]|-|-|✅ Required|
|
|
152
|
-
|legIntPurposes|number\[]|-|-|✅ Required|
|
|
153
|
-
|legitimateInterestUrl|string \|null \|undefined|-|-|Optional|
|
|
154
|
-
|isCustom|boolean \|undefined|-|-|Optional|
|
|
155
|
-
|usesLegitimateInterest|boolean \|undefined|-|-|Optional|
|
|
156
|
-
|dataRetention|Object \|undefined|-|-|Optional|
|
|
157
|
-
|dataDeclaration|number\[] \|undefined|-|-|Optional|
|
|
158
|
-
|
|
159
|
-
### ProcessedVendor
|
|
160
|
-
|
|
161
|
-
|Property|Type|Description|Default|Required|
|
|
162
|
-
|:--|:--|:--|:--|:--:|
|
|
163
|
-
|id|VendorId|-|-|✅ Required|
|
|
164
|
-
|name|string|-|-|✅ Required|
|
|
165
|
-
|policyUrl|string|-|-|✅ Required|
|
|
166
|
-
|usesNonCookieAccess|boolean|-|-|✅ Required|
|
|
167
|
-
|deviceStorageDisclosureUrl|string \|null|-|-|✅ Required|
|
|
168
|
-
|usesCookies|boolean|-|-|✅ Required|
|
|
169
|
-
|cookieMaxAgeSeconds|number \|null|-|-|✅ Required|
|
|
170
|
-
|cookieRefresh|boolean \|undefined|-|-|Optional|
|
|
171
|
-
|specialPurposes|number\[]|-|-|✅ Required|
|
|
172
|
-
|specialFeatures|number\[]|-|-|✅ Required|
|
|
173
|
-
|features|number\[]|-|-|✅ Required|
|
|
174
|
-
|purposes|number\[]|-|-|✅ Required|
|
|
175
|
-
|legIntPurposes|number\[]|-|-|✅ Required|
|
|
176
|
-
|legitimateInterestUrl|string \|null \|undefined|-|-|Optional|
|
|
177
|
-
|isCustom|boolean \|undefined|-|-|Optional|
|
|
178
|
-
|usesLegitimateInterest|boolean \|undefined|-|-|Optional|
|
|
179
|
-
|dataRetention|Object \|undefined|-|-|Optional|
|
|
180
|
-
|dataDeclaration|number\[] \|undefined|-|-|Optional|
|
|
181
|
-
|
|
182
|
-
#### `dataRetention`
|
|
183
|
-
|
|
184
|
-
|Property|Type|Description|Default|Required|
|
|
185
|
-
|:--|:--|:--|:--|:--:|
|
|
186
|
-
|purposes|Record\<number, number> \|undefined|-|-|Optional|
|
|
187
|
-
|specialPurposes|Record\<number, number> \|undefined|-|-|Optional|
|
|
188
|
-
|stdRetention|number \|undefined|-|-|Optional|
|
|
189
|
-
|
|
190
|
-
### ProcessedStack
|
|
191
|
-
|
|
192
|
-
|Property|Type|Description|Default|Required|
|
|
193
|
-
|:--|:--|:--|:--|:--:|
|
|
194
|
-
|id|number|-|-|✅ Required|
|
|
195
|
-
|name|string|-|-|✅ Required|
|
|
196
|
-
|description|string|-|-|✅ Required|
|
|
197
|
-
|purposes|ProcessedPurpose|-|-|✅ Required|
|
|
198
|
-
|
|
199
|
-
### ProcessedSpecialFeature
|
|
18
|
+
> Until useGVLData() is exported as public API, avoid importing it from deep internal paths. Those paths are not covered by semver guarantees and can change without notice.
|
|
200
19
|
|
|
201
|
-
|
|
202
|
-
|:--|:--|:--|:--|:--:|
|
|
203
|
-
|id|number|-|-|✅ Required|
|
|
204
|
-
|name|string|-|-|✅ Required|
|
|
205
|
-
|description|string|-|-|✅ Required|
|
|
206
|
-
|descriptionLegal|string \|undefined|-|-|Optional|
|
|
207
|
-
|illustrations|string\[]|-|-|✅ Required|
|
|
208
|
-
|vendors|ProcessedVendor|-|-|✅ Required|
|
|
20
|
+
When a public GVL-focused hook becomes part of the supported API, this page should document that public surface instead of the internal dialog hook.
|
package/docs/optimization.md
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Optimization
|
|
3
3
|
description: Improve c15t startup performance in React with prefetching, proxy rewrites, and rendering tradeoffs.
|
|
4
|
-
lastModified: 2026-
|
|
4
|
+
lastModified: 2026-04-14
|
|
5
5
|
---
|
|
6
6
|
Use this guide when you care about banner visibility speed, route static-ness, and reducing backend round-trip cost.
|
|
7
7
|
|
|
8
|
+
## Start Here
|
|
9
|
+
|
|
10
|
+
Apply the optimizations in this order:
|
|
11
|
+
|
|
12
|
+
|Situation|Use|Why|Tradeoff|
|
|
13
|
+
|--|--|--|--|
|
|
14
|
+
|Any production app using hosted mode|Same-origin `/api/c15t` proxy|Lowers browser startup overhead and keeps the backend origin out of client config|Requires framework or platform proxy setup|
|
|
15
|
+
|Banner speed matters on cold loads|`buildPrefetchScript()`|Starts `/init` before your app hydrates|Still a browser-side fetch, not SSR|
|
|
16
|
+
|Your app navigates client-side|Keep the provider mounted at the app root|Avoids remounting and re-running init work|Requires provider placement discipline|
|
|
17
|
+
|You cannot proxy and must stay cross-origin|`<link rel="preconnect">`|Starts DNS/TLS work earlier|Smaller gain than same-origin proxying|
|
|
18
|
+
|You are using Next.js and want SSR or static-route-specific guidance|Next.js optimization docs|Covers `C15tPrefetch`, `fetchInitialData()`, and rendering tradeoffs|Next-specific|
|
|
19
|
+
|
|
20
|
+
> ℹ️ **Info:**
|
|
21
|
+
> Treat same-origin proxying as the baseline optimization. The others are situational layers you add when startup timing or route behavior justifies them.
|
|
22
|
+
|
|
8
23
|
## 1) Prefer Same-Origin Proxy
|
|
9
24
|
|
|
10
25
|
Proxy c15t requests through your app server so the browser calls your own origin instead of a third-party domain.
|
|
@@ -23,6 +38,8 @@ Why this helps:
|
|
|
23
38
|
* Ad blockers are less likely to block your init endpoint
|
|
24
39
|
* You can change backend infrastructure without touching client code
|
|
25
40
|
|
|
41
|
+
If you can only do one optimization, do this one first.
|
|
42
|
+
|
|
26
43
|
## 2) Prefetch Init Data Early
|
|
27
44
|
|
|
28
45
|
Use `buildPrefetchScript()` from the `c15t` core package to start the `/init` request before your app hydrates. Inject the script as early as possible in your HTML `<head>`.
|
|
@@ -35,6 +52,12 @@ In production benchmarks with a same-origin rewrite, prefetching strategies show
|
|
|
35
52
|
|Browser prefetch|\~1.3x faster|\~2.6x earlier|\~1.25x faster|
|
|
36
53
|
|Server prefetch|\~2x faster|before page loads|\~1.9x faster|
|
|
37
54
|
|
|
55
|
+
Use this when:
|
|
56
|
+
|
|
57
|
+
* The app is client-rendered
|
|
58
|
+
* You want a faster first banner without introducing SSR complexity
|
|
59
|
+
* You control the HTML template or document `<head>`
|
|
60
|
+
|
|
38
61
|
### Inline Script Prefetch
|
|
39
62
|
|
|
40
63
|
```tsx
|
|
@@ -88,6 +111,8 @@ Why this helps:
|
|
|
88
111
|
* Prevents extra callback churn from remount cycles
|
|
89
112
|
* Keeps banner/dialog state stable between route transitions
|
|
90
113
|
|
|
114
|
+
Mount `ConsentManagerProvider` as high in the tree as possible so route transitions do not recreate it.
|
|
115
|
+
|
|
91
116
|
## Animation Performance
|
|
92
117
|
|
|
93
118
|
The default motion tokens are tuned for speed-first product UI:
|
|
@@ -104,6 +129,8 @@ To customize motion durations and easing, see [Styling](/docs/frameworks/react/s
|
|
|
104
129
|
|
|
105
130
|
## Reduce Network Overhead
|
|
106
131
|
|
|
132
|
+
If your browser must call a cross-origin backend URL directly, add `preconnect` so the browser can warm up the connection earlier. This is a fallback optimization when same-origin proxying is not possible.
|
|
133
|
+
|
|
107
134
|
If you must use a cross-origin backend URL, add preconnect so the browser starts DNS/TLS early:
|
|
108
135
|
|
|
109
136
|
```tsx
|
|
@@ -111,3 +138,10 @@ If you must use a cross-origin backend URL, add preconnect so the browser starts
|
|
|
111
138
|
<link rel="preconnect" href="https://your-instance.c15t.dev" crossOrigin="" />
|
|
112
139
|
</head>
|
|
113
140
|
```
|
|
141
|
+
|
|
142
|
+
## Next.js-Specific SSR And Static Routes
|
|
143
|
+
|
|
144
|
+
This page is intentionally React-generic. If you are using Next.js and need SSR or static-route-specific guidance:
|
|
145
|
+
|
|
146
|
+
* See [Next.js Optimization](/docs/frameworks/next/optimization) for the static-vs-dynamic decision guide
|
|
147
|
+
* See [Next.js Server-Side Data Fetching](/docs/frameworks/next/server-side) for `fetchInitialData()`
|
package/docs/quickstart.md
CHANGED
|
@@ -17,10 +17,10 @@ availableIn:
|
|
|
17
17
|
|
|
18
18
|
|Package manager|Command|
|
|
19
19
|
|:--|:--|
|
|
20
|
-
|npm|`npx @c15t/cli
|
|
21
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
22
|
-
|yarn|`yarn dlx @c15t/cli
|
|
23
|
-
|bun|`bunx @c15t/cli
|
|
20
|
+
|npm|`npx @c15t/cli`|
|
|
21
|
+
|pnpm|`pnpm dlx @c15t/cli`|
|
|
22
|
+
|yarn|`yarn dlx @c15t/cli`|
|
|
23
|
+
|bun|`bunx @c15t/cli`|
|
|
24
24
|
|
|
25
25
|
## Manual Installation
|
|
26
26
|
|
|
@@ -107,6 +107,9 @@ availableIn:
|
|
|
107
107
|
|
|
108
108
|
A consent banner appears at the bottom of the pageClicking "Customize" opens a dialog with toggles for each consent categoryAfter accepting or rejecting, the banner dismisses and your choice persists across page reloads
|
|
109
109
|
|
|
110
|
+
> ℹ️ **Info:**
|
|
111
|
+
> Want to improve startup performance? See Optimization for the decision guide, prefetch setup, and network tuning.
|
|
112
|
+
|
|
110
113
|
## Optional: Add DevTools
|
|
111
114
|
|
|
112
115
|
Install DevTools only if you want a runtime inspector while building and debugging:
|
|
@@ -142,10 +145,10 @@ Install c15t agent skills to let AI agents help with styling, i18n, scripts & ot
|
|
|
142
145
|
|
|
143
146
|
|Package manager|Command|
|
|
144
147
|
|:--|:--|
|
|
145
|
-
|npm|`npx @c15t/cli
|
|
146
|
-
|pnpm|`pnpm dlx @c15t/cli
|
|
147
|
-
|yarn|`yarn dlx @c15t/cli
|
|
148
|
-
|bun|`bunx @c15t/cli
|
|
148
|
+
|npm|`npx @c15t/cli skills`|
|
|
149
|
+
|pnpm|`pnpm dlx @c15t/cli skills`|
|
|
150
|
+
|yarn|`yarn dlx @c15t/cli skills`|
|
|
151
|
+
|bun|`bunx @c15t/cli skills`|
|
|
149
152
|
|
|
150
153
|
See [AI Agents](/docs/ai-agents) for bundled package docs and agent skills.
|
|
151
154
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c15t/react",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.12",
|
|
4
4
|
"description": "Developer-first CMP for React: cookie banner, consent manager, preferences centre. GDPR ready with minimal setup and rich customization",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
"not op_mini all"
|
|
165
165
|
],
|
|
166
166
|
"dependencies": {
|
|
167
|
-
"@c15t/ui": "2.0.0-rc.
|
|
167
|
+
"@c15t/ui": "2.0.0-rc.11",
|
|
168
168
|
"c15t": "2.0.0-rc.10"
|
|
169
169
|
},
|
|
170
170
|
"devDependencies": {
|