@c15t/react 3.0.0-alpha.3 → 3.0.0-alpha.5
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/AGENTS.md +92 -72
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/iab-panel/atoms/card.js +1 -1
- package/dist/components/iab-panel/atoms/feature-item.js +2 -0
- package/dist/components/iab-panel/atoms/header.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/iab-prompt/atoms/overlay.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel/atoms/card.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/components.js +1 -1
- package/dist/components/shared/libs/compose-refs.js +1 -0
- package/dist/components/shared/libs/slot.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft-context.js +2 -0
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/gpp.js +2 -0
- package/dist/hooks/use-color-scheme.js +1 -1
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +4 -4
- package/dist/index-parts/consent-banner.js +1 -0
- package/dist/index-parts/consent-dialog-link.js +1 -0
- package/dist/index-parts/consent-dialog-trigger.js +1 -0
- package/dist/index-parts/consent-dialog.js +1 -0
- package/dist/index-parts/consent-gate.js +1 -0
- package/dist/index-parts/core.js +1 -0
- package/dist/index-parts/draft.js +1 -0
- package/dist/index-parts/hooks.js +1 -0
- package/dist/index-parts/module-hooks.js +1 -0
- package/dist/index-parts/provider.js +1 -0
- package/dist/index-parts/theme.js +1 -0
- package/dist/index-parts/translations.js +1 -0
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -1
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/provider.js +1 -1
- package/dist/server/fetch-ssr-data.js +1 -1
- package/dist/server/headers.js +1 -1
- package/dist/server/normalize-url.js +1 -1
- package/dist/styles.tw3.css +16 -16
- package/dist/transports/offline.js +1 -1
- package/dist/utils/merge-slot-props.js +1 -1
- package/dist/utils/theme-utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
- package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +5 -2
- package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
- package/dist-types/components/iab-prompt/index.d.ts +2 -2
- package/dist-types/components/panel/atoms/card.d.ts +1 -0
- package/dist-types/components/prompt/components.d.ts +6 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/context/consent-tracking-context.d.ts +10 -0
- package/dist-types/context/theme-context.d.ts +2 -2
- package/dist-types/context.d.ts +6 -0
- package/dist-types/devtools.d.ts +13 -2
- package/dist-types/draft-context.d.ts +31 -0
- package/dist-types/draft.d.ts +13 -30
- package/dist-types/gpp.d.ts +27 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +57 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index-parts/consent-banner.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
- package/dist-types/index-parts/consent-dialog.d.ts +7 -0
- package/dist-types/index-parts/consent-gate.d.ts +7 -0
- package/dist-types/index-parts/core.d.ts +8 -0
- package/dist-types/index-parts/draft.d.ts +7 -0
- package/dist-types/index-parts/hooks.d.ts +7 -0
- package/dist-types/index-parts/module-hooks.d.ts +7 -0
- package/dist-types/index-parts/provider.d.ts +7 -0
- package/dist-types/index-parts/theme.d.ts +8 -0
- package/dist-types/index-parts/translations.d.ts +7 -0
- package/dist-types/index.d.ts +14 -15
- package/dist-types/kernel-selector.d.ts +0 -8
- package/dist-types/module-hooks/iframe-blocker.d.ts +12 -7
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/provider.d.ts +57 -17
- package/dist-types/server/headers.d.ts +14 -4
- package/dist-types/server/normalize-url.d.ts +14 -7
- package/dist-types/server/types.d.ts +12 -1
- package/dist-types/transports/offline.d.ts +7 -16
- package/dist-types/ui-config-context.d.ts +7 -1
- package/dist-types/utils/merge-slot-props.d.ts +15 -1
- package/dist-types/utils/theme-utils.d.ts +17 -2
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +92 -72
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +87 -0
- package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +22 -7
- package/docs/{guides → concepts}/consent-state.md +89 -105
- package/docs/{guides → concepts}/data-fetching.md +31 -27
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +119 -0
- package/docs/customization/overview.md +67 -34
- package/docs/customization/recipes.md +839 -49
- package/docs/customization/slots.md +216 -35
- package/docs/customization/stylesheets.md +147 -0
- package/docs/customization/tailwind.md +842 -0
- package/docs/customization/tokens.md +163 -96
- package/docs/customization/translations.md +60 -3
- package/docs/frameworks/react/callbacks.md +118 -0
- package/docs/frameworks/react/clear-on-revocation.md +106 -0
- package/docs/frameworks/react/components/consent-banner.md +42 -28
- package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
- package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
- package/docs/frameworks/react/components/consent-dialog.md +56 -50
- package/docs/frameworks/react/components/consent-gate.md +37 -20
- package/docs/frameworks/react/components/consent-provider.md +130 -0
- package/docs/frameworks/react/components/consent-widget.md +23 -20
- package/docs/frameworks/react/components/dev-tools.md +53 -85
- package/docs/frameworks/react/components.md +105 -0
- package/docs/frameworks/react/compose.md +354 -0
- package/docs/frameworks/react/content-security-policy.md +126 -0
- package/docs/frameworks/react/customize.md +250 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/gpp.md +163 -0
- package/docs/frameworks/react/{hooks/overview.md → hooks.md} +21 -16
- package/docs/frameworks/react/{iab/overview.md → iab.md} +80 -31
- package/docs/frameworks/react/network-blocker.md +35 -41
- package/docs/frameworks/react/quickstart.md +139 -101
- package/docs/frameworks/react/rendering.md +128 -0
- package/docs/frameworks/react/scripts.md +115 -0
- package/docs/frameworks/react/translations.md +155 -0
- package/docs/frameworks/react/troubleshooting.md +92 -47
- package/docs/frameworks/react/vendor-consent.md +170 -0
- package/docs/guides/banner-experiments.md +654 -0
- package/docs/guides/troubleshooting.md +120 -47
- package/docs/guides/verify-consent.md +81 -49
- package/docs/integrations/adobe-analytics.md +167 -159
- package/docs/integrations/ahrefs-analytics.md +152 -154
- package/docs/integrations/amplitude.md +162 -156
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +154 -154
- package/docs/integrations/cloudflare-web-analytics.md +156 -156
- package/docs/integrations/cloudflare-zaraz.md +209 -261
- package/docs/integrations/crisp.md +164 -158
- package/docs/integrations/databuddy.md +157 -173
- package/docs/integrations/fathom-analytics.md +158 -156
- package/docs/integrations/front-chat.md +167 -167
- package/docs/integrations/google-maps.md +118 -83
- package/docs/integrations/google-tag-manager.md +178 -163
- package/docs/integrations/google-tag.md +163 -160
- package/docs/integrations/heap.md +163 -155
- package/docs/integrations/hightouch.md +161 -157
- package/docs/integrations/hotjar.md +159 -155
- package/docs/integrations/intercom.md +183 -153
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +174 -150
- package/docs/integrations/logrocket.md +160 -156
- package/docs/integrations/matomo-analytics.md +188 -178
- package/docs/integrations/meta-pixel.md +188 -150
- package/docs/integrations/microsoft-clarity.md +163 -155
- package/docs/integrations/microsoft-uet.md +148 -154
- package/docs/integrations/mixpanel-analytics.md +155 -160
- package/docs/integrations/one-dollar-stats.md +166 -165
- package/docs/integrations/openai-pixel.md +204 -301
- package/docs/integrations/overview.md +137 -80
- package/docs/integrations/pinterest-tag.md +191 -183
- package/docs/integrations/pirsch.md +169 -159
- package/docs/integrations/plausible-analytics.md +172 -158
- package/docs/integrations/posthog.md +313 -240
- package/docs/integrations/promptwatch.md +154 -154
- package/docs/integrations/reddit-pixel.md +185 -157
- package/docs/integrations/rudderstack.md +201 -186
- package/docs/integrations/rybbit-analytics.md +171 -160
- package/docs/integrations/segment.md +182 -154
- package/docs/integrations/snapchat-pixel.md +186 -156
- package/docs/integrations/tiktok-pixel.md +171 -150
- package/docs/integrations/umami-analytics.md +163 -158
- package/docs/integrations/vercel-analytics.md +166 -157
- package/docs/integrations/x-pixel.md +176 -150
- package/docs/integrations/youtube.md +121 -86
- package/docs/upgrade-v3.md +496 -467
- package/package.json +20 -9
- package/dist/ui-save.js +0 -1
- package/dist-types/ui-save.d.ts +0 -25
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/react/components/consent-manager-provider.md +0 -95
- package/docs/frameworks/react/script-loader.md +0 -69
- package/docs/frameworks/react/styling/overview.md +0 -266
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/guides/shared-consent-controls.md +0 -158
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -210
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: ConsentBanner
|
|
3
|
-
description:
|
|
3
|
+
description: Render the c15t consent banner in a React app, choose its variant
|
|
4
|
+
and position, and compose its parts.
|
|
4
5
|
group: frameworks
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
8
|
+
## Render the banner inside `ConsentProvider`
|
|
9
|
+
|
|
10
|
+
Render `ConsentBanner` once inside `ConsentProvider`, next to `ConsentDialog`.
|
|
11
|
+
The [quickstart](../quickstart.md) mounts both in
|
|
12
|
+
`src/consent.tsx`:
|
|
13
|
+
|
|
14
|
+
```tsx title="src/consent.tsx"
|
|
15
|
+
import {
|
|
16
|
+
ConsentBanner,
|
|
17
|
+
ConsentDialog,
|
|
18
|
+
ConsentDialogLink,
|
|
19
|
+
ConsentProvider,
|
|
20
|
+
hosted,
|
|
21
|
+
} from 'c15t/react';
|
|
22
|
+
import type { ReactNode } from 'react';
|
|
23
|
+
|
|
24
|
+
import { scripts } from './scripts';
|
|
25
|
+
|
|
26
|
+
import 'c15t/react/styles.css';
|
|
27
|
+
|
|
28
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
29
|
+
|
|
30
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
31
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
32
|
+
{children}
|
|
33
|
+
<ConsentBanner />
|
|
34
|
+
<ConsentDialog />
|
|
35
|
+
<footer>
|
|
36
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
37
|
+
</footer>
|
|
38
|
+
</ConsentProvider>
|
|
39
|
+
);
|
|
24
40
|
```
|
|
25
41
|
|
|
26
42
|
The banner renders whatever the active policy rule requires. It reads the rule
|
|
@@ -122,16 +138,14 @@ button through the provider's theme:
|
|
|
122
138
|
```ts
|
|
123
139
|
const theme = {
|
|
124
140
|
colors: { primary: '#2f6f4e', primaryHover: '#24563c' },
|
|
125
|
-
consentActions: {
|
|
126
|
-
primary: { variant: 'primary', mode: 'filled' },
|
|
127
|
-
},
|
|
141
|
+
consentActions: { primary: { variant: 'primary', mode: 'filled' } },
|
|
128
142
|
} as const;
|
|
129
143
|
```
|
|
130
144
|
|
|
131
145
|
Pass `theme` in `ConsentProvider` options. Per-action theme overrides such
|
|
132
146
|
as `consentActions.dismiss` take precedence over this primary style.
|
|
133
|
-
See [
|
|
134
|
-
[
|
|
147
|
+
See [customize](../customize.md) for tokens and slots, and
|
|
148
|
+
[policies](../../../concepts/policies.md) for the rule IDs and coverage.
|
|
135
149
|
|
|
136
150
|
## Variants
|
|
137
151
|
|
|
@@ -143,14 +157,14 @@ Set the variant on the banner, or on the provider under
|
|
|
143
157
|
`presentation.prompt` when every banner should share it. The prop wins.
|
|
144
158
|
|
|
145
159
|
```tsx
|
|
146
|
-
<ConsentBanner variant="floating" position="bottom-center"
|
|
160
|
+
<ConsentBanner variant="floating" position="bottom-center" />;
|
|
147
161
|
```
|
|
148
162
|
|
|
149
163
|
A floating card in a corner or centered on an edge. This is the default for
|
|
150
164
|
every prompt. A notice keeps the same card, with its right link and "OK" in the footer.
|
|
151
165
|
|
|
152
166
|
```tsx
|
|
153
|
-
<ConsentBanner variant="bar" position="top"
|
|
167
|
+
<ConsentBanner variant="bar" position="top" />;
|
|
154
168
|
```
|
|
155
169
|
|
|
156
170
|
A bar across the full width of the viewport. From 1024px wide the text, the
|
|
@@ -158,14 +172,14 @@ right links, and the controls share one row. Opt in to it for regions that
|
|
|
158
172
|
expect a classic cookie bar.
|
|
159
173
|
|
|
160
174
|
```tsx
|
|
161
|
-
<ConsentBanner variant="widget" position="bottom-right"
|
|
175
|
+
<ConsentBanner variant="widget" position="bottom-right" />;
|
|
162
176
|
```
|
|
163
177
|
|
|
164
178
|
A compact card with smaller type. The full description and its legal links
|
|
165
179
|
remain visible. Pair it with a short notice.
|
|
166
180
|
|
|
167
181
|
```tsx
|
|
168
|
-
<ConsentBanner variant="wall"
|
|
182
|
+
<ConsentBanner variant="wall" />;
|
|
169
183
|
```
|
|
170
184
|
|
|
171
185
|
A centered card over a backdrop that blocks the page until the visitor
|
|
@@ -248,7 +262,7 @@ Use the individual parts when you need a different order or your own markup:
|
|
|
248
262
|
<ConsentBanner.RightLink right="opt-out" asChild>
|
|
249
263
|
<a href="/privacy/do-not-sell">Do not sell or share my data</a>
|
|
250
264
|
</ConsentBanner.RightLink>
|
|
251
|
-
</ConsentBanner.Rights
|
|
265
|
+
</ConsentBanner.Rights>;
|
|
252
266
|
```
|
|
253
267
|
|
|
254
268
|
`useBannerCopy()` returns the title, description, and prompt kind the banner
|
|
@@ -16,15 +16,17 @@ same `ConsentProvider`; the [quickstart](../quickstart.md) mounts both.
|
|
|
16
16
|
import { ConsentDialogLink } from 'c15t/react';
|
|
17
17
|
|
|
18
18
|
export function SiteFooter() {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
return (
|
|
20
|
+
<footer>
|
|
21
|
+
<nav aria-label="Legal">
|
|
22
|
+
<a href="/privacy">Privacy policy</a>
|
|
23
|
+
<a href="/terms">Terms</a>
|
|
24
|
+
<ConsentDialogLink className="footer-link">
|
|
25
|
+
Privacy settings
|
|
26
|
+
</ConsentDialogLink>
|
|
27
|
+
</nav>
|
|
28
|
+
</footer>
|
|
29
|
+
);
|
|
28
30
|
}
|
|
29
31
|
```
|
|
30
32
|
|
|
@@ -37,8 +39,8 @@ To reuse an existing link component, pass `asChild`:
|
|
|
37
39
|
|
|
38
40
|
```tsx
|
|
39
41
|
<ConsentDialogLink asChild>
|
|
40
|
-
|
|
41
|
-
</ConsentDialogLink
|
|
42
|
+
<a href="#cookie-preferences">Manage cookies</a>
|
|
43
|
+
</ConsentDialogLink>;
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Props
|
|
@@ -1,35 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: ConsentDialogTrigger
|
|
3
|
-
description:
|
|
4
|
-
the
|
|
3
|
+
description: Add the c15t ConsentDialogTrigger floating button or toolbar inside
|
|
4
|
+
a React ConsentProvider so visitors can reopen the preference center after the
|
|
5
|
+
first prompt.
|
|
5
6
|
group: frameworks
|
|
6
7
|
---
|
|
7
8
|
|
|
8
|
-
##
|
|
9
|
+
## Add the trigger inside ConsentProvider
|
|
9
10
|
|
|
10
|
-
`ConsentDialogTrigger` is a draggable floating button that opens the preference center. Place it once
|
|
11
|
+
`ConsentDialogTrigger` is a draggable floating button that opens the preference center. Place it once inside `ConsentProvider`, next to the banner and dialog from the [quickstart](../quickstart.md), so visitors can revisit their choices without a footer link.
|
|
11
12
|
|
|
12
|
-
```tsx
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
ConsentProvider,
|
|
18
|
-
offline,
|
|
19
|
-
} from 'c15t/react';
|
|
20
|
-
import { policyRulePresets } from '@c15t/schema';
|
|
21
|
-
|
|
22
|
-
const mode = offline({ policyRules: [policyRulePresets.europeOptIn()] });
|
|
23
|
-
|
|
24
|
-
export function App() {
|
|
25
|
-
return (
|
|
26
|
-
<ConsentProvider options={{ mode }}>
|
|
27
|
-
<ConsentBanner />
|
|
28
|
-
<ConsentDialog />
|
|
29
|
-
<ConsentDialogTrigger showWhen="after-prompt" />
|
|
30
|
-
</ConsentProvider>
|
|
31
|
-
);
|
|
32
|
-
}
|
|
13
|
+
```tsx title="src/consent.tsx"
|
|
14
|
+
import { ConsentDialogTrigger } from 'c15t/react';
|
|
15
|
+
|
|
16
|
+
// Inside the ConsentProvider from your quickstart, next to the dialog:
|
|
17
|
+
<ConsentDialogTrigger showWhen="after-prompt" />;
|
|
33
18
|
```
|
|
34
19
|
|
|
35
20
|
`showWhen="after-prompt"` keeps the button out of the way while a choice or notice is still owed and shows it once the visitor has answered, so the banner and the trigger never compete for the same corner. The default, `always`, shows it whenever the preference center is closed.
|
|
@@ -56,43 +41,63 @@ Use `ConsentDialogTriggerToolbar` when you want app-owned controls beside the pr
|
|
|
56
41
|
import { ConsentDialogTriggerToolbar } from 'c15t/react';
|
|
57
42
|
|
|
58
43
|
<ConsentDialogTriggerToolbar
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
label: 'Manage privacy settings',
|
|
78
|
-
}}
|
|
79
|
-
/>
|
|
44
|
+
ariaLabel="Site controls"
|
|
45
|
+
actions={[
|
|
46
|
+
{
|
|
47
|
+
id: 'theme',
|
|
48
|
+
label: isDark ? 'Switch to light theme' : 'Switch to dark theme',
|
|
49
|
+
icon: isDark ? <SunIcon /> : <MoonIcon />,
|
|
50
|
+
pressed: isDark,
|
|
51
|
+
onSelect: toggleColorScheme,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'support',
|
|
55
|
+
label: 'Open support chat',
|
|
56
|
+
icon: <ChatIcon />,
|
|
57
|
+
onSelect: openSupportChat,
|
|
58
|
+
},
|
|
59
|
+
]}
|
|
60
|
+
preferences={{ icon: 'fingerprint', label: 'Manage privacy settings' }}
|
|
61
|
+
/>;
|
|
80
62
|
```
|
|
81
63
|
|
|
82
64
|
Each custom action needs a stable `id`, an accessible `label`, an `icon`, and an `onSelect` callback. Use `pressed` for toggle actions and `disabled` for unavailable ones. Your app owns the state behind each action. The preferences action moves to the edge nearest the toolbar's snapped corner while custom actions keep their configured order.
|
|
83
65
|
|
|
84
|
-
The toolbar does not depend on the consent policy. Your actions always render, so a theme toggle stays available while a banner is up. On the toolbar, `showWhen` applies only to the built-in preferences action: `after-prompt` hides that one item while a choice or notice is owed and shows it once the visitor has answered, and `never` leaves it out. The toolbar renders nothing only when it has no visible item. On the single `ConsentDialogTrigger`, `showWhen` applies to the whole control.
|
|
85
|
-
|
|
86
|
-
No policy, no UI. When no rule has resolved, because resolution failed, no rule matched and you set no default, or init is still withheld, the built-in preferences action and the single `ConsentDialogTrigger` render nothing; app-owned toolbar actions still do. They appear as soon as a rule resolves, without a remount. A rule with `model: 'none'` owes no rights, so the built-in action and the single trigger hide under it too, unless the rule adds `rights: ['preferences']`. The policy chooses what the control says, you choose where and when it sits, and it disappears only when there is nothing to manage.
|
|
87
|
-
|
|
88
|
-
The built-in action names the strongest right the active rule guarantees. Under a rule that carries the `opt-out` right, such as a US opt-out notice, its accessible name is the translated "Do not sell or share my data" and the button carries `data-right="opt-out"`. Under any other rule it reads "Manage preferences" with `data-right="preferences"`. Either way it opens the preference center, and a `preferences.label` you pass replaces the default. The button also carries `data-c15t-rights` with every right on the rule.
|
|
89
|
-
|
|
90
66
|
Toolbars are horizontal by default. Set `orientation="vertical"` to stack the actions and switch arrow-key navigation to up and down:
|
|
91
67
|
|
|
92
68
|
```tsx
|
|
93
|
-
<ConsentDialogTriggerToolbar orientation="vertical" actions={toolbarActions}
|
|
69
|
+
<ConsentDialogTriggerToolbar orientation="vertical" actions={toolbarActions} />;
|
|
94
70
|
```
|
|
95
71
|
|
|
72
|
+
### When the controls show
|
|
73
|
+
|
|
74
|
+
Your toolbar actions do not depend on the consent policy, so a theme toggle
|
|
75
|
+
stays available while a banner is up. The built-in preferences action, and the
|
|
76
|
+
single `ConsentDialogTrigger`, follow the policy:
|
|
77
|
+
|
|
78
|
+
| Situation | Built-in action and `ConsentDialogTrigger` |
|
|
79
|
+
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
80
|
+
| A choice or notice is still owed, with `showWhen="after-prompt"` | Hidden until the visitor answers |
|
|
81
|
+
| `showWhen="never"` on the toolbar | Left out of the toolbar |
|
|
82
|
+
| No policy rule has resolved: resolution failed, no rule matched and you set no default, or init is still withheld | Hidden; appears without a remount once a rule resolves |
|
|
83
|
+
| The rule has `model: 'none'` | Hidden, unless the rule adds `rights: ['preferences']` |
|
|
84
|
+
| Any other case | Shown |
|
|
85
|
+
|
|
86
|
+
On the toolbar, `showWhen` applies only to the built-in action. On the single
|
|
87
|
+
`ConsentDialogTrigger`, it applies to the whole control. The toolbar renders
|
|
88
|
+
nothing only when it has no visible item.
|
|
89
|
+
|
|
90
|
+
### What the built-in action says
|
|
91
|
+
|
|
92
|
+
* Under a rule with the `opt-out` right, such as a US opt-out notice, its
|
|
93
|
+
accessible name is the translated "Do not sell or share my data" and it
|
|
94
|
+
carries `data-right="opt-out"`.
|
|
95
|
+
* Under any other rule it reads "Manage preferences" and carries
|
|
96
|
+
`data-right="preferences"`.
|
|
97
|
+
* Either way it opens the preference center. A `preferences.label` you pass
|
|
98
|
+
replaces the default name, and the button carries `data-c15t-rights` with
|
|
99
|
+
every right on the rule.
|
|
100
|
+
|
|
96
101
|
### Toolbar props
|
|
97
102
|
|
|
98
103
|
| Prop | Type | Default | Description |
|
|
@@ -113,30 +118,30 @@ The toolbar follows the standard precedence: bundled styles, then provider slots
|
|
|
113
118
|
|
|
114
119
|
```tsx
|
|
115
120
|
<ConsentProvider
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
options={{
|
|
122
|
+
mode,
|
|
123
|
+
components: {
|
|
124
|
+
trigger: {
|
|
125
|
+
toolbar: { className: 'my-toolbar' },
|
|
126
|
+
toolbarItem: { className: 'my-toolbar-item' },
|
|
127
|
+
toolbarIcon: { className: 'my-toolbar-icon' },
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
}}
|
|
126
131
|
>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
</ConsentProvider
|
|
132
|
+
<ConsentDialogTriggerToolbar
|
|
133
|
+
className="fixed-toolbar"
|
|
134
|
+
actions={[
|
|
135
|
+
{
|
|
136
|
+
id: 'support',
|
|
137
|
+
label: 'Open support chat',
|
|
138
|
+
icon: <ChatIcon />,
|
|
139
|
+
onSelect: openSupportChat,
|
|
140
|
+
className: 'support-action',
|
|
141
|
+
},
|
|
142
|
+
]}
|
|
143
|
+
/>
|
|
144
|
+
</ConsentProvider>;
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
The toolbar slot keys are `trigger.toolbar`, `trigger.toolbarItem`, and `trigger.toolbarIcon`. With `noStyle`, style the current position and interaction state through `data-corner`, `data-dragging`, and `data-snapping` on the toolbar element.
|
|
@@ -14,32 +14,37 @@ group: frameworks
|
|
|
14
14
|
code.
|
|
15
15
|
|
|
16
16
|
```tsx title="src/consent.tsx"
|
|
17
|
-
import type { ReactNode } from 'react';
|
|
18
17
|
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
ConsentBanner,
|
|
19
|
+
ConsentDialog,
|
|
20
|
+
ConsentDialogLink,
|
|
21
|
+
ConsentProvider,
|
|
22
|
+
hosted,
|
|
24
23
|
} from 'c15t/react';
|
|
24
|
+
import type { ReactNode } from 'react';
|
|
25
|
+
|
|
26
|
+
import { scripts } from './scripts';
|
|
27
|
+
|
|
25
28
|
import 'c15t/react/styles.css';
|
|
26
29
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
31
|
+
|
|
32
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
33
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
34
|
+
{children}
|
|
35
|
+
<ConsentBanner />
|
|
36
|
+
<ConsentDialog />
|
|
37
|
+
<footer>
|
|
38
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
39
|
+
</footer>
|
|
40
|
+
</ConsentProvider>
|
|
41
|
+
);
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
To hide the "Secured by" tag in the dialog card, pass `hideBranding`:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<ConsentDialog hideBranding />;
|
|
43
48
|
```
|
|
44
49
|
|
|
45
50
|
To open it from your own component, set the active surface:
|
|
@@ -48,41 +53,41 @@ To open it from your own component, set the active surface:
|
|
|
48
53
|
import { useSetActiveUI } from 'c15t/react';
|
|
49
54
|
|
|
50
55
|
export function PrivacyMenuItem() {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
const setActiveUI = useSetActiveUI();
|
|
57
|
+
return (
|
|
58
|
+
<button type="button" onClick={() => setActiveUI('dialog')}>
|
|
59
|
+
Cookie preferences
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
57
62
|
}
|
|
58
63
|
```
|
|
59
64
|
|
|
60
65
|
`ConsentDialogLink` does the same with the policy's rights exposed for
|
|
61
66
|
styling; see [ConsentDialogLink](./consent-dialog-link.md). For a floating
|
|
62
67
|
button, see [ConsentDialogTrigger](./consent-dialog-trigger.md). Under an IAB
|
|
63
|
-
policy, mount `IABConsentDialog` from the [IAB guide](../iab
|
|
68
|
+
policy, mount `IABConsentDialog` from the [IAB guide](../iab.md)
|
|
64
69
|
instead; this dialog stays closed for the `iab` model.
|
|
65
70
|
|
|
66
71
|
## Props
|
|
67
72
|
|
|
68
|
-
| Prop | Type | Default | Description
|
|
69
|
-
| ------------------------- | ------------------------------------- | ------------------------------- |
|
|
70
|
-
| `open` | `boolean` | follows the active surface | Controls the open state. When set, Escape and the dialog's own Save, Accept
|
|
71
|
-
| `showTrigger` | `boolean \|ConsentDialogTriggerProps` | `false` | Renders a floating `ConsentDialogTrigger` next to the dialog. Pass an object to configure that trigger.
|
|
72
|
-
| `models` | `Model[]` | `['opt-in', 'opt-out', 'none']` | Policy models the dialog responds to. Under a model that is not listed, such as `iab`, it stays closed.
|
|
73
|
-
| `legalLinks` | `(keyof LegalLinks)[] \|null` | none | Which of the links configured in the provider `options.legalLinks` render after the description. Omitting the prop renders none. `null` hides them.
|
|
74
|
-
| `hideBranding` | `boolean` | `false` | Hides the "Secured by" tag in the card.
|
|
75
|
-
| `uiSource` | `string` | `'dialog'` | Source identifier recorded with saves made from this dialog.
|
|
76
|
-
| `scrollLock`, `trapFocus` | `boolean` | from `blocking` | Deprecated. Either one set to `false` makes the dialog non-blocking, unless the provider sets `presentation.preferences.blocking` explicitly, which wins. Prefer the provider option.
|
|
77
|
-
| `disableAnimation` | `boolean` | `false` | Skips the enter and exit animation of the backdrop and the card.
|
|
78
|
-
| `noStyle` | `boolean` | `false` | Removes the built-in styling from every part.
|
|
73
|
+
| Prop | Type | Default | Description |
|
|
74
|
+
| ------------------------- | ------------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `open` | `boolean` | follows the active surface | Controls the open state. When set, Escape and the dialog's own Save Settings, Accept All and Reject All buttons no longer close it; change the prop instead. A missing policy or an unlisted model still keeps it closed. |
|
|
76
|
+
| `showTrigger` | `boolean \|ConsentDialogTriggerProps` | `false` | Renders a floating `ConsentDialogTrigger` next to the dialog. Pass an object to configure that trigger. |
|
|
77
|
+
| `models` | `Model[]` | `['opt-in', 'opt-out', 'none']` | Policy models the dialog responds to. Under a model that is not listed, such as `iab`, it stays closed. |
|
|
78
|
+
| `legalLinks` | `(keyof LegalLinks)[] \|null` | none | Which of the links configured in the provider `options.legalLinks` render after the description. Omitting the prop renders none. `null` hides them. |
|
|
79
|
+
| `hideBranding` | `boolean` | `false` | Hides the "Secured by" tag in the card. |
|
|
80
|
+
| `uiSource` | `string` | `'dialog'` | Source identifier recorded with saves made from this dialog. |
|
|
81
|
+
| `scrollLock`, `trapFocus` | `boolean` | from `blocking` | Deprecated. Either one set to `false` makes the dialog non-blocking, unless the provider sets `presentation.preferences.blocking` explicitly, which wins. Prefer the provider option. |
|
|
82
|
+
| `disableAnimation` | `boolean` | `false` | Skips the enter and exit animation of the backdrop and the card. |
|
|
83
|
+
| `noStyle` | `boolean` | `false` | Removes the built-in styling from every part. |
|
|
79
84
|
|
|
80
85
|
## Behavior
|
|
81
86
|
|
|
82
87
|
The dialog is a modal wrapper around the preference center. Its card has a
|
|
83
88
|
header with the title `consentManagerDialog.title` and the description
|
|
84
89
|
`consentManagerDialog.description` followed by the legal links, then the
|
|
85
|
-
same category accordion and Reject
|
|
90
|
+
same category accordion and Reject All, Accept All and Save Settings actions that
|
|
86
91
|
`ConsentWidget` renders, then the branding tag. It mounts through a portal
|
|
87
92
|
into `document.body` after hydration, so it is never part of the server
|
|
88
93
|
HTML.
|
|
@@ -127,12 +132,12 @@ cached: the next hover, focus or open tries the download again.
|
|
|
127
132
|
The dialog's CSS travels in the same chunk and is applied before the dialog
|
|
128
133
|
renders, so your app's `styles.css` import stays small.
|
|
129
134
|
|
|
130
|
-
Without an `open` prop, Escape closes the dialog. Save, Accept
|
|
131
|
-
Reject
|
|
135
|
+
Without an `open` prop, Escape closes the dialog. Save Settings, Accept All and
|
|
136
|
+
Reject All close it as soon as the choice is recorded in the browser, in
|
|
132
137
|
the same task as the click; the exit animation still plays. The request to
|
|
133
138
|
the backend runs afterwards and never reopens the dialog: a failed request
|
|
134
139
|
keeps the choice, reports through `onError` and is retried later. See
|
|
135
|
-
[when a choice is saved](../../../
|
|
140
|
+
[when a choice is saved](../../../concepts/consent-state.md#when-a-choice-is-saved)
|
|
136
141
|
for the order of events. A draft that went stale because the policy changed keeps the dialog open
|
|
137
142
|
for review. With `open={true}`, these actions leave the dialog visible; the
|
|
138
143
|
parent must set `open={false}` to close it. Clicking the backdrop does not
|
|
@@ -168,11 +173,12 @@ The panel carries `role="dialog"`, `aria-labelledby="consent-dialog-title"`
|
|
|
168
173
|
and `aria-describedby="consent-dialog-description"`, plus `aria-modal="true"`
|
|
169
174
|
while it is blocking. Its `dir` attribute follows the active language.
|
|
170
175
|
|
|
171
|
-
While blocking, focus moves on open to the
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
+
While blocking, focus moves on open to the first tabbable control in the
|
|
177
|
+
panel, so a keyboard user sees the focus ring on a control rather than
|
|
178
|
+
around the whole panel; the `aria-labelledby` and `aria-describedby`
|
|
179
|
+
wiring means a screen reader still announces the title and description as
|
|
180
|
+
focus enters. Tab and Shift+Tab wrap inside the panel, and on close focus
|
|
181
|
+
returns to the element that opened the dialog. A non-blocking dialog manages no focus: nothing moves focus into the
|
|
176
182
|
panel or back to the opener. The backdrop is `aria-hidden` and is not
|
|
177
183
|
focusable.
|
|
178
184
|
|
|
@@ -227,7 +233,7 @@ download but every visitor downloads the dialog.
|
|
|
227
233
|
|
|
228
234
|
The compound parts import from `c15t/react` as properties of `ConsentDialog`,
|
|
229
235
|
and `ConsentWidget` from the same module. See
|
|
230
|
-
[
|
|
236
|
+
[customize](../customize.md) for slots and tokens.
|
|
231
237
|
|
|
232
238
|
## Verify
|
|
233
239
|
|
|
@@ -19,17 +19,17 @@ placeholder button opens.
|
|
|
19
19
|
import { ConsentGate } from 'c15t/react';
|
|
20
20
|
|
|
21
21
|
export function ProductVideo() {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
return (
|
|
23
|
+
<ConsentGate category="marketing" className="video-frame">
|
|
24
|
+
<iframe
|
|
25
|
+
src="https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
|
|
26
|
+
title="Product tour"
|
|
27
|
+
loading="lazy"
|
|
28
|
+
allowFullScreen
|
|
29
|
+
style={{ width: '100%', aspectRatio: '16 / 9', border: 0 }}
|
|
30
|
+
/>
|
|
31
|
+
</ConsentGate>
|
|
32
|
+
);
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
@@ -53,8 +53,8 @@ placeholder is replaced.
|
|
|
53
53
|
Other `div` attributes such as `className`, `style` and `ref` apply to the
|
|
54
54
|
wrapper element that stays in the page in both states. `ConsentGateProps` also
|
|
55
55
|
declares `noStyle` and `theme`, but the component does not apply them yet;
|
|
56
|
-
style the wrapper with `className
|
|
57
|
-
provider
|
|
56
|
+
style the wrapper with `className`, and style the built-in placeholder
|
|
57
|
+
through its parts in the provider options.
|
|
58
58
|
|
|
59
59
|
## Behavior
|
|
60
60
|
|
|
@@ -70,16 +70,17 @@ children unmount and the embed disappears. `ConsentGate` does not depend on the
|
|
|
70
70
|
network blocker or the iframe blocker modules; use those for markup you
|
|
71
71
|
cannot wrap in a component.
|
|
72
72
|
|
|
73
|
-
The built-in placeholder shows the title `
|
|
74
|
-
consent to view this content
|
|
75
|
-
`consentTypes.<category>.title`, and a button labelled
|
|
76
|
-
|
|
73
|
+
The built-in placeholder shows the title `consentGate.title`,
|
|
74
|
+
`Accept {category} consent to view this content.`, with `{category}`
|
|
75
|
+
replaced by `consentTypes.<category>.title`, and a button labelled
|
|
76
|
+
`consentGate.actionButton`, `Enable {category} consent`. The button opens the
|
|
77
|
+
preference center; it
|
|
77
78
|
does not grant the category by itself, because the visitor still has to
|
|
78
79
|
save. Mount a `ConsentDialog` in the same provider so the button has
|
|
79
80
|
something to open.
|
|
80
81
|
|
|
81
82
|
Under a policy with `scopeMode: 'strict'`, a category outside the rule's
|
|
82
|
-
scope cannot be granted. The built-in placeholder then shows `
|
|
83
|
+
scope cannot be granted. The built-in placeholder then shows `consentGate.policyBlocked`,
|
|
83
84
|
"This content is unavailable under your region's consent policy.", without
|
|
84
85
|
the button, and a stored grant for that category stays blocked.
|
|
85
86
|
|
|
@@ -126,8 +127,24 @@ translated text; pass children to either to replace it.
|
|
|
126
127
|
</ConsentGate>
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
130
|
+
To restyle the built-in placeholder without replacing it, set its parts
|
|
131
|
+
under `components['consent-gate']` in the provider options. `root` is the
|
|
132
|
+
card, and `title` and `button` are its text and button. The `consentGate`, `consentGateTitle` and
|
|
133
|
+
`consentGateButton` theme slots reach the same parts. The button part
|
|
134
|
+
applies on top of `button.primary`.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
components: {
|
|
138
|
+
'consent-gate': {
|
|
139
|
+
root: { className: 'rounded-none' },
|
|
140
|
+
button: { className: 'font-semibold' },
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The built-in placeholder carries `data-testid="consent-gate-placeholder"`,
|
|
146
|
+
its title `data-testid="consent-gate-title"` and its button
|
|
147
|
+
`data-testid="consent-gate-button"`.
|
|
131
148
|
|
|
132
149
|
## Accessibility
|
|
133
150
|
|