@tickboxhq/banner-default 0.1.0 → 0.1.2

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.
@@ -342,6 +342,31 @@ var TICKBOX_STYLES = `
342
342
  }
343
343
  .tb-link:hover { text-decoration: underline; }
344
344
 
345
+ .tb-branding {
346
+ /* flex-basis 100% breaks .tb-branding onto its own row inside the
347
+ wrapping flex banner so it doesn't sit next to the action buttons.
348
+ On .tb-notice (block layout) flex-basis is inert; the explicit
349
+ margin-top below handles the spacing there. */
350
+ flex-basis: 100%;
351
+ margin: 0;
352
+ padding-top: 4px;
353
+ font-size: 11px;
354
+ color: var(--tb-fg-muted);
355
+ text-align: center;
356
+ opacity: 0.55;
357
+ letter-spacing: 0.01em;
358
+ }
359
+ .tb-notice .tb-branding {
360
+ margin-top: 10px;
361
+ padding-top: 0;
362
+ text-align: right;
363
+ }
364
+ .tb-branding a {
365
+ color: inherit;
366
+ text-decoration: none;
367
+ }
368
+ .tb-branding a:hover { text-decoration: underline; opacity: 1; }
369
+
345
370
  .tb-btn {
346
371
  appearance: none;
347
372
  border: 1px solid transparent;
@@ -616,5 +641,5 @@ function mergeCopy(base, remote) {
616
641
  }
617
642
 
618
643
  export { applyThemeColors, injectStyles, locales, mergeCopy, readCachedTheme, refreshTheme, resolveLocalePack };
619
- //# sourceMappingURL=chunk-HCLSY5CT.js.map
620
- //# sourceMappingURL=chunk-HCLSY5CT.js.map
644
+ //# sourceMappingURL=chunk-SV4PBHBY.js.map
645
+ //# sourceMappingURL=chunk-SV4PBHBY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/locales/de.ts","../src/shared/locales/en.ts","../src/shared/locales/es.ts","../src/shared/locales/fr.ts","../src/shared/locales/it.ts","../src/shared/locales/nl.ts","../src/shared/locales/pl.ts","../src/shared/locales/pt.ts","../src/shared/locales/uk.ts","../src/shared/locales/index.ts","../src/shared/styles.ts","../src/shared/theme.ts"],"names":["banner","notice"],"mappings":"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","file":"chunk-SV4PBHBY.js","sourcesContent":["import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies und Tracking',\n description:\n 'Wir verwenden Cookies, damit diese Website funktioniert, und mit Ihrer Einwilligung, um die Nutzung zu messen. Sie können selbst wählen, was Sie zulassen.',\n acceptLabel: 'Alle akzeptieren',\n rejectLabel: 'Alle ablehnen',\n customiseLabel: 'Anpassen',\n saveLabel: 'Einstellungen speichern',\n closeLabel: 'Schließen',\n policyLinkLabel: 'Datenschutz',\n requiredBadge: 'Erforderlich',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Hinweis zur Analyse',\n description:\n 'Wir verwenden datenschutzfreundliche Analyse-Tools, um zu verstehen, wie diese Website genutzt wird. Es werden keine personenbezogenen Daten erhoben und keine Werbeprofile erstellt.',\n acknowledgeLabel: 'Verstanden',\n optOutLabel: 'Ablehnen',\n policyLinkLabel: 'Datenschutz',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies and tracking',\n description:\n 'We use cookies to make this site work and, with your consent, to measure usage. You can choose what to allow.',\n acceptLabel: 'Accept all',\n rejectLabel: 'Reject all',\n customiseLabel: 'Customise',\n saveLabel: 'Save preferences',\n closeLabel: 'Close',\n policyLinkLabel: 'Privacy policy',\n requiredBadge: 'Required',\n}\n\nexport const notice: NoticeCopy = {\n title: 'A note about analytics',\n description:\n 'We use privacy-friendly analytics to understand how this site is used. No personal data is collected and no advertising profiles are built.',\n acknowledgeLabel: 'Got it',\n optOutLabel: 'Opt out',\n policyLinkLabel: 'Privacy policy',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies y seguimiento',\n description:\n 'Utilizamos cookies para que este sitio funcione y, con tu consentimiento, para medir su uso. Tú eliges lo que permites.',\n acceptLabel: 'Aceptar todo',\n rejectLabel: 'Rechazar todo',\n customiseLabel: 'Personalizar',\n saveLabel: 'Guardar preferencias',\n closeLabel: 'Cerrar',\n policyLinkLabel: 'Política de privacidad',\n requiredBadge: 'Obligatorio',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Sobre la analítica',\n description:\n 'Utilizamos analítica respetuosa con la privacidad para entender cómo se usa este sitio. No recopilamos datos personales ni creamos perfiles publicitarios.',\n acknowledgeLabel: 'Entendido',\n optOutLabel: 'Rechazar',\n policyLinkLabel: 'Política de privacidad',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies et suivi',\n description:\n 'Nous utilisons des cookies pour faire fonctionner ce site et, avec votre consentement, pour mesurer son utilisation. Vous choisissez ce que vous autorisez.',\n acceptLabel: 'Tout accepter',\n rejectLabel: 'Tout refuser',\n customiseLabel: 'Personnaliser',\n saveLabel: 'Enregistrer les préférences',\n closeLabel: 'Fermer',\n policyLinkLabel: 'Politique de confidentialité',\n requiredBadge: 'Requis',\n}\n\nexport const notice: NoticeCopy = {\n title: \"À propos de l'analyse\",\n description:\n \"Nous utilisons une analyse respectueuse de la vie privée pour comprendre comment ce site est utilisé. Aucune donnée personnelle n'est collectée et aucun profil publicitaire n'est constitué.\",\n acknowledgeLabel: 'Compris',\n optOutLabel: 'Refuser',\n policyLinkLabel: 'Politique de confidentialité',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookie e tracciamento',\n description:\n \"Utilizziamo i cookie per far funzionare il sito e, con il tuo consenso, per misurarne l'utilizzo. Puoi scegliere cosa consentire.\",\n acceptLabel: 'Accetta tutto',\n rejectLabel: 'Rifiuta tutto',\n customiseLabel: 'Personalizza',\n saveLabel: 'Salva preferenze',\n closeLabel: 'Chiudi',\n policyLinkLabel: 'Informativa sulla privacy',\n requiredBadge: 'Obbligatorio',\n}\n\nexport const notice: NoticeCopy = {\n title: \"Nota sull'analisi\",\n description:\n 'Utilizziamo strumenti di analisi rispettosi della privacy per capire come viene usato questo sito. Non raccogliamo dati personali né creiamo profili pubblicitari.',\n acknowledgeLabel: 'Ho capito',\n optOutLabel: 'Rifiuta',\n policyLinkLabel: 'Informativa sulla privacy',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies en tracking',\n description:\n 'Wij gebruiken cookies om deze site te laten werken en, met uw toestemming, om het gebruik te meten. U kiest wat u toestaat.',\n acceptLabel: 'Alles accepteren',\n rejectLabel: 'Alles weigeren',\n customiseLabel: 'Aanpassen',\n saveLabel: 'Voorkeuren opslaan',\n closeLabel: 'Sluiten',\n policyLinkLabel: 'Privacybeleid',\n requiredBadge: 'Vereist',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Over analytics',\n description:\n 'Wij gebruiken privacyvriendelijke analytics om te begrijpen hoe deze site wordt gebruikt. Er worden geen persoonsgegevens verzameld en er worden geen advertentieprofielen opgebouwd.',\n acknowledgeLabel: 'Begrepen',\n optOutLabel: 'Afmelden',\n policyLinkLabel: 'Privacybeleid',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Pliki cookie i śledzenie',\n description:\n 'Używamy plików cookie, aby ta strona działała, oraz – za Twoją zgodą – aby mierzyć jej użycie. To Ty decydujesz, na co pozwolić.',\n acceptLabel: 'Zaakceptuj wszystkie',\n rejectLabel: 'Odrzuć wszystkie',\n customiseLabel: 'Dostosuj',\n saveLabel: 'Zapisz preferencje',\n closeLabel: 'Zamknij',\n policyLinkLabel: 'Polityka prywatności',\n requiredBadge: 'Wymagane',\n}\n\nexport const notice: NoticeCopy = {\n title: 'O analityce',\n description:\n 'Używamy analityki przyjaznej prywatności, aby zrozumieć, jak korzysta się z tej strony. Nie zbieramy danych osobowych ani nie tworzymy profili reklamowych.',\n acknowledgeLabel: 'Rozumiem',\n optOutLabel: 'Zrezygnuj',\n policyLinkLabel: 'Polityka prywatności',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies e rastreio',\n description:\n 'Utilizamos cookies para que este site funcione e, com o seu consentimento, para medir a sua utilização. Pode escolher o que permitir.',\n acceptLabel: 'Aceitar tudo',\n rejectLabel: 'Rejeitar tudo',\n customiseLabel: 'Personalizar',\n saveLabel: 'Guardar preferências',\n closeLabel: 'Fechar',\n policyLinkLabel: 'Política de privacidade',\n requiredBadge: 'Obrigatório',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Sobre a análise',\n description:\n 'Utilizamos análises respeitadoras da privacidade para perceber como este site é utilizado. Não recolhemos dados pessoais nem criamos perfis publicitários.',\n acknowledgeLabel: 'Entendi',\n optOutLabel: 'Recusar',\n policyLinkLabel: 'Política de privacidade',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Файли cookie та відстеження',\n description:\n 'Ми використовуємо файли cookie, щоб цей сайт працював, а за вашою згодою — щоб вимірювати використання. Ви самі обираєте, що дозволити.',\n acceptLabel: 'Прийняти всі',\n rejectLabel: 'Відхилити всі',\n customiseLabel: 'Налаштувати',\n saveLabel: 'Зберегти налаштування',\n closeLabel: 'Закрити',\n policyLinkLabel: 'Політика конфіденційності',\n requiredBadge: \"Обов'язково\",\n}\n\nexport const notice: NoticeCopy = {\n title: 'Про аналітику',\n description:\n 'Ми використовуємо аналітику, що поважає приватність, щоб зрозуміти, як використовується цей сайт. Ми не збираємо персональні дані й не створюємо рекламні профілі.',\n acknowledgeLabel: 'Зрозуміло',\n optOutLabel: 'Відмовитися',\n policyLinkLabel: 'Політика конфіденційності',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\nimport * as de from './de.js'\nimport * as en from './en.js'\nimport * as es from './es.js'\nimport * as fr from './fr.js'\nimport * as it from './it.js'\nimport * as nl from './nl.js'\nimport * as pl from './pl.js'\nimport * as pt from './pt.js'\nimport * as uk from './uk.js'\n\nexport type LocalePack = {\n banner: BannerCopy\n notice: NoticeCopy\n}\n\nexport const locales: Record<string, LocalePack> = {\n en: { banner: en.banner, notice: en.notice },\n de: { banner: de.banner, notice: de.notice },\n fr: { banner: fr.banner, notice: fr.notice },\n es: { banner: es.banner, notice: es.notice },\n it: { banner: it.banner, notice: it.notice },\n nl: { banner: nl.banner, notice: nl.notice },\n pt: { banner: pt.banner, notice: pt.notice },\n pl: { banner: pl.banner, notice: pl.notice },\n uk: { banner: uk.banner, notice: uk.notice },\n}\n\nexport type LocaleCode = keyof typeof locales\n\n/**\n * Resolve a BCP-47 locale tag to a built-in locale pack. Falls back from\n * the full tag (`en-GB`) to the language prefix (`en`), then to English.\n *\n * Pass `'auto'` to read `navigator.language` at call time. Anywhere that\n * `navigator` is missing (SSR, Node), `'auto'` falls back to English.\n */\nexport function resolveLocalePack(locale: string | undefined): LocalePack {\n if (!locale) return locales.en as LocalePack\n const tag = locale === 'auto' ? readNavigatorLanguage() : locale\n if (!tag) return locales.en as LocalePack\n const lower = tag.toLowerCase()\n if (locales[lower]) return locales[lower] as LocalePack\n const prefix = lower.split('-')[0]\n if (prefix && locales[prefix]) return locales[prefix] as LocalePack\n return locales.en as LocalePack\n}\n\nfunction readNavigatorLanguage(): string | undefined {\n if (typeof navigator === 'undefined') return undefined\n return navigator.language\n}\n","/**\n * Inline CSS for the default banner / notice / modal components.\n *\n * Uses CSS custom properties so users can re-theme without forking. Light\n * and dark themes are wired through `prefers-color-scheme` and the\n * `[data-tb-theme]` attribute.\n *\n * Visual style: GitHub-ish — system font, 6px corners, subtle border + soft\n * shadow, equal-prominence accept/reject buttons.\n */\nexport const TICKBOX_STYLES = `\n:where(.tb-root) {\n --tb-bg: #ffffff;\n --tb-fg: #1f2328;\n --tb-fg-muted: #59636e;\n --tb-border: #d1d9e0;\n --tb-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);\n --tb-primary-bg: #1f2328;\n --tb-primary-fg: #ffffff;\n --tb-primary-bg-hover: #000000;\n --tb-secondary-bg: #ffffff;\n --tb-secondary-fg: #1f2328;\n --tb-secondary-bg-hover: #f6f8fa;\n --tb-link: #0969da;\n --tb-radius: 6px;\n --tb-z: 2147483000;\n font-family:\n -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica,\n Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\";\n color: var(--tb-fg);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n@media (prefers-color-scheme: dark) {\n :where(.tb-root:not([data-tb-theme=\"light\"])) {\n --tb-bg: #0d1117;\n --tb-fg: #f0f6fc;\n --tb-fg-muted: #9198a1;\n --tb-border: #30363d;\n --tb-shadow: 0 8px 24px rgba(1, 4, 9, 0.85);\n --tb-primary-bg: #f0f6fc;\n --tb-primary-fg: #0d1117;\n --tb-primary-bg-hover: #ffffff;\n --tb-secondary-bg: #15191f;\n --tb-secondary-fg: #f0f6fc;\n --tb-secondary-bg-hover: #1f2328;\n --tb-link: #4493f8;\n }\n}\n:where(.tb-root[data-tb-theme=\"dark\"]) {\n --tb-bg: #0d1117;\n --tb-fg: #f0f6fc;\n --tb-fg-muted: #9198a1;\n --tb-border: #30363d;\n --tb-shadow: 0 8px 24px rgba(1, 4, 9, 0.85);\n --tb-primary-bg: #f0f6fc;\n --tb-primary-fg: #0d1117;\n --tb-primary-bg-hover: #ffffff;\n --tb-secondary-bg: #15191f;\n --tb-secondary-fg: #f0f6fc;\n --tb-secondary-bg-hover: #1f2328;\n --tb-link: #4493f8;\n}\n\n.tb-banner {\n position: fixed;\n left: 16px;\n right: 16px;\n bottom: 16px;\n max-width: 1100px;\n margin-inline: auto;\n z-index: var(--tb-z);\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n padding: 16px 20px;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-banner-text {\n flex: 1 1 320px;\n min-width: 0;\n}\n.tb-banner-title {\n font-weight: 600;\n margin: 0 0 2px;\n font-size: 14px;\n}\n.tb-banner-desc {\n margin: 0;\n color: var(--tb-fg-muted);\n}\n.tb-banner-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.tb-notice {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: var(--tb-z);\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n padding: 14px 16px;\n max-width: 360px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-notice-title {\n font-weight: 600;\n margin: 0 0 4px;\n font-size: 14px;\n}\n.tb-notice-desc {\n margin: 0 0 10px;\n color: var(--tb-fg-muted);\n font-size: 13px;\n}\n.tb-notice-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n\n.tb-link {\n color: var(--tb-link);\n text-decoration: none;\n font-size: 13px;\n margin-right: auto;\n}\n.tb-link:hover { text-decoration: underline; }\n\n.tb-branding {\n /* flex-basis 100% breaks .tb-branding onto its own row inside the\n wrapping flex banner so it doesn't sit next to the action buttons.\n On .tb-notice (block layout) flex-basis is inert; the explicit\n margin-top below handles the spacing there. */\n flex-basis: 100%;\n margin: 0;\n padding-top: 4px;\n font-size: 11px;\n color: var(--tb-fg-muted);\n text-align: center;\n opacity: 0.55;\n letter-spacing: 0.01em;\n}\n.tb-notice .tb-branding {\n margin-top: 10px;\n padding-top: 0;\n text-align: right;\n}\n.tb-branding a {\n color: inherit;\n text-decoration: none;\n}\n.tb-branding a:hover { text-decoration: underline; opacity: 1; }\n\n.tb-btn {\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--tb-radius);\n padding: 6px 14px;\n font-size: 13px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n line-height: 1.5;\n transition: background-color 80ms ease;\n white-space: nowrap;\n}\n.tb-btn:focus-visible {\n outline: 2px solid var(--tb-link);\n outline-offset: 2px;\n}\n.tb-btn-primary {\n background: var(--tb-primary-bg);\n color: var(--tb-primary-fg);\n}\n.tb-btn-primary:hover { background: var(--tb-primary-bg-hover); }\n.tb-btn-secondary {\n background: var(--tb-secondary-bg);\n color: var(--tb-secondary-fg);\n border-color: var(--tb-border);\n}\n.tb-btn-secondary:hover { background: var(--tb-secondary-bg-hover); }\n/*\n * Style for Accept All and Reject All on the first banner layer. They MUST\n * look identical — UK ICO and EU EDPB treat unequal visual weight on those\n * buttons as a dark pattern. Customisation should not break this symmetry;\n * if you need to apply brand colours, apply them here, not to one button.\n */\n.tb-btn-equal {\n background: var(--tb-secondary-bg);\n color: var(--tb-secondary-fg);\n border-color: var(--tb-border);\n}\n.tb-btn-equal:hover { background: var(--tb-secondary-bg-hover); }\n.tb-btn-ghost {\n background: transparent;\n color: var(--tb-fg-muted);\n padding: 6px 10px;\n}\n.tb-btn-ghost:hover { color: var(--tb-fg); }\n\n.tb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(15, 18, 24, 0.5);\n z-index: var(--tb-z);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-modal {\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n width: 100%;\n max-width: 520px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n}\n.tb-modal-head {\n padding: 14px 16px;\n border-bottom: 1px solid var(--tb-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n.tb-modal-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n.tb-modal-body {\n padding: 12px 16px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.tb-modal-foot {\n padding: 12px 16px;\n border-top: 1px solid var(--tb-border);\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n\n.tb-cat {\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n padding: 12px;\n display: flex;\n gap: 12px;\n align-items: flex-start;\n}\n.tb-cat-text { flex: 1; min-width: 0; }\n.tb-cat-name {\n font-weight: 600;\n margin: 0 0 2px;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.tb-cat-desc {\n margin: 0;\n color: var(--tb-fg-muted);\n font-size: 13px;\n}\n.tb-badge {\n display: inline-block;\n font-size: 11px;\n font-weight: 500;\n color: var(--tb-fg-muted);\n background: var(--tb-secondary-bg-hover);\n border: 1px solid var(--tb-border);\n border-radius: 999px;\n padding: 1px 8px;\n}\n\n.tb-switch {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n margin-top: 2px;\n}\n.tb-switch input {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute;\n}\n.tb-switch-track {\n position: absolute;\n inset: 0;\n background: var(--tb-border);\n border-radius: 999px;\n transition: background-color 100ms ease;\n cursor: pointer;\n}\n.tb-switch-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n background: var(--tb-bg);\n border-radius: 50%;\n transition: transform 100ms ease;\n}\n.tb-switch input:checked + .tb-switch-track {\n background: var(--tb-primary-bg);\n}\n.tb-switch input:checked + .tb-switch-track .tb-switch-thumb {\n transform: translateX(14px);\n}\n.tb-switch input:disabled + .tb-switch-track {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.tb-switch input:focus-visible + .tb-switch-track {\n outline: 2px solid var(--tb-link);\n outline-offset: 2px;\n}\n\n@keyframes tb-fade-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n@media (max-width: 640px) {\n .tb-banner {\n flex-direction: column;\n align-items: stretch;\n }\n .tb-banner-actions {\n flex-direction: column;\n }\n .tb-banner-actions .tb-btn { width: 100%; }\n}\n`\n\nconst STYLE_ID = 'tickbox-default-styles'\n\nlet injected = false\n\n/**\n * Insert the stylesheet into `<head>` exactly once per page. Safe to call\n * from every component mount — subsequent calls are no-ops. No-op on the\n * server (no `document`).\n */\nexport function injectStyles(): void {\n if (injected) return\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID)) {\n injected = true\n return\n }\n const el = document.createElement('style')\n el.id = STYLE_ID\n el.textContent = TICKBOX_STYLES\n document.head.appendChild(el)\n injected = true\n}\n","/**\n * Runtime theme fetcher + cache for @tickboxhq/banner-default.\n *\n * The compliance contract (jurisdiction, categories, vendors) stays in the\n * customer's consent.config.ts under PR review. Presentation only — copy,\n * colours, locale — can be edited remotely in the dashboard and fetched\n * by the SDK at runtime so marketing teams can iterate without a deploy.\n *\n * Fetch strategy: read cached theme from localStorage and apply\n * synchronously on mount (no flicker), then refresh in the background.\n * Updated cache is written back; the *next* page load picks up the change.\n * Failed fetches are silent — the banner keeps rendering with whatever\n * cache or code defaults it had.\n */\n\nconst DEFAULT_ENDPOINT = 'https://api.tickbox.dev'\nconst STORAGE_KEY_PREFIX = '__tb_theme_'\n/** Treat cache older than 24h as suspect. We still render with it (no\n * flicker), but we always refetch — same as a CDN with stale-while-revalidate. */\nconst STALE_AFTER_MS = 24 * 60 * 60 * 1000\n\nexport type RemoteTheme = {\n copy?: {\n title?: string\n body?: string\n buttonAccept?: string\n buttonReject?: string\n buttonCustomise?: string\n privacyLink?: string\n }\n colors?: {\n accent?: string\n background?: string\n text?: string\n }\n locale?: string\n}\n\ntype CachedTheme = {\n theme: RemoteTheme\n fetchedAt: number\n}\n\n/** Synchronous read of the last cached theme. Used on mount to render\n * immediately without waiting for a network round-trip. */\nexport function readCachedTheme(siteId: string): RemoteTheme | null {\n if (typeof localStorage === 'undefined') return null\n try {\n const raw = localStorage.getItem(STORAGE_KEY_PREFIX + siteId)\n if (!raw) return null\n const parsed = JSON.parse(raw) as CachedTheme\n if (parsed && typeof parsed === 'object' && parsed.theme) {\n return parsed.theme\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Fetch the latest theme from the API, write to cache. Returns the fresh\n * theme on success, `null` on any failure (caller already rendered the\n * cached one). */\nexport async function refreshTheme(\n siteId: string,\n endpoint: string = DEFAULT_ENDPOINT,\n): Promise<RemoteTheme | null> {\n if (typeof fetch === 'undefined') return null\n const url = `${endpoint.replace(/\\/+$/, '')}/v1/theme/${encodeURIComponent(siteId)}`\n try {\n const res = await fetch(url, { method: 'GET' })\n if (!res.ok) return null\n const body = (await res.json()) as { theme?: RemoteTheme }\n const theme = body?.theme ?? {}\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(\n STORAGE_KEY_PREFIX + siteId,\n JSON.stringify({ theme, fetchedAt: Date.now() } satisfies CachedTheme),\n )\n } catch {\n // Storage quota or disabled — fail silently. The banner still\n // renders with whatever's currently in memory.\n }\n }\n return theme\n } catch {\n return null\n }\n}\n\n/** Indicates whether the cached theme is older than the stale-after window.\n * Currently informational; we refetch on every mount regardless. Exposed\n * so future call sites can decide to skip the refetch (e.g. SSR). */\nexport function isStale(siteId: string): boolean {\n if (typeof localStorage === 'undefined') return true\n try {\n const raw = localStorage.getItem(STORAGE_KEY_PREFIX + siteId)\n if (!raw) return true\n const parsed = JSON.parse(raw) as CachedTheme\n return Date.now() - parsed.fetchedAt > STALE_AFTER_MS\n } catch {\n return true\n }\n}\n\n/**\n * Apply a theme's colour overrides as CSS custom properties on the banner\n * root element. The banner's own stylesheet reads these variables (or falls\n * back to defaults if unset), so theming is a matter of setting the right\n * --tb-* values.\n *\n * sanitise() drops anything outside the CSS-colour character set as defence\n * in depth against attribute injection — the values arrive via fetch from\n * api.tickbox.dev, so a compromise of the dashboard could only inject\n * benign CSS at worst.\n */\nexport function applyThemeColors(root: HTMLElement, theme: RemoteTheme | null): void {\n if (!theme?.colors) return\n if (theme.colors.accent)\n root.style.setProperty('--tb-primary-bg', sanitiseColor(theme.colors.accent))\n if (theme.colors.background)\n root.style.setProperty('--tb-bg', sanitiseColor(theme.colors.background))\n if (theme.colors.text) root.style.setProperty('--tb-fg', sanitiseColor(theme.colors.text))\n}\n\nfunction sanitiseColor(v: string): string {\n // Permit common CSS-colour characters: hex digits, color() / rgb() /\n // hsl() / oklch() function tokens. Block <, >, &, \", ' and newlines.\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n\n/**\n * Merge a RemoteTheme's `copy` block into a partial BannerCopy passed by\n * the caller (the customer's `copy` prop on <ConsentBannerDefault>). The\n * remote theme wins where set; code defaults fill in the rest at render\n * time via resolveLocalePack.\n */\nexport function mergeCopy<T extends Record<string, string>>(\n base: Partial<T>,\n remote: RemoteTheme['copy'] | undefined,\n): Partial<T> {\n if (!remote) return base\n const out: Record<string, string> = { ...(base as Record<string, string>) }\n if (remote.title) out.title = remote.title\n if (remote.body) out.description = remote.body\n if (remote.buttonAccept) out.acceptLabel = remote.buttonAccept\n if (remote.buttonReject) out.rejectLabel = remote.buttonReject\n if (remote.buttonCustomise) out.customiseLabel = remote.buttonCustomise\n if (remote.privacyLink) out.policyLinkLabel = remote.privacyLink\n return out as Partial<T>\n}\n"]}
@@ -37,6 +37,14 @@ type ConsentBannerDefaultProps = {
37
37
  siteId?: string;
38
38
  /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
39
39
  cloudEndpoint?: string;
40
+ /**
41
+ * When true, adds a small "Powered by Tickbox" link to tickbox.dev under
42
+ * the banner. Off by default — most sites won't want third-party
43
+ * branding on a compliance widget. Local-only setting; not part of the
44
+ * remote-theme payload, since whether to credit the SDK is a code-level
45
+ * decision, not a marketing-team one.
46
+ */
47
+ branding?: boolean;
40
48
  };
41
49
  /**
42
50
  * Drop-in styled consent banner. Mounts itself only when the headless
@@ -70,6 +78,20 @@ type ConsentNoticeDefaultProps = {
70
78
  optOutCategoryId?: string;
71
79
  /** Force light or dark theme. */
72
80
  theme?: 'light' | 'dark';
81
+ /**
82
+ * Site identifier from app.tickbox.dev. When set, the notice fetches its
83
+ * remote presentation theme (copy + colours) on mount the same way the
84
+ * banner does, so marketing-team-edited copy lands without a redeploy.
85
+ */
86
+ siteId?: string;
87
+ /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
88
+ cloudEndpoint?: string;
89
+ /**
90
+ * When true, adds a small "Powered by Tickbox" link to tickbox.dev under
91
+ * the notice. Off by default — see ConsentBannerDefault.branding for the
92
+ * reasoning.
93
+ */
94
+ branding?: boolean;
73
95
  };
74
96
  /**
75
97
  * Drop-in styled notice card for sites that have only `notice`-mode
@@ -1,5 +1,5 @@
1
- import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-HCLSY5CT.js';
2
- export { locales, resolveLocalePack } from '../chunk-HCLSY5CT.js';
1
+ import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-SV4PBHBY.js';
2
+ export { locales, resolveLocalePack } from '../chunk-SV4PBHBY.js';
3
3
  import { ConsentBanner, ConsentNotice } from '@tickboxhq/react';
4
4
  import { useState, useEffect, useRef, useId } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -54,6 +54,11 @@ function BannerInner({
54
54
  /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.denyAll(), children: copy.rejectLabel }),
55
55
  /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-ghost", onClick: () => setShowModal(true), children: copy.customiseLabel }),
56
56
  /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.grantAll(), children: copy.acceptLabel })
57
+ ] }),
58
+ props.branding && /* @__PURE__ */ jsxs("p", { className: "tb-branding", children: [
59
+ "Powered by",
60
+ " ",
61
+ /* @__PURE__ */ jsx("a", { href: "https://tickbox.dev", target: "_blank", rel: "noopener noreferrer", children: "Tickbox" })
57
62
  ] })
58
63
  ]
59
64
  }
@@ -205,15 +210,34 @@ function ConsentNoticeDefault(props) {
205
210
  return /* @__PURE__ */ jsx(ConsentNotice, { children: (api) => /* @__PURE__ */ jsx(NoticeInner, { api, props }) });
206
211
  }
207
212
  function NoticeInner({ api, props }) {
208
- const copy = { ...resolveLocalePack(props.locale).notice, ...props.copy };
209
- const optOutId = props.optOutCategoryId ?? "analytics";
213
+ const rootRef = useRef(null);
214
+ const [remoteTheme, setRemoteTheme] = useState(
215
+ () => props.siteId ? readCachedTheme(props.siteId) : null
216
+ );
210
217
  useEffect(() => {
211
218
  injectStyles();
212
- }, []);
219
+ if (!props.siteId) return;
220
+ let cancelled = false;
221
+ (async () => {
222
+ const fresh = await refreshTheme(props.siteId, props.cloudEndpoint);
223
+ if (!cancelled && fresh) setRemoteTheme(fresh);
224
+ })();
225
+ return () => {
226
+ cancelled = true;
227
+ };
228
+ }, [props.siteId, props.cloudEndpoint]);
229
+ useEffect(() => {
230
+ if (rootRef.current) applyThemeColors(rootRef.current, remoteTheme);
231
+ }, [remoteTheme]);
232
+ const mergedCopyOverrides = mergeCopy(props.copy ?? {}, remoteTheme?.copy);
233
+ const locale = remoteTheme?.locale ?? props.locale;
234
+ const copy = { ...resolveLocalePack(locale).notice, ...mergedCopyOverrides };
235
+ const optOutId = props.optOutCategoryId ?? "analytics";
213
236
  const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
214
237
  return /* @__PURE__ */ jsxs(
215
238
  "div",
216
239
  {
240
+ ref: rootRef,
217
241
  className: "tb-root tb-notice",
218
242
  role: "status",
219
243
  "aria-live": "polite",
@@ -239,6 +263,11 @@ function NoticeInner({ api, props }) {
239
263
  }
240
264
  ),
241
265
  /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-primary", onClick: () => api.save(), children: copy.acknowledgeLabel })
266
+ ] }),
267
+ props.branding && /* @__PURE__ */ jsxs("p", { className: "tb-branding", children: [
268
+ "Powered by",
269
+ " ",
270
+ /* @__PURE__ */ jsx("a", { href: "https://tickbox.dev", target: "_blank", rel: "noopener noreferrer", children: "Tickbox" })
242
271
  ] })
243
272
  ]
244
273
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/react/banner.tsx","../../src/react/notice.tsx"],"names":["jsx","useEffect","jsxs"],"mappings":";;;;;;AA6DO,SAAS,qBAAqB,KAAA,EAAkC;AACrE,EAAA,uBAAO,GAAA,CAAC,iBAAe,QAAA,EAAA,CAAC,GAAA,yBAAS,WAAA,EAAA,EAAY,GAAA,EAAU,OAAc,CAAA,EAAG,CAAA;AAC1E;AAEA,SAAS,WAAA,CAAY;AAAA,EACnB,GAAA;AAAA,EACA;AACF,CAAA,EAGG;AAGD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA;AAAA,IAA6B,MACjE,KAAA,CAAM,MAAA,GAAS,eAAA,CAAgB,KAAA,CAAM,MAAM,CAAA,GAAI;AAAA,GACjD;AAEA,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,YAAA,EAAa;AACb,IAAA,IAAI,CAAC,MAAM,MAAA,EAAQ;AACnB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,KAAA,CAAM,YAAY;AAChB,MAAA,MAAM,QAAQ,MAAM,YAAA,CAAa,KAAA,CAAM,MAAA,EAAS,MAAM,aAAa,CAAA;AACnE,MAAA,IAAI,CAAC,SAAA,IAAa,KAAA,EAAO,cAAA,CAAe,KAAK,CAAA;AAAA,IAC/C,CAAA,GAAG;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,GAAG,CAAC,KAAA,CAAM,MAAA,EAAQ,KAAA,CAAM,aAAa,CAAC,CAAA;AAEtC,EAAA,MAAM,sBAAsB,SAAA,CAAU,KAAA,CAAM,QAAQ,EAAC,EAAG,aAAa,IAAI,CAAA;AACzE,EAAA,MAAM,MAAA,GAAS,WAAA,EAAa,MAAA,IAAU,KAAA,CAAM,MAAA;AAC5C,EAAA,MAAM,IAAA,GAAmB,EAAE,GAAG,iBAAA,CAAkB,MAAM,CAAA,CAAE,MAAA,EAAQ,GAAG,mBAAA,EAAoB;AAEvF,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,OAAA,GAAU,OAA8B,IAAI,CAAA;AAElD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAA,CAAQ,OAAA,EAAS,gBAAA,CAAiB,OAAA,CAAQ,SAAS,WAAW,CAAA;AAAA,EACpE,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,GAAQ,EAAE,iBAAiB,KAAA,CAAM,KAAA,KAAU,EAAC;AAErE,EAAA,uBACE,IAAA,CAAA,QAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAA,IAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,OAAA;AAAA,QACL,SAAA,EAAU,mBAAA;AAAA,QACV,IAAA,EAAK,QAAA;AAAA,QACL,cAAY,IAAA,CAAK,KAAA;AAAA,QAChB,GAAG,UAAA;AAAA,QAEJ,QAAA,EAAA;AAAA,0BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,gBAAA,EACb,QAAA,EAAA;AAAA,4BAAA,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iBAAA,EAAmB,QAAA,EAAA,IAAA,CAAK,KAAA,EAAM,CAAA;AAAA,4BAC3C,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,gBAAA,EAAkB,eAAK,WAAA,EAAY;AAAA,WAAA,EAClD,CAAA;AAAA,0BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EACZ,QAAA,EAAA;AAAA,YAAA,KAAA,CAAM,SAAA,wBACJ,GAAA,EAAA,EAAE,SAAA,EAAU,WAAU,IAAA,EAAM,KAAA,CAAM,SAAA,EAChC,QAAA,EAAA,IAAA,CAAK,eAAA,EACR,CAAA;AAAA,4BAEF,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,qBAAA,EAAsB,OAAA,EAAS,MAAM,GAAA,CAAI,OAAA,EAAQ,EAC9E,QAAA,EAAA,IAAA,CAAK,WAAA,EACR,CAAA;AAAA,4BACA,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,qBAAA,EAAsB,OAAA,EAAS,MAAM,YAAA,CAAa,IAAI,CAAA,EACnF,QAAA,EAAA,IAAA,CAAK,cAAA,EACR,CAAA;AAAA,4BACA,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,qBAAA,EAAsB,OAAA,EAAS,MAAM,GAAA,CAAI,QAAA,EAAS,EAC/E,QAAA,EAAA,IAAA,CAAK,WAAA,EACR;AAAA,WAAA,EACF;AAAA;AAAA;AAAA,KACF;AAAA,IACC,SAAA,oBACC,GAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,IAAA;AAAA,QACA,OAAO,KAAA,CAAM,KAAA;AAAA,QACb,WAAA;AAAA,QACA,OAAA,EAAS,MAAM,YAAA,CAAa,KAAK;AAAA;AAAA;AACnC,GAAA,EAEJ,CAAA;AAEJ;AAEA,SAAS,cAAA,CAAe;AAAA,EACtB,GAAA;AAAA,EACA,IAAA;AAAA,EACA,KAAA;AAAA,EACA,WAAA;AAAA,EACA;AACF,CAAA,EAMG;AACD,EAAA,MAAM,UAAU,KAAA,EAAM;AACtB,EAAA,MAAM,YAAA,GAAe,OAA8B,IAAI,CAAA;AAEvD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,SAAS,MAAM,CAAA,EAAkB;AAC/B,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,EAAQ;AAChC,MAAA,IAAI,EAAE,GAAA,KAAQ,KAAA,EAAO,SAAA,CAAU,CAAA,EAAG,aAAa,OAAO,CAAA;AAAA,IACxD;AACA,IAAA,QAAA,CAAS,gBAAA,CAAiB,WAAW,KAAK,CAAA;AAC1C,IAAA,MAAM,oBAAoB,QAAA,CAAS,aAAA;AACnC,IAAA,MAAM,KAAA,GAAQ,aAAa,OAAA,EAAS,aAAA;AAAA,MAClC;AAAA,KACF;AACA,IAAA,KAAA,EAAO,KAAA,EAAM;AACb,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,mBAAA,CAAoB,WAAW,KAAK,CAAA;AAC7C,MAAA,iBAAA,EAAmB,KAAA,IAAQ;AAAA,IAC7B,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,aAAa,KAAA,GAAQ,EAAE,eAAA,EAAiB,KAAA,KAAU,EAAC;AAMzD,EAAA,MAAM,aAAqC,EAAC;AAC5C,EAAA,IAAI,WAAA,EAAa,QAAQ,MAAA,EAAQ;AAC/B,IAAA,UAAA,CAAW,iBAAiB,CAAA,GAAI,aAAA,CAAc,WAAA,CAAY,OAAO,MAAM,CAAA;AAAA,EACzE;AACA,EAAA,IAAI,WAAA,EAAa,QAAQ,UAAA,EAAY;AACnC,IAAA,UAAA,CAAW,SAAS,CAAA,GAAI,aAAA,CAAc,WAAA,CAAY,OAAO,UAAU,CAAA;AAAA,EACrE;AACA,EAAA,IAAI,WAAA,EAAa,QAAQ,IAAA,EAAM;AAC7B,IAAA,UAAA,CAAW,SAAS,CAAA,GAAI,aAAA,CAAc,WAAA,CAAY,OAAO,IAAI,CAAA;AAAA,EAC/D;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,2BAAA;AAAA,MACV,KAAA,EAAO,UAAA;AAAA,MACP,OAAA,EAAS,OAAA;AAAA,MACT,SAAA,EAAW,CAAC,CAAA,KAAM;AAChB,QAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU,OAAA,EAAQ;AAAA,MAClC,CAAA;AAAA,MACA,IAAA,EAAK,cAAA;AAAA,MACJ,GAAG,UAAA;AAAA,MAEJ,QAAA,kBAAA,IAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,GAAA,EAAK,YAAA;AAAA,UACL,SAAA,EAAU,UAAA;AAAA,UACV,IAAA,EAAK,QAAA;AAAA,UACL,YAAA,EAAW,MAAA;AAAA,UACX,iBAAA,EAAiB,OAAA;AAAA,UACjB,OAAA,EAAS,CAAC,CAAA,KAAM,CAAA,CAAE,eAAA,EAAgB;AAAA,UAClC,SAAA,EAAW,CAAC,CAAA,KAAM,CAAA,CAAE,eAAA,EAAgB;AAAA,UAEpC,QAAA,EAAA;AAAA,4BAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,eAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,QAAG,EAAA,EAAI,OAAA,EAAS,SAAA,EAAU,gBAAA,EACxB,eAAK,cAAA,EACR,CAAA;AAAA,8BACA,GAAA;AAAA,gBAAC,QAAA;AAAA,gBAAA;AAAA,kBACC,IAAA,EAAK,QAAA;AAAA,kBACL,SAAA,EAAU,qBAAA;AAAA,kBACV,cAAY,IAAA,CAAK,UAAA;AAAA,kBACjB,OAAA,EAAS,OAAA;AAAA,kBACV,QAAA,EAAA;AAAA;AAAA;AAED,aAAA,EACF,CAAA;AAAA,4BACA,GAAA,CAAC,SAAI,SAAA,EAAU,eAAA,EACZ,cAAI,QAAA,CAAS,GAAA,CAAI,CAAC,GAAA,KAAQ;AACzB,cAAA,MAAM,OAAA,GAAU,GAAA,CAAI,SAAA,CAAU,GAAA,CAAI,EAAE,CAAA,KAAM,IAAA;AAC1C,cAAA,MAAM,EAAA,GAAK,CAAA,OAAA,EAAU,GAAA,CAAI,EAAE,CAAA,CAAA;AAC3B,cAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAiB,SAAA,EAAU,QAAA,EAC1B,QAAA,EAAA;AAAA,gCAAA,IAAA,CAAC,KAAA,EAAA,EAAI,WAAU,aAAA,EACb,QAAA,EAAA;AAAA,kCAAA,IAAA,CAAC,GAAA,EAAA,EAAE,WAAU,aAAA,EACX,QAAA,EAAA;AAAA,oCAAA,GAAA,CAAC,OAAA,EAAA,EAAM,OAAA,EAAS,EAAA,EAAK,QAAA,EAAA,GAAA,CAAI,EAAA,EAAG,CAAA;AAAA,oBAC3B,IAAI,QAAA,oBAAY,GAAA,CAAC,UAAK,SAAA,EAAU,UAAA,EAAY,eAAK,aAAA,EAAc;AAAA,mBAAA,EAClE,CAAA;AAAA,kBACC,IAAI,WAAA,oBAAe,GAAA,CAAC,OAAE,SAAA,EAAU,aAAA,EAAe,cAAI,WAAA,EAAY;AAAA,iBAAA,EAClE,CAAA;AAAA,gCACA,IAAA,CAAC,OAAA,EAAA,EAAM,SAAA,EAAU,WAAA,EACf,QAAA,EAAA;AAAA,kCAAA,GAAA;AAAA,oBAAC,OAAA;AAAA,oBAAA;AAAA,sBACC,EAAA;AAAA,sBACA,IAAA,EAAK,UAAA;AAAA,sBACL,OAAA;AAAA,sBACA,UAAU,GAAA,CAAI,QAAA;AAAA,sBACd,QAAA,EAAU,CAAC,CAAA,KAAM;AACf,wBAAA,IAAI,EAAE,MAAA,CAAO,OAAA,EAAS,GAAA,CAAI,KAAA,CAAM,IAAI,EAAE,CAAA;AAAA,6BACjC,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,EAAE,CAAA;AAAA,sBACtB;AAAA;AAAA,mBACF;AAAA,kCACA,GAAA,CAAC,UAAK,SAAA,EAAU,iBAAA,EACd,8BAAC,MAAA,EAAA,EAAK,SAAA,EAAU,mBAAkB,CAAA,EACpC;AAAA,iBAAA,EACF;AAAA,eAAA,EAAA,EAtBQ,IAAI,EAuBd,CAAA;AAAA,YAEJ,CAAC,CAAA,EACH,CAAA;AAAA,4BACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EACb,QAAA,EAAA;AAAA,8BAAA,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,qBAAA,EAAsB,OAAA,EAAS,MAAM,GAAA,CAAI,OAAA,EAAQ,EAC9E,QAAA,EAAA,IAAA,CAAK,WAAA,EACR,CAAA;AAAA,8BACA,GAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,uBAAA,EAAwB,OAAA,EAAS,MAAM,GAAA,CAAI,IAAA,EAAK,EAC7E,QAAA,EAAA,IAAA,CAAK,SAAA,EACR;AAAA,aAAA,EACF;AAAA;AAAA;AAAA;AACF;AAAA,GACF;AAEJ;AAEA,SAAS,SAAA,CAAU,GAAkB,SAAA,EAAkC;AACrE,EAAA,IAAI,CAAC,SAAA,EAAW;AAChB,EAAA,MAAM,aAAa,SAAA,CAAU,gBAAA;AAAA,IAC3B;AAAA,GACF;AACA,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC7B,EAAA,MAAM,KAAA,GAAQ,WAAW,CAAC,CAAA;AAC1B,EAAA,MAAM,IAAA,GAAO,UAAA,CAAW,UAAA,CAAW,MAAA,GAAS,CAAC,CAAA;AAC7C,EAAA,IAAI,CAAC,KAAA,IAAS,CAAC,IAAA,EAAM;AACrB,EAAA,MAAM,SAAS,QAAA,CAAS,aAAA;AACxB,EAAA,IAAI,CAAA,CAAE,QAAA,IAAY,MAAA,KAAW,KAAA,EAAO;AAClC,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,IAAA,CAAK,KAAA,EAAM;AAAA,EACb,CAAA,MAAA,IAAW,CAAC,CAAA,CAAE,QAAA,IAAY,WAAW,IAAA,EAAM;AACzC,IAAA,CAAA,CAAE,cAAA,EAAe;AACjB,IAAA,KAAA,CAAM,KAAA,EAAM;AAAA,EACd;AACF;AAEA,SAAS,cAAc,CAAA,EAAmB;AACxC,EAAA,OAAO,EAAE,OAAA,CAAQ,gBAAA,EAAkB,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AACpD;AClQO,SAAS,qBAAqB,KAAA,EAAkC;AACrE,EAAA,uBAAOA,GAAAA,CAAC,aAAA,EAAA,EAAe,QAAA,EAAA,CAAC,GAAA,qBAAQA,GAAAA,CAAC,WAAA,EAAA,EAAY,GAAA,EAAU,KAAA,EAAc,CAAA,EAAG,CAAA;AAC1E;AAEA,SAAS,WAAA,CAAY,EAAE,GAAA,EAAK,KAAA,EAAM,EAA0D;AAC1F,EAAA,MAAM,IAAA,GAAmB,EAAE,GAAG,iBAAA,CAAkB,KAAA,CAAM,MAAM,CAAA,CAAE,MAAA,EAAQ,GAAG,KAAA,CAAM,IAAA,EAAK;AACpF,EAAA,MAAM,QAAA,GAAW,MAAM,gBAAA,IAAoB,WAAA;AAE3C,EAAAC,UAAU,MAAM;AACd,IAAA,YAAA,EAAa;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAa,MAAM,KAAA,GAAQ,EAAE,iBAAiB,KAAA,CAAM,KAAA,KAAU,EAAC;AAErE,EAAA,uBACEC,IAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,mBAAA;AAAA,MACV,IAAA,EAAK,QAAA;AAAA,MACL,WAAA,EAAU,QAAA;AAAA,MACV,cAAY,IAAA,CAAK,KAAA;AAAA,MAChB,GAAG,UAAA;AAAA,MAEJ,QAAA,EAAA;AAAA,wBAAAF,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,iBAAA,EAAmB,eAAK,KAAA,EAAM,CAAA;AAAA,wBAC3CA,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,gBAAA,EAAkB,eAAK,WAAA,EAAY,CAAA;AAAA,wBAChDE,IAAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,mBAAA,EACZ,QAAA,EAAA;AAAA,UAAA,KAAA,CAAM,SAAA,oBACLF,GAAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,WAAU,IAAA,EAAM,KAAA,CAAM,SAAA,EAChC,QAAA,EAAA,IAAA,CAAK,eAAA,EACR,CAAA;AAAA,0BAEFA,GAAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,SAAA,EAAU,yBAAA;AAAA,cACV,SAAS,MAAM;AACb,gBAAA,IAAI,GAAA,CAAI,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,QAAQ,CAAA,EAAG;AAC/C,kBAAA,GAAA,CAAI,KAAK,QAAQ,CAAA;AAAA,gBACnB;AACA,gBAAA,GAAA,CAAI,IAAA,EAAK;AAAA,cACX,CAAA;AAAA,cAEC,QAAA,EAAA,IAAA,CAAK;AAAA;AAAA,WACR;AAAA,0BACAA,GAAAA,CAAC,QAAA,EAAA,EAAO,IAAA,EAAK,QAAA,EAAS,SAAA,EAAU,uBAAA,EAAwB,OAAA,EAAS,MAAM,GAAA,CAAI,IAAA,EAAK,EAC7E,eAAK,gBAAA,EACR;AAAA,SAAA,EACF;AAAA;AAAA;AAAA,GACF;AAEJ","file":"index.js","sourcesContent":["import { type ConsentApi, ConsentBanner } from '@tickboxhq/react'\nimport { useEffect, useId, useRef, useState } from 'react'\nimport type { BannerCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, `'fr-CH'`, ...) or `'auto'` to\n * read from `navigator.language`. Falls back from the full tag to the\n * language prefix, then to English. Built-in: en, de, fr, es, it, nl,\n * pt, pl, uk.\n */\n locale?: string\n /**\n * Override individual labels and copy strings. Layered on top of\n * whichever locale is selected, so you can ship in one language and\n * tweak a single label.\n */\n copy?: Partial<BannerCopy>\n /**\n * URL of the privacy policy linked from the banner. If omitted, the link\n * is hidden. The Tickbox config's `policy.url` is the natural source —\n * pass it here.\n */\n policyUrl?: string | undefined\n /**\n * Force light or dark theme. By default the banner follows\n * `prefers-color-scheme`.\n */\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the banner fetches its\n * remote presentation theme (copy + colours) on mount from\n * cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last\n * fetched theme is cached in localStorage so the next page load renders\n * synchronously with no flicker.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n}\n\n/**\n * Drop-in styled consent banner. Mounts itself only when the headless\n * `<ConsentBanner>` says it should be open. Click \"Customise\" to expand\n * a per-category modal.\n *\n * @example\n * ```tsx\n * import config from './consent.config'\n * <ConsentBannerDefault policyUrl={config.policy?.url} />\n * ```\n */\nexport function ConsentBannerDefault(props: ConsentBannerDefaultProps) {\n return <ConsentBanner>{(api) => <BannerInner api={api} props={props} />}</ConsentBanner>\n}\n\nfunction BannerInner({\n api,\n props,\n}: {\n api: ConsentApi\n props: ConsentBannerDefaultProps\n}) {\n // Synchronous read of any cached theme so first paint already shows brand\n // colours / copy.\n const [remoteTheme, setRemoteTheme] = useState<RemoteTheme | null>(() =>\n props.siteId ? readCachedTheme(props.siteId) : null,\n )\n\n useEffect(() => {\n injectStyles()\n if (!props.siteId) return\n let cancelled = false\n void (async () => {\n const fresh = await refreshTheme(props.siteId!, props.cloudEndpoint)\n if (!cancelled && fresh) setRemoteTheme(fresh)\n })()\n return () => {\n cancelled = true\n }\n }, [props.siteId, props.cloudEndpoint])\n\n const mergedCopyOverrides = mergeCopy(props.copy ?? {}, remoteTheme?.copy)\n const locale = remoteTheme?.locale ?? props.locale\n const copy: BannerCopy = { ...resolveLocalePack(locale).banner, ...mergedCopyOverrides }\n\n const [showModal, setShowModal] = useState(false)\n const rootRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (rootRef.current) applyThemeColors(rootRef.current, remoteTheme)\n }, [remoteTheme])\n\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return (\n <>\n <div\n ref={rootRef}\n className=\"tb-root tb-banner\"\n role=\"region\"\n aria-label={copy.title}\n {...themeAttrs}\n >\n <div className=\"tb-banner-text\">\n <p className=\"tb-banner-title\">{copy.title}</p>\n <p className=\"tb-banner-desc\">{copy.description}</p>\n </div>\n <div className=\"tb-banner-actions\">\n {props.policyUrl && (\n <a className=\"tb-link\" href={props.policyUrl}>\n {copy.policyLinkLabel}\n </a>\n )}\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.denyAll()}>\n {copy.rejectLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-ghost\" onClick={() => setShowModal(true)}>\n {copy.customiseLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.grantAll()}>\n {copy.acceptLabel}\n </button>\n </div>\n </div>\n {showModal && (\n <CustomiseModal\n api={api}\n copy={copy}\n theme={props.theme}\n remoteTheme={remoteTheme}\n onClose={() => setShowModal(false)}\n />\n )}\n </>\n )\n}\n\nfunction CustomiseModal({\n api,\n copy,\n theme,\n remoteTheme,\n onClose,\n}: {\n api: ConsentApi\n copy: BannerCopy\n theme?: 'light' | 'dark'\n remoteTheme?: RemoteTheme | null\n onClose: () => void\n}) {\n const titleId = useId()\n const containerRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') onClose()\n if (e.key === 'Tab') trapFocus(e, containerRef.current)\n }\n document.addEventListener('keydown', onKey)\n const previouslyFocused = document.activeElement as HTMLElement | null\n const first = containerRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, [tabindex]:not([tabindex=\"-1\"])',\n )\n first?.focus()\n return () => {\n document.removeEventListener('keydown', onKey)\n previouslyFocused?.focus?.()\n }\n }, [onClose])\n\n const themeAttrs = theme ? { 'data-tb-theme': theme } : {}\n\n // Apply remote-theme colour overrides inline so the modal picks them up\n // immediately on every mount (no race with a ref+effect cycle). React\n // doesn't type CSS custom properties on CSSProperties, so build as a\n // plain record and cast at the assignment site.\n const modalStyle: Record<string, string> = {}\n if (remoteTheme?.colors?.accent) {\n modalStyle['--tb-primary-bg'] = sanitiseColor(remoteTheme.colors.accent)\n }\n if (remoteTheme?.colors?.background) {\n modalStyle['--tb-bg'] = sanitiseColor(remoteTheme.colors.background)\n }\n if (remoteTheme?.colors?.text) {\n modalStyle['--tb-fg'] = sanitiseColor(remoteTheme.colors.text)\n }\n\n return (\n <div\n className=\"tb-root tb-modal-backdrop\"\n style={modalStyle as React.CSSProperties}\n onClick={onClose}\n onKeyDown={(e) => {\n if (e.key === 'Escape') onClose()\n }}\n role=\"presentation\"\n {...themeAttrs}\n >\n <div\n ref={containerRef}\n className=\"tb-modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <div className=\"tb-modal-head\">\n <h2 id={titleId} className=\"tb-modal-title\">\n {copy.customiseLabel}\n </h2>\n <button\n type=\"button\"\n className=\"tb-btn tb-btn-ghost\"\n aria-label={copy.closeLabel}\n onClick={onClose}\n >\n ✕\n </button>\n </div>\n <div className=\"tb-modal-body\">\n {api.resolved.map((cat) => {\n const checked = api.decisions[cat.id] === true\n const id = `tb-cat-${cat.id}`\n return (\n <div key={cat.id} className=\"tb-cat\">\n <div className=\"tb-cat-text\">\n <p className=\"tb-cat-name\">\n <label htmlFor={id}>{cat.id}</label>\n {cat.required && <span className=\"tb-badge\">{copy.requiredBadge}</span>}\n </p>\n {cat.description && <p className=\"tb-cat-desc\">{cat.description}</p>}\n </div>\n <label className=\"tb-switch\">\n <input\n id={id}\n type=\"checkbox\"\n checked={checked}\n disabled={cat.required}\n onChange={(e) => {\n if (e.target.checked) api.grant(cat.id)\n else api.deny(cat.id)\n }}\n />\n <span className=\"tb-switch-track\">\n <span className=\"tb-switch-thumb\" />\n </span>\n </label>\n </div>\n )\n })}\n </div>\n <div className=\"tb-modal-foot\">\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.denyAll()}>\n {copy.rejectLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-primary\" onClick={() => api.save()}>\n {copy.saveLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n\nfunction trapFocus(e: KeyboardEvent, container: HTMLDivElement | null) {\n if (!container) return\n const focusables = container.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n )\n if (focusables.length === 0) return\n const first = focusables[0]\n const last = focusables[focusables.length - 1]\n if (!first || !last) return\n const active = document.activeElement\n if (e.shiftKey && active === first) {\n e.preventDefault()\n last.focus()\n } else if (!e.shiftKey && active === last) {\n e.preventDefault()\n first.focus()\n }\n}\n\nfunction sanitiseColor(v: string): string {\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n","import { type ConsentApi, ConsentNotice } from '@tickboxhq/react'\nimport { useEffect } from 'react'\nimport type { NoticeCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, ...) or `'auto'`. See\n * `ConsentBannerDefault` for the built-in list.\n */\n locale?: string\n /**\n * Override individual labels. Layered on top of the resolved locale.\n */\n copy?: Partial<NoticeCopy>\n /** Privacy-policy URL. If omitted, the link is hidden. */\n policyUrl?: string | undefined\n /**\n * Category ID to deny when the user clicks \"Opt out\". Defaults to\n * `'analytics'` since that's the most common notice-mode category.\n */\n optOutCategoryId?: string\n /** Force light or dark theme. */\n theme?: 'light' | 'dark'\n}\n\n/**\n * Drop-in styled notice card for sites that have only `notice`-mode\n * categories (typically UK DUAA-exempt analytics like Plausible or\n * GoatCounter). Bottom-right toast.\n *\n * @example\n * ```tsx\n * import config from './consent.config'\n * <ConsentNoticeDefault policyUrl={config.policy?.url} />\n * ```\n */\nexport function ConsentNoticeDefault(props: ConsentNoticeDefaultProps) {\n return <ConsentNotice>{(api) => <NoticeInner api={api} props={props} />}</ConsentNotice>\n}\n\nfunction NoticeInner({ api, props }: { api: ConsentApi; props: ConsentNoticeDefaultProps }) {\n const copy: NoticeCopy = { ...resolveLocalePack(props.locale).notice, ...props.copy }\n const optOutId = props.optOutCategoryId ?? 'analytics'\n\n useEffect(() => {\n injectStyles()\n }, [])\n\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return (\n <div\n className=\"tb-root tb-notice\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={copy.title}\n {...themeAttrs}\n >\n <p className=\"tb-notice-title\">{copy.title}</p>\n <p className=\"tb-notice-desc\">{copy.description}</p>\n <div className=\"tb-notice-actions\">\n {props.policyUrl && (\n <a className=\"tb-link\" href={props.policyUrl}>\n {copy.policyLinkLabel}\n </a>\n )}\n <button\n type=\"button\"\n className=\"tb-btn tb-btn-secondary\"\n onClick={() => {\n if (api.resolved.some((r) => r.id === optOutId)) {\n api.deny(optOutId)\n }\n api.save()\n }}\n >\n {copy.optOutLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-primary\" onClick={() => api.save()}>\n {copy.acknowledgeLabel}\n </button>\n </div>\n </div>\n )\n}\n"]}
1
+ {"version":3,"sources":["../../src/react/banner.tsx","../../src/react/notice.tsx"],"names":["jsx","useRef","useState","useEffect","jsxs"],"mappings":"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","file":"index.js","sourcesContent":["import { type ConsentApi, ConsentBanner } from '@tickboxhq/react'\nimport { useEffect, useId, useRef, useState } from 'react'\nimport type { BannerCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, `'fr-CH'`, ...) or `'auto'` to\n * read from `navigator.language`. Falls back from the full tag to the\n * language prefix, then to English. Built-in: en, de, fr, es, it, nl,\n * pt, pl, uk.\n */\n locale?: string\n /**\n * Override individual labels and copy strings. Layered on top of\n * whichever locale is selected, so you can ship in one language and\n * tweak a single label.\n */\n copy?: Partial<BannerCopy>\n /**\n * URL of the privacy policy linked from the banner. If omitted, the link\n * is hidden. The Tickbox config's `policy.url` is the natural source —\n * pass it here.\n */\n policyUrl?: string | undefined\n /**\n * Force light or dark theme. By default the banner follows\n * `prefers-color-scheme`.\n */\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the banner fetches its\n * remote presentation theme (copy + colours) on mount from\n * cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last\n * fetched theme is cached in localStorage so the next page load renders\n * synchronously with no flicker.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n /**\n * When true, adds a small \"Powered by Tickbox\" link to tickbox.dev under\n * the banner. Off by default — most sites won't want third-party\n * branding on a compliance widget. Local-only setting; not part of the\n * remote-theme payload, since whether to credit the SDK is a code-level\n * decision, not a marketing-team one.\n */\n branding?: boolean\n}\n\n/**\n * Drop-in styled consent banner. Mounts itself only when the headless\n * `<ConsentBanner>` says it should be open. Click \"Customise\" to expand\n * a per-category modal.\n *\n * @example\n * ```tsx\n * import config from './consent.config'\n * <ConsentBannerDefault policyUrl={config.policy?.url} />\n * ```\n */\nexport function ConsentBannerDefault(props: ConsentBannerDefaultProps) {\n return <ConsentBanner>{(api) => <BannerInner api={api} props={props} />}</ConsentBanner>\n}\n\nfunction BannerInner({\n api,\n props,\n}: {\n api: ConsentApi\n props: ConsentBannerDefaultProps\n}) {\n // Synchronous read of any cached theme so first paint already shows brand\n // colours / copy.\n const [remoteTheme, setRemoteTheme] = useState<RemoteTheme | null>(() =>\n props.siteId ? readCachedTheme(props.siteId) : null,\n )\n\n useEffect(() => {\n injectStyles()\n if (!props.siteId) return\n let cancelled = false\n void (async () => {\n const fresh = await refreshTheme(props.siteId!, props.cloudEndpoint)\n if (!cancelled && fresh) setRemoteTheme(fresh)\n })()\n return () => {\n cancelled = true\n }\n }, [props.siteId, props.cloudEndpoint])\n\n const mergedCopyOverrides = mergeCopy(props.copy ?? {}, remoteTheme?.copy)\n const locale = remoteTheme?.locale ?? props.locale\n const copy: BannerCopy = { ...resolveLocalePack(locale).banner, ...mergedCopyOverrides }\n\n const [showModal, setShowModal] = useState(false)\n const rootRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (rootRef.current) applyThemeColors(rootRef.current, remoteTheme)\n }, [remoteTheme])\n\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return (\n <>\n <div\n ref={rootRef}\n className=\"tb-root tb-banner\"\n role=\"region\"\n aria-label={copy.title}\n {...themeAttrs}\n >\n <div className=\"tb-banner-text\">\n <p className=\"tb-banner-title\">{copy.title}</p>\n <p className=\"tb-banner-desc\">{copy.description}</p>\n </div>\n <div className=\"tb-banner-actions\">\n {props.policyUrl && (\n <a className=\"tb-link\" href={props.policyUrl}>\n {copy.policyLinkLabel}\n </a>\n )}\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.denyAll()}>\n {copy.rejectLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-ghost\" onClick={() => setShowModal(true)}>\n {copy.customiseLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.grantAll()}>\n {copy.acceptLabel}\n </button>\n </div>\n {props.branding && (\n <p className=\"tb-branding\">\n Powered by{' '}\n <a href=\"https://tickbox.dev\" target=\"_blank\" rel=\"noopener noreferrer\">\n Tickbox\n </a>\n </p>\n )}\n </div>\n {showModal && (\n <CustomiseModal\n api={api}\n copy={copy}\n theme={props.theme}\n remoteTheme={remoteTheme}\n onClose={() => setShowModal(false)}\n />\n )}\n </>\n )\n}\n\nfunction CustomiseModal({\n api,\n copy,\n theme,\n remoteTheme,\n onClose,\n}: {\n api: ConsentApi\n copy: BannerCopy\n theme?: 'light' | 'dark'\n remoteTheme?: RemoteTheme | null\n onClose: () => void\n}) {\n const titleId = useId()\n const containerRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') onClose()\n if (e.key === 'Tab') trapFocus(e, containerRef.current)\n }\n document.addEventListener('keydown', onKey)\n const previouslyFocused = document.activeElement as HTMLElement | null\n const first = containerRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, [tabindex]:not([tabindex=\"-1\"])',\n )\n first?.focus()\n return () => {\n document.removeEventListener('keydown', onKey)\n previouslyFocused?.focus?.()\n }\n }, [onClose])\n\n const themeAttrs = theme ? { 'data-tb-theme': theme } : {}\n\n // Apply remote-theme colour overrides inline so the modal picks them up\n // immediately on every mount (no race with a ref+effect cycle). React\n // doesn't type CSS custom properties on CSSProperties, so build as a\n // plain record and cast at the assignment site.\n const modalStyle: Record<string, string> = {}\n if (remoteTheme?.colors?.accent) {\n modalStyle['--tb-primary-bg'] = sanitiseColor(remoteTheme.colors.accent)\n }\n if (remoteTheme?.colors?.background) {\n modalStyle['--tb-bg'] = sanitiseColor(remoteTheme.colors.background)\n }\n if (remoteTheme?.colors?.text) {\n modalStyle['--tb-fg'] = sanitiseColor(remoteTheme.colors.text)\n }\n\n return (\n <div\n className=\"tb-root tb-modal-backdrop\"\n style={modalStyle as React.CSSProperties}\n onClick={onClose}\n onKeyDown={(e) => {\n if (e.key === 'Escape') onClose()\n }}\n role=\"presentation\"\n {...themeAttrs}\n >\n <div\n ref={containerRef}\n className=\"tb-modal\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => e.stopPropagation()}\n >\n <div className=\"tb-modal-head\">\n <h2 id={titleId} className=\"tb-modal-title\">\n {copy.customiseLabel}\n </h2>\n <button\n type=\"button\"\n className=\"tb-btn tb-btn-ghost\"\n aria-label={copy.closeLabel}\n onClick={onClose}\n >\n ✕\n </button>\n </div>\n <div className=\"tb-modal-body\">\n {api.resolved.map((cat) => {\n const checked = api.decisions[cat.id] === true\n const id = `tb-cat-${cat.id}`\n return (\n <div key={cat.id} className=\"tb-cat\">\n <div className=\"tb-cat-text\">\n <p className=\"tb-cat-name\">\n <label htmlFor={id}>{cat.id}</label>\n {cat.required && <span className=\"tb-badge\">{copy.requiredBadge}</span>}\n </p>\n {cat.description && <p className=\"tb-cat-desc\">{cat.description}</p>}\n </div>\n <label className=\"tb-switch\">\n <input\n id={id}\n type=\"checkbox\"\n checked={checked}\n disabled={cat.required}\n onChange={(e) => {\n if (e.target.checked) api.grant(cat.id)\n else api.deny(cat.id)\n }}\n />\n <span className=\"tb-switch-track\">\n <span className=\"tb-switch-thumb\" />\n </span>\n </label>\n </div>\n )\n })}\n </div>\n <div className=\"tb-modal-foot\">\n <button type=\"button\" className=\"tb-btn tb-btn-equal\" onClick={() => api.denyAll()}>\n {copy.rejectLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-primary\" onClick={() => api.save()}>\n {copy.saveLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n\nfunction trapFocus(e: KeyboardEvent, container: HTMLDivElement | null) {\n if (!container) return\n const focusables = container.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n )\n if (focusables.length === 0) return\n const first = focusables[0]\n const last = focusables[focusables.length - 1]\n if (!first || !last) return\n const active = document.activeElement\n if (e.shiftKey && active === first) {\n e.preventDefault()\n last.focus()\n } else if (!e.shiftKey && active === last) {\n e.preventDefault()\n first.focus()\n }\n}\n\nfunction sanitiseColor(v: string): string {\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n","import { type ConsentApi, ConsentNotice } from '@tickboxhq/react'\nimport { useEffect, useRef, useState } from 'react'\nimport type { NoticeCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentNoticeDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, ...) or `'auto'`. See\n * `ConsentBannerDefault` for the built-in list.\n */\n locale?: string\n /**\n * Override individual labels. Layered on top of the resolved locale.\n */\n copy?: Partial<NoticeCopy>\n /** Privacy-policy URL. If omitted, the link is hidden. */\n policyUrl?: string | undefined\n /**\n * Category ID to deny when the user clicks \"Opt out\". Defaults to\n * `'analytics'` since that's the most common notice-mode category.\n */\n optOutCategoryId?: string\n /** Force light or dark theme. */\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the notice fetches its\n * remote presentation theme (copy + colours) on mount the same way the\n * banner does, so marketing-team-edited copy lands without a redeploy.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n /**\n * When true, adds a small \"Powered by Tickbox\" link to tickbox.dev under\n * the notice. Off by default — see ConsentBannerDefault.branding for the\n * reasoning.\n */\n branding?: boolean\n}\n\n/**\n * Drop-in styled notice card for sites that have only `notice`-mode\n * categories (typically UK DUAA-exempt analytics like Plausible or\n * GoatCounter). Bottom-right toast.\n *\n * @example\n * ```tsx\n * import config from './consent.config'\n * <ConsentNoticeDefault policyUrl={config.policy?.url} />\n * ```\n */\nexport function ConsentNoticeDefault(props: ConsentNoticeDefaultProps) {\n return <ConsentNotice>{(api) => <NoticeInner api={api} props={props} />}</ConsentNotice>\n}\n\nfunction NoticeInner({ api, props }: { api: ConsentApi; props: ConsentNoticeDefaultProps }) {\n const rootRef = useRef<HTMLDivElement>(null)\n // Same remote-theme dance the banner does: prime synchronously from the\n // localStorage cache so first paint matches the dashboard, then refetch.\n const [remoteTheme, setRemoteTheme] = useState<RemoteTheme | null>(() =>\n props.siteId ? readCachedTheme(props.siteId) : null,\n )\n\n useEffect(() => {\n injectStyles()\n if (!props.siteId) return\n let cancelled = false\n ;(async () => {\n const fresh = await refreshTheme(props.siteId!, props.cloudEndpoint)\n if (!cancelled && fresh) setRemoteTheme(fresh)\n })()\n return () => {\n cancelled = true\n }\n }, [props.siteId, props.cloudEndpoint])\n\n useEffect(() => {\n if (rootRef.current) applyThemeColors(rootRef.current, remoteTheme)\n }, [remoteTheme])\n\n const mergedCopyOverrides = mergeCopy<NoticeCopy>(props.copy ?? {}, remoteTheme?.copy)\n const locale = remoteTheme?.locale ?? props.locale\n const copy: NoticeCopy = { ...resolveLocalePack(locale).notice, ...mergedCopyOverrides }\n const optOutId = props.optOutCategoryId ?? 'analytics'\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return (\n <div\n ref={rootRef}\n className=\"tb-root tb-notice\"\n role=\"status\"\n aria-live=\"polite\"\n aria-label={copy.title}\n {...themeAttrs}\n >\n <p className=\"tb-notice-title\">{copy.title}</p>\n <p className=\"tb-notice-desc\">{copy.description}</p>\n <div className=\"tb-notice-actions\">\n {props.policyUrl && (\n <a className=\"tb-link\" href={props.policyUrl}>\n {copy.policyLinkLabel}\n </a>\n )}\n <button\n type=\"button\"\n className=\"tb-btn tb-btn-secondary\"\n onClick={() => {\n if (api.resolved.some((r) => r.id === optOutId)) {\n api.deny(optOutId)\n }\n api.save()\n }}\n >\n {copy.optOutLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-primary\" onClick={() => api.save()}>\n {copy.acknowledgeLabel}\n </button>\n </div>\n {props.branding && (\n <p className=\"tb-branding\">\n Powered by{' '}\n <a href=\"https://tickbox.dev\" target=\"_blank\" rel=\"noopener noreferrer\">\n Tickbox\n </a>\n </p>\n )}\n </div>\n )\n}\n"]}
@@ -23,6 +23,11 @@ type ConsentBannerDefaultProps = {
23
23
  siteId?: string;
24
24
  /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
25
25
  cloudEndpoint?: string;
26
+ /**
27
+ * When true, adds a small "Powered by Tickbox" link to tickbox.dev under
28
+ * the banner. Off by default; local-only setting.
29
+ */
30
+ branding?: boolean;
26
31
  };
27
32
  /**
28
33
  * Drop-in styled consent banner for Vue. Mounts only when the headless
@@ -73,6 +78,10 @@ declare const ConsentBannerDefault: vue.DefineComponent<vue.ExtractPropTypes<{
73
78
  type: PropType<string | undefined>;
74
79
  default: undefined;
75
80
  };
81
+ branding: {
82
+ type: BooleanConstructor;
83
+ default: boolean;
84
+ };
76
85
  }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
77
86
  [key: string]: any;
78
87
  }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -100,6 +109,10 @@ declare const ConsentBannerDefault: vue.DefineComponent<vue.ExtractPropTypes<{
100
109
  type: PropType<string | undefined>;
101
110
  default: undefined;
102
111
  };
112
+ branding: {
113
+ type: BooleanConstructor;
114
+ default: boolean;
115
+ };
103
116
  }>> & Readonly<{}>, {
104
117
  copy: Partial<BannerCopy>;
105
118
  locale: string | undefined;
@@ -107,6 +120,7 @@ declare const ConsentBannerDefault: vue.DefineComponent<vue.ExtractPropTypes<{
107
120
  theme: "light" | "dark" | undefined;
108
121
  siteId: string | undefined;
109
122
  cloudEndpoint: string | undefined;
123
+ branding: boolean;
110
124
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
111
125
 
112
126
  type ConsentNoticeDefaultProps = {
@@ -115,6 +129,19 @@ type ConsentNoticeDefaultProps = {
115
129
  policyUrl?: string;
116
130
  optOutCategoryId?: string;
117
131
  theme?: 'light' | 'dark';
132
+ /**
133
+ * Site identifier from app.tickbox.dev. When set, the notice fetches its
134
+ * remote presentation theme (copy + colours) on mount — same plumbing
135
+ * the banner uses.
136
+ */
137
+ siteId?: string;
138
+ /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
139
+ cloudEndpoint?: string;
140
+ /**
141
+ * When true, adds a small "Powered by Tickbox" link to tickbox.dev under
142
+ * the notice. Off by default; local-only setting.
143
+ */
144
+ branding?: boolean;
118
145
  };
119
146
  /**
120
147
  * Drop-in styled notice card for sites with only `notice`-mode categories
@@ -142,6 +169,18 @@ declare const ConsentNoticeDefault: vue.DefineComponent<vue.ExtractPropTypes<{
142
169
  type: PropType<"light" | "dark" | undefined>;
143
170
  default: undefined;
144
171
  };
172
+ siteId: {
173
+ type: PropType<string | undefined>;
174
+ default: undefined;
175
+ };
176
+ cloudEndpoint: {
177
+ type: PropType<string | undefined>;
178
+ default: undefined;
179
+ };
180
+ branding: {
181
+ type: BooleanConstructor;
182
+ default: boolean;
183
+ };
145
184
  }>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
146
185
  [key: string]: any;
147
186
  }>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
@@ -165,11 +204,26 @@ declare const ConsentNoticeDefault: vue.DefineComponent<vue.ExtractPropTypes<{
165
204
  type: PropType<"light" | "dark" | undefined>;
166
205
  default: undefined;
167
206
  };
207
+ siteId: {
208
+ type: PropType<string | undefined>;
209
+ default: undefined;
210
+ };
211
+ cloudEndpoint: {
212
+ type: PropType<string | undefined>;
213
+ default: undefined;
214
+ };
215
+ branding: {
216
+ type: BooleanConstructor;
217
+ default: boolean;
218
+ };
168
219
  }>> & Readonly<{}>, {
169
220
  copy: Partial<NoticeCopy>;
170
221
  locale: string | undefined;
171
222
  policyUrl: string | undefined;
172
223
  theme: "light" | "dark" | undefined;
224
+ siteId: string | undefined;
225
+ cloudEndpoint: string | undefined;
226
+ branding: boolean;
173
227
  optOutCategoryId: string;
174
228
  }, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
175
229
 
package/dist/vue/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-HCLSY5CT.js';
2
- export { locales, resolveLocalePack } from '../chunk-HCLSY5CT.js';
1
+ import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-SV4PBHBY.js';
2
+ export { locales, resolveLocalePack } from '../chunk-SV4PBHBY.js';
3
3
  import { ConsentBanner, ConsentNotice } from '@tickboxhq/vue';
4
4
  import { defineComponent, ref, onMounted, h, watch } from 'vue';
5
5
 
@@ -11,7 +11,8 @@ var ConsentBannerDefault = defineComponent({
11
11
  policyUrl: { type: String, default: void 0 },
12
12
  theme: { type: String, default: void 0 },
13
13
  siteId: { type: String, default: void 0 },
14
- cloudEndpoint: { type: String, default: void 0 }
14
+ cloudEndpoint: { type: String, default: void 0 },
15
+ branding: { type: Boolean, default: false }
15
16
  },
16
17
  setup(props) {
17
18
  const remoteTheme = ref(props.siteId ? readCachedTheme(props.siteId) : null);
@@ -28,7 +29,8 @@ var ConsentBannerDefault = defineComponent({
28
29
  userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),
29
30
  policyUrl: props.policyUrl,
30
31
  theme: props.theme,
31
- remoteTheme: remoteTheme.value
32
+ remoteTheme: remoteTheme.value,
33
+ branding: props.branding
32
34
  })
33
35
  });
34
36
  }
@@ -41,7 +43,8 @@ var BannerInner = defineComponent({
41
43
  userCopy: { type: Object, required: true },
42
44
  policyUrl: { type: String, default: void 0 },
43
45
  theme: { type: String, default: void 0 },
44
- remoteTheme: { type: Object, default: null }
46
+ remoteTheme: { type: Object, default: null },
47
+ branding: { type: Boolean, default: false }
45
48
  },
46
49
  setup(props) {
47
50
  const showModal = ref(false);
@@ -103,7 +106,19 @@ var BannerInner = defineComponent({
103
106
  },
104
107
  copy.acceptLabel
105
108
  )
106
- ])
109
+ ]),
110
+ props.branding ? h("p", { class: "tb-branding" }, [
111
+ "Powered by ",
112
+ h(
113
+ "a",
114
+ {
115
+ href: "https://tickbox.dev",
116
+ target: "_blank",
117
+ rel: "noopener noreferrer"
118
+ },
119
+ "Tickbox"
120
+ )
121
+ ]) : null
107
122
  ]
108
123
  ),
109
124
  showModal.value ? renderModal(props.api, copy, props.theme, props.remoteTheme, () => {
@@ -230,63 +245,115 @@ var ConsentNoticeDefault = defineComponent({
230
245
  copy: { type: Object, default: () => ({}) },
231
246
  policyUrl: { type: String, default: void 0 },
232
247
  optOutCategoryId: { type: String, default: "analytics" },
233
- theme: { type: String, default: void 0 }
248
+ theme: { type: String, default: void 0 },
249
+ siteId: { type: String, default: void 0 },
250
+ cloudEndpoint: { type: String, default: void 0 },
251
+ branding: { type: Boolean, default: false }
234
252
  },
235
253
  setup(props) {
236
- onMounted(() => injectStyles());
254
+ const remoteTheme = ref(props.siteId ? readCachedTheme(props.siteId) : null);
255
+ onMounted(async () => {
256
+ injectStyles();
257
+ if (!props.siteId) return;
258
+ const fresh = await refreshTheme(props.siteId, props.cloudEndpoint);
259
+ if (fresh) remoteTheme.value = fresh;
260
+ });
237
261
  return () => h(ConsentNotice, null, {
238
- default: (api) => renderNotice(api, props)
262
+ default: (api) => h(NoticeInner, {
263
+ api,
264
+ locale: remoteTheme.value?.locale ?? props.locale,
265
+ userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),
266
+ policyUrl: props.policyUrl,
267
+ optOutCategoryId: props.optOutCategoryId,
268
+ theme: props.theme,
269
+ remoteTheme: remoteTheme.value,
270
+ branding: props.branding
271
+ })
239
272
  });
240
273
  }
241
274
  });
242
- function renderNotice(api, props) {
243
- const copy = {
244
- ...resolveLocalePack(props.locale).notice,
245
- ...props.copy ?? {}
246
- };
247
- const optOutId = props.optOutCategoryId ?? "analytics";
248
- const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
249
- return h(
250
- "div",
251
- {
252
- class: "tb-root tb-notice",
253
- role: "status",
254
- "aria-live": "polite",
255
- "aria-label": copy.title,
256
- ...themeAttrs
257
- },
258
- [
259
- h("p", { class: "tb-notice-title" }, copy.title),
260
- h("p", { class: "tb-notice-desc" }, copy.description),
261
- h("div", { class: "tb-notice-actions" }, [
262
- props.policyUrl ? h("a", { class: "tb-link", href: props.policyUrl }, copy.policyLinkLabel) : null,
263
- h(
264
- "button",
265
- {
266
- type: "button",
267
- class: "tb-btn tb-btn-secondary",
268
- onClick: () => {
269
- if (api.resolved.some((r) => r.id === optOutId)) {
270
- api.deny(optOutId);
271
- }
272
- api.save();
273
- }
274
- },
275
- copy.optOutLabel
276
- ),
277
- h(
278
- "button",
279
- {
280
- type: "button",
281
- class: "tb-btn tb-btn-primary",
282
- onClick: () => api.save()
283
- },
284
- copy.acknowledgeLabel
285
- )
286
- ])
287
- ]
288
- );
289
- }
275
+ var NoticeInner = defineComponent({
276
+ name: "ConsentNoticeDefaultInner",
277
+ props: {
278
+ api: { type: Object, required: true },
279
+ locale: { type: String, default: void 0 },
280
+ userCopy: { type: Object, required: true },
281
+ policyUrl: { type: String, default: void 0 },
282
+ optOutCategoryId: { type: String, default: "analytics" },
283
+ theme: { type: String, default: void 0 },
284
+ remoteTheme: { type: Object, default: null },
285
+ branding: { type: Boolean, default: false }
286
+ },
287
+ setup(props) {
288
+ const rootRef = ref(null);
289
+ const applyColors = () => {
290
+ if (rootRef.value) applyThemeColors(rootRef.value, props.remoteTheme);
291
+ };
292
+ onMounted(applyColors);
293
+ watch(() => props.remoteTheme, applyColors, { deep: true });
294
+ return () => {
295
+ const copy = {
296
+ ...resolveLocalePack(props.locale).notice,
297
+ ...props.userCopy
298
+ };
299
+ const optOutId = props.optOutCategoryId ?? "analytics";
300
+ const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
301
+ return h(
302
+ "div",
303
+ {
304
+ ref: rootRef,
305
+ class: "tb-root tb-notice",
306
+ role: "status",
307
+ "aria-live": "polite",
308
+ "aria-label": copy.title,
309
+ ...themeAttrs
310
+ },
311
+ [
312
+ h("p", { class: "tb-notice-title" }, copy.title),
313
+ h("p", { class: "tb-notice-desc" }, copy.description),
314
+ h("div", { class: "tb-notice-actions" }, [
315
+ props.policyUrl ? h("a", { class: "tb-link", href: props.policyUrl }, copy.policyLinkLabel) : null,
316
+ h(
317
+ "button",
318
+ {
319
+ type: "button",
320
+ class: "tb-btn tb-btn-secondary",
321
+ onClick: () => {
322
+ if (props.api.resolved.some((r) => r.id === optOutId)) {
323
+ props.api.deny(optOutId);
324
+ }
325
+ props.api.save();
326
+ }
327
+ },
328
+ copy.optOutLabel
329
+ ),
330
+ h(
331
+ "button",
332
+ {
333
+ type: "button",
334
+ class: "tb-btn tb-btn-primary",
335
+ onClick: () => props.api.save()
336
+ },
337
+ copy.acknowledgeLabel
338
+ )
339
+ ]),
340
+ props.branding ? h("p", { class: "tb-branding" }, [
341
+ "Powered by ",
342
+ h(
343
+ "a",
344
+ {
345
+ href: "https://tickbox.dev",
346
+ target: "_blank",
347
+ rel: "noopener noreferrer"
348
+ },
349
+ "Tickbox"
350
+ )
351
+ ]) : null
352
+ ]
353
+ );
354
+ };
355
+ }
356
+ });
290
357
 
291
358
  export { ConsentBannerDefault, ConsentNoticeDefault };
292
359
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vue/banner.ts","../../src/vue/notice.ts"],"names":["defineComponent","onMounted","h"],"mappings":"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","file":"index.js","sourcesContent":["import type { ConsentSlotApi } from '@tickboxhq/vue'\nimport { ConsentBanner } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted, ref, watch } from 'vue'\nimport type { BannerCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, `'fr-CH'`, ...) or `'auto'`.\n * Built-in: en, de, fr, es, it, nl, pt, pl, uk. Falls back from the full\n * tag to the language prefix, then to English.\n */\n locale?: string\n copy?: Partial<BannerCopy>\n policyUrl?: string\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the banner fetches its\n * remote presentation theme (copy + colours) on mount from\n * cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last\n * fetched theme is cached in localStorage so the next page load renders\n * synchronously with no flicker.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n}\n\n/**\n * Drop-in styled consent banner for Vue. Mounts only when the headless\n * `<ConsentBanner>` says it should be open. \"Customise\" opens a modal\n * with per-category toggles.\n *\n * Equal-prominence design: Accept All and Reject All use identical button\n * styling. UK ICO and EU EDPB guidance treats unequal visual weight on\n * those buttons as a dark pattern. Customise is rendered as a ghost button\n * so the two consent paths stay symmetrical.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { ConsentBannerDefault } from '@tickboxhq/banner-default/vue'\n * import config from './consent.config'\n * </script>\n * <template>\n * <ConsentBannerDefault\n * :site-id=\"config.cloud?.siteId\"\n * :policy-url=\"config.policy?.url\"\n * />\n * </template>\n * ```\n */\nexport const ConsentBannerDefault = defineComponent({\n name: 'ConsentBannerDefault',\n props: {\n locale: { type: String as PropType<string | undefined>, default: undefined },\n copy: { type: Object as PropType<Partial<BannerCopy>>, default: () => ({}) },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n siteId: { type: String as PropType<string | undefined>, default: undefined },\n cloudEndpoint: { type: String as PropType<string | undefined>, default: undefined },\n },\n setup(props) {\n // Synchronous read of any cached theme so we render with brand\n // colours / copy on first paint — no flicker between code defaults and\n // the remote theme.\n const remoteTheme = ref<RemoteTheme | null>(props.siteId ? readCachedTheme(props.siteId) : null)\n\n onMounted(async () => {\n injectStyles()\n if (!props.siteId) return\n const fresh = await refreshTheme(props.siteId, props.cloudEndpoint)\n if (fresh) remoteTheme.value = fresh\n })\n\n return () =>\n h(ConsentBanner, null, {\n default: (api: unknown) =>\n h(BannerInner, {\n api: api as ConsentSlotApi,\n locale: remoteTheme.value?.locale ?? props.locale,\n userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),\n policyUrl: props.policyUrl,\n theme: props.theme,\n remoteTheme: remoteTheme.value,\n }),\n })\n },\n})\n\n/**\n * Inner component that owns `showModal` state. Keeping it in the same\n * scope as the render guarantees Vue's reactivity wires up correctly\n * (slot-only render functions don't track refs from outer scopes).\n */\nconst BannerInner = defineComponent({\n name: 'ConsentBannerDefaultInner',\n props: {\n api: { type: Object as PropType<ConsentSlotApi>, required: true },\n locale: { type: String as PropType<string | undefined>, default: undefined },\n userCopy: { type: Object as PropType<Partial<BannerCopy>>, required: true },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n remoteTheme: { type: Object as PropType<RemoteTheme | null>, default: null },\n },\n setup(props) {\n const showModal = ref(false)\n const rootRef = ref<HTMLElement | null>(null)\n\n // Apply colour overrides after every render so the values follow theme\n // changes (e.g. after a fresh fetch finishes post-mount).\n const applyColors = () => {\n if (rootRef.value) applyThemeColors(rootRef.value, props.remoteTheme)\n }\n onMounted(applyColors)\n watch(() => props.remoteTheme, applyColors, { deep: true })\n\n return () => {\n const copy: BannerCopy = {\n ...resolveLocalePack(props.locale).banner,\n ...props.userCopy,\n }\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return h('div', null, [\n h(\n 'div',\n {\n ref: rootRef,\n class: 'tb-root tb-banner',\n role: 'region',\n 'aria-label': copy.title,\n ...themeAttrs,\n },\n [\n h('div', { class: 'tb-banner-text' }, [\n h('p', { class: 'tb-banner-title' }, copy.title),\n h('p', { class: 'tb-banner-desc' }, copy.description),\n ]),\n h('div', { class: 'tb-banner-actions' }, [\n props.policyUrl\n ? h('a', { class: 'tb-link', href: props.policyUrl }, copy.policyLinkLabel)\n : null,\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => props.api.denyAll(),\n },\n copy.rejectLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-ghost',\n onClick: () => {\n showModal.value = true\n },\n },\n copy.customiseLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => props.api.grantAll(),\n },\n copy.acceptLabel,\n ),\n ]),\n ],\n ),\n showModal.value\n ? renderModal(props.api, copy, props.theme, props.remoteTheme, () => {\n showModal.value = false\n })\n : null,\n ])\n }\n },\n})\n\nfunction renderModal(\n api: ConsentSlotApi,\n copy: BannerCopy,\n theme: 'light' | 'dark' | undefined,\n remoteTheme: RemoteTheme | null,\n onClose: () => void,\n) {\n const themeAttrs = theme ? { 'data-tb-theme': theme } : {}\n\n // Apply theme colours to the modal root too. Set as inline style so it\n // doesn't depend on a post-render ref+effect cycle (the modal mounts and\n // unmounts frequently).\n const inlineStyle: Record<string, string> = {}\n if (remoteTheme?.colors?.accent) {\n inlineStyle['--tb-primary-bg'] = sanitiseColor(remoteTheme.colors.accent)\n }\n if (remoteTheme?.colors?.background) {\n inlineStyle['--tb-bg'] = sanitiseColor(remoteTheme.colors.background)\n }\n if (remoteTheme?.colors?.text) {\n inlineStyle['--tb-fg'] = sanitiseColor(remoteTheme.colors.text)\n }\n\n return h(\n 'div',\n {\n class: 'tb-root tb-modal-backdrop',\n role: 'presentation',\n style: inlineStyle,\n onClick: onClose,\n onKeydown: (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose()\n },\n ...themeAttrs,\n },\n [\n h(\n 'div',\n {\n class: 'tb-modal',\n role: 'dialog',\n 'aria-modal': 'true',\n 'aria-label': copy.customiseLabel,\n onClick: (e: MouseEvent) => e.stopPropagation(),\n onKeydown: (e: KeyboardEvent) => e.stopPropagation(),\n },\n [\n h('div', { class: 'tb-modal-head' }, [\n h('h2', { class: 'tb-modal-title' }, copy.customiseLabel),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-ghost',\n 'aria-label': copy.closeLabel,\n onClick: onClose,\n },\n '✕',\n ),\n ]),\n h(\n 'div',\n { class: 'tb-modal-body' },\n api.resolved.map((cat) => {\n const checked = api.decisions[cat.id] === true\n const id = `tb-cat-${cat.id}`\n return h('div', { key: cat.id, class: 'tb-cat' }, [\n h('div', { class: 'tb-cat-text' }, [\n h('p', { class: 'tb-cat-name' }, [\n h('label', { for: id }, cat.id),\n cat.required ? h('span', { class: 'tb-badge' }, copy.requiredBadge) : null,\n ]),\n cat.description ? h('p', { class: 'tb-cat-desc' }, cat.description) : null,\n ]),\n h('label', { class: 'tb-switch' }, [\n h('input', {\n id,\n type: 'checkbox',\n checked,\n disabled: cat.required,\n onChange: (e: Event) => {\n const next = (e.target as HTMLInputElement).checked\n if (next) api.grant(cat.id)\n else api.deny(cat.id)\n },\n }),\n h('span', { class: 'tb-switch-track' }, [\n h('span', { class: 'tb-switch-thumb' }),\n ]),\n ]),\n ])\n }),\n ),\n h('div', { class: 'tb-modal-foot' }, [\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => api.denyAll(),\n },\n copy.rejectLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-primary',\n onClick: () => api.save(),\n },\n copy.saveLabel,\n ),\n ]),\n ],\n ),\n ],\n )\n}\n\nfunction sanitiseColor(v: string): string {\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n","import type { ConsentSlotApi } from '@tickboxhq/vue'\nimport { ConsentNotice } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted } from 'vue'\nimport type { NoticeCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\n locale?: string\n copy?: Partial<NoticeCopy>\n policyUrl?: string\n optOutCategoryId?: string\n theme?: 'light' | 'dark'\n}\n\n/**\n * Drop-in styled notice card for sites with only `notice`-mode categories\n * (typically UK DUAA-exempt analytics). Bottom-right toast with\n * \"Got it\" / \"Opt out\" actions.\n */\nexport const ConsentNoticeDefault = defineComponent({\n name: 'ConsentNoticeDefault',\n props: {\n locale: { type: String as PropType<string | undefined>, default: undefined },\n copy: { type: Object as PropType<Partial<NoticeCopy>>, default: () => ({}) },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n optOutCategoryId: { type: String, default: 'analytics' },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n },\n setup(props) {\n onMounted(() => injectStyles())\n return () =>\n h(ConsentNotice, null, {\n default: (api: unknown) => renderNotice(api as ConsentSlotApi, props),\n })\n },\n})\n\nfunction renderNotice(api: ConsentSlotApi, props: ConsentNoticeDefaultProps) {\n const copy: NoticeCopy = {\n ...resolveLocalePack(props.locale).notice,\n ...(props.copy ?? {}),\n }\n const optOutId = props.optOutCategoryId ?? 'analytics'\n\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return h(\n 'div',\n {\n class: 'tb-root tb-notice',\n role: 'status',\n 'aria-live': 'polite',\n 'aria-label': copy.title,\n ...themeAttrs,\n },\n [\n h('p', { class: 'tb-notice-title' }, copy.title),\n h('p', { class: 'tb-notice-desc' }, copy.description),\n h('div', { class: 'tb-notice-actions' }, [\n props.policyUrl\n ? h('a', { class: 'tb-link', href: props.policyUrl }, copy.policyLinkLabel)\n : null,\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-secondary',\n onClick: () => {\n if (api.resolved.some((r) => r.id === optOutId)) {\n api.deny(optOutId)\n }\n api.save()\n },\n },\n copy.optOutLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-primary',\n onClick: () => api.save(),\n },\n copy.acknowledgeLabel,\n ),\n ]),\n ],\n )\n}\n"]}
1
+ {"version":3,"sources":["../../src/vue/banner.ts","../../src/vue/notice.ts"],"names":["defineComponent","ref","onMounted","h","watch"],"mappings":"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","file":"index.js","sourcesContent":["import type { ConsentSlotApi } from '@tickboxhq/vue'\nimport { ConsentBanner } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted, ref, watch } from 'vue'\nimport type { BannerCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * BCP-47 language tag (`'en'`, `'de'`, `'fr-CH'`, ...) or `'auto'`.\n * Built-in: en, de, fr, es, it, nl, pt, pl, uk. Falls back from the full\n * tag to the language prefix, then to English.\n */\n locale?: string\n copy?: Partial<BannerCopy>\n policyUrl?: string\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the banner fetches its\n * remote presentation theme (copy + colours) on mount from\n * cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last\n * fetched theme is cached in localStorage so the next page load renders\n * synchronously with no flicker.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n /**\n * When true, adds a small \"Powered by Tickbox\" link to tickbox.dev under\n * the banner. Off by default; local-only setting.\n */\n branding?: boolean\n}\n\n/**\n * Drop-in styled consent banner for Vue. Mounts only when the headless\n * `<ConsentBanner>` says it should be open. \"Customise\" opens a modal\n * with per-category toggles.\n *\n * Equal-prominence design: Accept All and Reject All use identical button\n * styling. UK ICO and EU EDPB guidance treats unequal visual weight on\n * those buttons as a dark pattern. Customise is rendered as a ghost button\n * so the two consent paths stay symmetrical.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { ConsentBannerDefault } from '@tickboxhq/banner-default/vue'\n * import config from './consent.config'\n * </script>\n * <template>\n * <ConsentBannerDefault\n * :site-id=\"config.cloud?.siteId\"\n * :policy-url=\"config.policy?.url\"\n * />\n * </template>\n * ```\n */\nexport const ConsentBannerDefault = defineComponent({\n name: 'ConsentBannerDefault',\n props: {\n locale: { type: String as PropType<string | undefined>, default: undefined },\n copy: { type: Object as PropType<Partial<BannerCopy>>, default: () => ({}) },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n siteId: { type: String as PropType<string | undefined>, default: undefined },\n cloudEndpoint: { type: String as PropType<string | undefined>, default: undefined },\n branding: { type: Boolean, default: false },\n },\n setup(props) {\n // Synchronous read of any cached theme so we render with brand\n // colours / copy on first paint — no flicker between code defaults and\n // the remote theme.\n const remoteTheme = ref<RemoteTheme | null>(props.siteId ? readCachedTheme(props.siteId) : null)\n\n onMounted(async () => {\n injectStyles()\n if (!props.siteId) return\n const fresh = await refreshTheme(props.siteId, props.cloudEndpoint)\n if (fresh) remoteTheme.value = fresh\n })\n\n return () =>\n h(ConsentBanner, null, {\n default: (api: unknown) =>\n h(BannerInner, {\n api: api as ConsentSlotApi,\n locale: remoteTheme.value?.locale ?? props.locale,\n userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),\n policyUrl: props.policyUrl,\n theme: props.theme,\n remoteTheme: remoteTheme.value,\n branding: props.branding,\n }),\n })\n },\n})\n\n/**\n * Inner component that owns `showModal` state. Keeping it in the same\n * scope as the render guarantees Vue's reactivity wires up correctly\n * (slot-only render functions don't track refs from outer scopes).\n */\nconst BannerInner = defineComponent({\n name: 'ConsentBannerDefaultInner',\n props: {\n api: { type: Object as PropType<ConsentSlotApi>, required: true },\n locale: { type: String as PropType<string | undefined>, default: undefined },\n userCopy: { type: Object as PropType<Partial<BannerCopy>>, required: true },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n remoteTheme: { type: Object as PropType<RemoteTheme | null>, default: null },\n branding: { type: Boolean, default: false },\n },\n setup(props) {\n const showModal = ref(false)\n const rootRef = ref<HTMLElement | null>(null)\n\n // Apply colour overrides after every render so the values follow theme\n // changes (e.g. after a fresh fetch finishes post-mount).\n const applyColors = () => {\n if (rootRef.value) applyThemeColors(rootRef.value, props.remoteTheme)\n }\n onMounted(applyColors)\n watch(() => props.remoteTheme, applyColors, { deep: true })\n\n return () => {\n const copy: BannerCopy = {\n ...resolveLocalePack(props.locale).banner,\n ...props.userCopy,\n }\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return h('div', null, [\n h(\n 'div',\n {\n ref: rootRef,\n class: 'tb-root tb-banner',\n role: 'region',\n 'aria-label': copy.title,\n ...themeAttrs,\n },\n [\n h('div', { class: 'tb-banner-text' }, [\n h('p', { class: 'tb-banner-title' }, copy.title),\n h('p', { class: 'tb-banner-desc' }, copy.description),\n ]),\n h('div', { class: 'tb-banner-actions' }, [\n props.policyUrl\n ? h('a', { class: 'tb-link', href: props.policyUrl }, copy.policyLinkLabel)\n : null,\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => props.api.denyAll(),\n },\n copy.rejectLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-ghost',\n onClick: () => {\n showModal.value = true\n },\n },\n copy.customiseLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => props.api.grantAll(),\n },\n copy.acceptLabel,\n ),\n ]),\n props.branding\n ? h('p', { class: 'tb-branding' }, [\n 'Powered by ',\n h(\n 'a',\n {\n href: 'https://tickbox.dev',\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n 'Tickbox',\n ),\n ])\n : null,\n ],\n ),\n showModal.value\n ? renderModal(props.api, copy, props.theme, props.remoteTheme, () => {\n showModal.value = false\n })\n : null,\n ])\n }\n },\n})\n\nfunction renderModal(\n api: ConsentSlotApi,\n copy: BannerCopy,\n theme: 'light' | 'dark' | undefined,\n remoteTheme: RemoteTheme | null,\n onClose: () => void,\n) {\n const themeAttrs = theme ? { 'data-tb-theme': theme } : {}\n\n // Apply theme colours to the modal root too. Set as inline style so it\n // doesn't depend on a post-render ref+effect cycle (the modal mounts and\n // unmounts frequently).\n const inlineStyle: Record<string, string> = {}\n if (remoteTheme?.colors?.accent) {\n inlineStyle['--tb-primary-bg'] = sanitiseColor(remoteTheme.colors.accent)\n }\n if (remoteTheme?.colors?.background) {\n inlineStyle['--tb-bg'] = sanitiseColor(remoteTheme.colors.background)\n }\n if (remoteTheme?.colors?.text) {\n inlineStyle['--tb-fg'] = sanitiseColor(remoteTheme.colors.text)\n }\n\n return h(\n 'div',\n {\n class: 'tb-root tb-modal-backdrop',\n role: 'presentation',\n style: inlineStyle,\n onClick: onClose,\n onKeydown: (e: KeyboardEvent) => {\n if (e.key === 'Escape') onClose()\n },\n ...themeAttrs,\n },\n [\n h(\n 'div',\n {\n class: 'tb-modal',\n role: 'dialog',\n 'aria-modal': 'true',\n 'aria-label': copy.customiseLabel,\n onClick: (e: MouseEvent) => e.stopPropagation(),\n onKeydown: (e: KeyboardEvent) => e.stopPropagation(),\n },\n [\n h('div', { class: 'tb-modal-head' }, [\n h('h2', { class: 'tb-modal-title' }, copy.customiseLabel),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-ghost',\n 'aria-label': copy.closeLabel,\n onClick: onClose,\n },\n '✕',\n ),\n ]),\n h(\n 'div',\n { class: 'tb-modal-body' },\n api.resolved.map((cat) => {\n const checked = api.decisions[cat.id] === true\n const id = `tb-cat-${cat.id}`\n return h('div', { key: cat.id, class: 'tb-cat' }, [\n h('div', { class: 'tb-cat-text' }, [\n h('p', { class: 'tb-cat-name' }, [\n h('label', { for: id }, cat.id),\n cat.required ? h('span', { class: 'tb-badge' }, copy.requiredBadge) : null,\n ]),\n cat.description ? h('p', { class: 'tb-cat-desc' }, cat.description) : null,\n ]),\n h('label', { class: 'tb-switch' }, [\n h('input', {\n id,\n type: 'checkbox',\n checked,\n disabled: cat.required,\n onChange: (e: Event) => {\n const next = (e.target as HTMLInputElement).checked\n if (next) api.grant(cat.id)\n else api.deny(cat.id)\n },\n }),\n h('span', { class: 'tb-switch-track' }, [\n h('span', { class: 'tb-switch-thumb' }),\n ]),\n ]),\n ])\n }),\n ),\n h('div', { class: 'tb-modal-foot' }, [\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-equal',\n onClick: () => api.denyAll(),\n },\n copy.rejectLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-primary',\n onClick: () => api.save(),\n },\n copy.saveLabel,\n ),\n ]),\n ],\n ),\n ],\n )\n}\n\nfunction sanitiseColor(v: string): string {\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n","import type { ConsentSlotApi } from '@tickboxhq/vue'\nimport { ConsentNotice } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted, ref, watch } from 'vue'\nimport type { NoticeCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.js'\nimport {\n type RemoteTheme,\n applyThemeColors,\n mergeCopy,\n readCachedTheme,\n refreshTheme,\n} from '../shared/theme.js'\n\nexport type ConsentNoticeDefaultProps = {\n locale?: string\n copy?: Partial<NoticeCopy>\n policyUrl?: string\n optOutCategoryId?: string\n theme?: 'light' | 'dark'\n /**\n * Site identifier from app.tickbox.dev. When set, the notice fetches its\n * remote presentation theme (copy + colours) on mount — same plumbing\n * the banner uses.\n */\n siteId?: string\n /** Defaults to https://api.tickbox.dev. Override only for self-hosting. */\n cloudEndpoint?: string\n /**\n * When true, adds a small \"Powered by Tickbox\" link to tickbox.dev under\n * the notice. Off by default; local-only setting.\n */\n branding?: boolean\n}\n\n/**\n * Drop-in styled notice card for sites with only `notice`-mode categories\n * (typically UK DUAA-exempt analytics). Bottom-right toast with\n * \"Got it\" / \"Opt out\" actions.\n */\nexport const ConsentNoticeDefault = defineComponent({\n name: 'ConsentNoticeDefault',\n props: {\n locale: { type: String as PropType<string | undefined>, default: undefined },\n copy: { type: Object as PropType<Partial<NoticeCopy>>, default: () => ({}) },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n optOutCategoryId: { type: String, default: 'analytics' },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n siteId: { type: String as PropType<string | undefined>, default: undefined },\n cloudEndpoint: { type: String as PropType<string | undefined>, default: undefined },\n branding: { type: Boolean, default: false },\n },\n setup(props) {\n // Prime from cache so first paint matches the dashboard's last-saved\n // theme; refetch in the background.\n const remoteTheme = ref<RemoteTheme | null>(props.siteId ? readCachedTheme(props.siteId) : null)\n\n onMounted(async () => {\n injectStyles()\n if (!props.siteId) return\n const fresh = await refreshTheme(props.siteId, props.cloudEndpoint)\n if (fresh) remoteTheme.value = fresh\n })\n\n return () =>\n h(ConsentNotice, null, {\n default: (api: unknown) =>\n h(NoticeInner, {\n api: api as ConsentSlotApi,\n locale: remoteTheme.value?.locale ?? props.locale,\n userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),\n policyUrl: props.policyUrl,\n optOutCategoryId: props.optOutCategoryId,\n theme: props.theme,\n remoteTheme: remoteTheme.value,\n branding: props.branding,\n }),\n })\n },\n})\n\nconst NoticeInner = defineComponent({\n name: 'ConsentNoticeDefaultInner',\n props: {\n api: { type: Object as PropType<ConsentSlotApi>, required: true },\n locale: { type: String as PropType<string | undefined>, default: undefined },\n userCopy: { type: Object as PropType<Partial<NoticeCopy>>, required: true },\n policyUrl: { type: String as PropType<string | undefined>, default: undefined },\n optOutCategoryId: { type: String, default: 'analytics' },\n theme: { type: String as PropType<'light' | 'dark' | undefined>, default: undefined },\n remoteTheme: { type: Object as PropType<RemoteTheme | null>, default: null },\n branding: { type: Boolean, default: false },\n },\n setup(props) {\n const rootRef = ref<HTMLElement | null>(null)\n\n const applyColors = () => {\n if (rootRef.value) applyThemeColors(rootRef.value, props.remoteTheme)\n }\n onMounted(applyColors)\n watch(() => props.remoteTheme, applyColors, { deep: true })\n\n return () => {\n const copy: NoticeCopy = {\n ...resolveLocalePack(props.locale).notice,\n ...props.userCopy,\n }\n const optOutId = props.optOutCategoryId ?? 'analytics'\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return h(\n 'div',\n {\n ref: rootRef,\n class: 'tb-root tb-notice',\n role: 'status',\n 'aria-live': 'polite',\n 'aria-label': copy.title,\n ...themeAttrs,\n },\n [\n h('p', { class: 'tb-notice-title' }, copy.title),\n h('p', { class: 'tb-notice-desc' }, copy.description),\n h('div', { class: 'tb-notice-actions' }, [\n props.policyUrl\n ? h('a', { class: 'tb-link', href: props.policyUrl }, copy.policyLinkLabel)\n : null,\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-secondary',\n onClick: () => {\n if (props.api.resolved.some((r) => r.id === optOutId)) {\n props.api.deny(optOutId)\n }\n props.api.save()\n },\n },\n copy.optOutLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-primary',\n onClick: () => props.api.save(),\n },\n copy.acknowledgeLabel,\n ),\n ]),\n props.branding\n ? h('p', { class: 'tb-branding' }, [\n 'Powered by ',\n h(\n 'a',\n {\n href: 'https://tickbox.dev',\n target: '_blank',\n rel: 'noopener noreferrer',\n },\n 'Tickbox',\n ),\n ])\n : null,\n ],\n )\n }\n },\n})\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tickboxhq/banner-default",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Drop-in styled consent banner and notice components for Tickbox",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tickbox.dev",
@@ -29,13 +29,13 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@tickboxhq/core": "0.1.0"
32
+ "@tickboxhq/core": "0.1.2"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
36
36
  "vue": "^3.4.0",
37
- "@tickboxhq/react": "0.1.0",
38
- "@tickboxhq/vue": "0.1.0"
37
+ "@tickboxhq/react": "0.1.2",
38
+ "@tickboxhq/vue": "0.1.2"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@tickboxhq/react": {
@@ -61,8 +61,8 @@
61
61
  "typescript": "^5.7.2",
62
62
  "vitest": "^2.1.8",
63
63
  "vue": "^3.5.13",
64
- "@tickboxhq/vue": "0.1.0",
65
- "@tickboxhq/react": "0.1.0"
64
+ "@tickboxhq/react": "0.1.2",
65
+ "@tickboxhq/vue": "0.1.2"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/locales/de.ts","../src/shared/locales/en.ts","../src/shared/locales/es.ts","../src/shared/locales/fr.ts","../src/shared/locales/it.ts","../src/shared/locales/nl.ts","../src/shared/locales/pl.ts","../src/shared/locales/pt.ts","../src/shared/locales/uk.ts","../src/shared/locales/index.ts","../src/shared/styles.ts","../src/shared/theme.ts"],"names":["banner","notice"],"mappings":"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","file":"chunk-HCLSY5CT.js","sourcesContent":["import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies und Tracking',\n description:\n 'Wir verwenden Cookies, damit diese Website funktioniert, und mit Ihrer Einwilligung, um die Nutzung zu messen. Sie können selbst wählen, was Sie zulassen.',\n acceptLabel: 'Alle akzeptieren',\n rejectLabel: 'Alle ablehnen',\n customiseLabel: 'Anpassen',\n saveLabel: 'Einstellungen speichern',\n closeLabel: 'Schließen',\n policyLinkLabel: 'Datenschutz',\n requiredBadge: 'Erforderlich',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Hinweis zur Analyse',\n description:\n 'Wir verwenden datenschutzfreundliche Analyse-Tools, um zu verstehen, wie diese Website genutzt wird. Es werden keine personenbezogenen Daten erhoben und keine Werbeprofile erstellt.',\n acknowledgeLabel: 'Verstanden',\n optOutLabel: 'Ablehnen',\n policyLinkLabel: 'Datenschutz',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies and tracking',\n description:\n 'We use cookies to make this site work and, with your consent, to measure usage. You can choose what to allow.',\n acceptLabel: 'Accept all',\n rejectLabel: 'Reject all',\n customiseLabel: 'Customise',\n saveLabel: 'Save preferences',\n closeLabel: 'Close',\n policyLinkLabel: 'Privacy policy',\n requiredBadge: 'Required',\n}\n\nexport const notice: NoticeCopy = {\n title: 'A note about analytics',\n description:\n 'We use privacy-friendly analytics to understand how this site is used. No personal data is collected and no advertising profiles are built.',\n acknowledgeLabel: 'Got it',\n optOutLabel: 'Opt out',\n policyLinkLabel: 'Privacy policy',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies y seguimiento',\n description:\n 'Utilizamos cookies para que este sitio funcione y, con tu consentimiento, para medir su uso. Tú eliges lo que permites.',\n acceptLabel: 'Aceptar todo',\n rejectLabel: 'Rechazar todo',\n customiseLabel: 'Personalizar',\n saveLabel: 'Guardar preferencias',\n closeLabel: 'Cerrar',\n policyLinkLabel: 'Política de privacidad',\n requiredBadge: 'Obligatorio',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Sobre la analítica',\n description:\n 'Utilizamos analítica respetuosa con la privacidad para entender cómo se usa este sitio. No recopilamos datos personales ni creamos perfiles publicitarios.',\n acknowledgeLabel: 'Entendido',\n optOutLabel: 'Rechazar',\n policyLinkLabel: 'Política de privacidad',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies et suivi',\n description:\n 'Nous utilisons des cookies pour faire fonctionner ce site et, avec votre consentement, pour mesurer son utilisation. Vous choisissez ce que vous autorisez.',\n acceptLabel: 'Tout accepter',\n rejectLabel: 'Tout refuser',\n customiseLabel: 'Personnaliser',\n saveLabel: 'Enregistrer les préférences',\n closeLabel: 'Fermer',\n policyLinkLabel: 'Politique de confidentialité',\n requiredBadge: 'Requis',\n}\n\nexport const notice: NoticeCopy = {\n title: \"À propos de l'analyse\",\n description:\n \"Nous utilisons une analyse respectueuse de la vie privée pour comprendre comment ce site est utilisé. Aucune donnée personnelle n'est collectée et aucun profil publicitaire n'est constitué.\",\n acknowledgeLabel: 'Compris',\n optOutLabel: 'Refuser',\n policyLinkLabel: 'Politique de confidentialité',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookie e tracciamento',\n description:\n \"Utilizziamo i cookie per far funzionare il sito e, con il tuo consenso, per misurarne l'utilizzo. Puoi scegliere cosa consentire.\",\n acceptLabel: 'Accetta tutto',\n rejectLabel: 'Rifiuta tutto',\n customiseLabel: 'Personalizza',\n saveLabel: 'Salva preferenze',\n closeLabel: 'Chiudi',\n policyLinkLabel: 'Informativa sulla privacy',\n requiredBadge: 'Obbligatorio',\n}\n\nexport const notice: NoticeCopy = {\n title: \"Nota sull'analisi\",\n description:\n 'Utilizziamo strumenti di analisi rispettosi della privacy per capire come viene usato questo sito. Non raccogliamo dati personali né creiamo profili pubblicitari.',\n acknowledgeLabel: 'Ho capito',\n optOutLabel: 'Rifiuta',\n policyLinkLabel: 'Informativa sulla privacy',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies en tracking',\n description:\n 'Wij gebruiken cookies om deze site te laten werken en, met uw toestemming, om het gebruik te meten. U kiest wat u toestaat.',\n acceptLabel: 'Alles accepteren',\n rejectLabel: 'Alles weigeren',\n customiseLabel: 'Aanpassen',\n saveLabel: 'Voorkeuren opslaan',\n closeLabel: 'Sluiten',\n policyLinkLabel: 'Privacybeleid',\n requiredBadge: 'Vereist',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Over analytics',\n description:\n 'Wij gebruiken privacyvriendelijke analytics om te begrijpen hoe deze site wordt gebruikt. Er worden geen persoonsgegevens verzameld en er worden geen advertentieprofielen opgebouwd.',\n acknowledgeLabel: 'Begrepen',\n optOutLabel: 'Afmelden',\n policyLinkLabel: 'Privacybeleid',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Pliki cookie i śledzenie',\n description:\n 'Używamy plików cookie, aby ta strona działała, oraz – za Twoją zgodą – aby mierzyć jej użycie. To Ty decydujesz, na co pozwolić.',\n acceptLabel: 'Zaakceptuj wszystkie',\n rejectLabel: 'Odrzuć wszystkie',\n customiseLabel: 'Dostosuj',\n saveLabel: 'Zapisz preferencje',\n closeLabel: 'Zamknij',\n policyLinkLabel: 'Polityka prywatności',\n requiredBadge: 'Wymagane',\n}\n\nexport const notice: NoticeCopy = {\n title: 'O analityce',\n description:\n 'Używamy analityki przyjaznej prywatności, aby zrozumieć, jak korzysta się z tej strony. Nie zbieramy danych osobowych ani nie tworzymy profili reklamowych.',\n acknowledgeLabel: 'Rozumiem',\n optOutLabel: 'Zrezygnuj',\n policyLinkLabel: 'Polityka prywatności',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Cookies e rastreio',\n description:\n 'Utilizamos cookies para que este site funcione e, com o seu consentimento, para medir a sua utilização. Pode escolher o que permitir.',\n acceptLabel: 'Aceitar tudo',\n rejectLabel: 'Rejeitar tudo',\n customiseLabel: 'Personalizar',\n saveLabel: 'Guardar preferências',\n closeLabel: 'Fechar',\n policyLinkLabel: 'Política de privacidade',\n requiredBadge: 'Obrigatório',\n}\n\nexport const notice: NoticeCopy = {\n title: 'Sobre a análise',\n description:\n 'Utilizamos análises respeitadoras da privacidade para perceber como este site é utilizado. Não recolhemos dados pessoais nem criamos perfis publicitários.',\n acknowledgeLabel: 'Entendi',\n optOutLabel: 'Recusar',\n policyLinkLabel: 'Política de privacidade',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\n\nexport const banner: BannerCopy = {\n title: 'Файли cookie та відстеження',\n description:\n 'Ми використовуємо файли cookie, щоб цей сайт працював, а за вашою згодою — щоб вимірювати використання. Ви самі обираєте, що дозволити.',\n acceptLabel: 'Прийняти всі',\n rejectLabel: 'Відхилити всі',\n customiseLabel: 'Налаштувати',\n saveLabel: 'Зберегти налаштування',\n closeLabel: 'Закрити',\n policyLinkLabel: 'Політика конфіденційності',\n requiredBadge: \"Обов'язково\",\n}\n\nexport const notice: NoticeCopy = {\n title: 'Про аналітику',\n description:\n 'Ми використовуємо аналітику, що поважає приватність, щоб зрозуміти, як використовується цей сайт. Ми не збираємо персональні дані й не створюємо рекламні профілі.',\n acknowledgeLabel: 'Зрозуміло',\n optOutLabel: 'Відмовитися',\n policyLinkLabel: 'Політика конфіденційності',\n}\n","import type { BannerCopy, NoticeCopy } from '../copy.js'\nimport * as de from './de.js'\nimport * as en from './en.js'\nimport * as es from './es.js'\nimport * as fr from './fr.js'\nimport * as it from './it.js'\nimport * as nl from './nl.js'\nimport * as pl from './pl.js'\nimport * as pt from './pt.js'\nimport * as uk from './uk.js'\n\nexport type LocalePack = {\n banner: BannerCopy\n notice: NoticeCopy\n}\n\nexport const locales: Record<string, LocalePack> = {\n en: { banner: en.banner, notice: en.notice },\n de: { banner: de.banner, notice: de.notice },\n fr: { banner: fr.banner, notice: fr.notice },\n es: { banner: es.banner, notice: es.notice },\n it: { banner: it.banner, notice: it.notice },\n nl: { banner: nl.banner, notice: nl.notice },\n pt: { banner: pt.banner, notice: pt.notice },\n pl: { banner: pl.banner, notice: pl.notice },\n uk: { banner: uk.banner, notice: uk.notice },\n}\n\nexport type LocaleCode = keyof typeof locales\n\n/**\n * Resolve a BCP-47 locale tag to a built-in locale pack. Falls back from\n * the full tag (`en-GB`) to the language prefix (`en`), then to English.\n *\n * Pass `'auto'` to read `navigator.language` at call time. Anywhere that\n * `navigator` is missing (SSR, Node), `'auto'` falls back to English.\n */\nexport function resolveLocalePack(locale: string | undefined): LocalePack {\n if (!locale) return locales.en as LocalePack\n const tag = locale === 'auto' ? readNavigatorLanguage() : locale\n if (!tag) return locales.en as LocalePack\n const lower = tag.toLowerCase()\n if (locales[lower]) return locales[lower] as LocalePack\n const prefix = lower.split('-')[0]\n if (prefix && locales[prefix]) return locales[prefix] as LocalePack\n return locales.en as LocalePack\n}\n\nfunction readNavigatorLanguage(): string | undefined {\n if (typeof navigator === 'undefined') return undefined\n return navigator.language\n}\n","/**\n * Inline CSS for the default banner / notice / modal components.\n *\n * Uses CSS custom properties so users can re-theme without forking. Light\n * and dark themes are wired through `prefers-color-scheme` and the\n * `[data-tb-theme]` attribute.\n *\n * Visual style: GitHub-ish — system font, 6px corners, subtle border + soft\n * shadow, equal-prominence accept/reject buttons.\n */\nexport const TICKBOX_STYLES = `\n:where(.tb-root) {\n --tb-bg: #ffffff;\n --tb-fg: #1f2328;\n --tb-fg-muted: #59636e;\n --tb-border: #d1d9e0;\n --tb-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);\n --tb-primary-bg: #1f2328;\n --tb-primary-fg: #ffffff;\n --tb-primary-bg-hover: #000000;\n --tb-secondary-bg: #ffffff;\n --tb-secondary-fg: #1f2328;\n --tb-secondary-bg-hover: #f6f8fa;\n --tb-link: #0969da;\n --tb-radius: 6px;\n --tb-z: 2147483000;\n font-family:\n -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica,\n Arial, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\";\n color: var(--tb-fg);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n}\n@media (prefers-color-scheme: dark) {\n :where(.tb-root:not([data-tb-theme=\"light\"])) {\n --tb-bg: #0d1117;\n --tb-fg: #f0f6fc;\n --tb-fg-muted: #9198a1;\n --tb-border: #30363d;\n --tb-shadow: 0 8px 24px rgba(1, 4, 9, 0.85);\n --tb-primary-bg: #f0f6fc;\n --tb-primary-fg: #0d1117;\n --tb-primary-bg-hover: #ffffff;\n --tb-secondary-bg: #15191f;\n --tb-secondary-fg: #f0f6fc;\n --tb-secondary-bg-hover: #1f2328;\n --tb-link: #4493f8;\n }\n}\n:where(.tb-root[data-tb-theme=\"dark\"]) {\n --tb-bg: #0d1117;\n --tb-fg: #f0f6fc;\n --tb-fg-muted: #9198a1;\n --tb-border: #30363d;\n --tb-shadow: 0 8px 24px rgba(1, 4, 9, 0.85);\n --tb-primary-bg: #f0f6fc;\n --tb-primary-fg: #0d1117;\n --tb-primary-bg-hover: #ffffff;\n --tb-secondary-bg: #15191f;\n --tb-secondary-fg: #f0f6fc;\n --tb-secondary-bg-hover: #1f2328;\n --tb-link: #4493f8;\n}\n\n.tb-banner {\n position: fixed;\n left: 16px;\n right: 16px;\n bottom: 16px;\n max-width: 1100px;\n margin-inline: auto;\n z-index: var(--tb-z);\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n padding: 16px 20px;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-banner-text {\n flex: 1 1 320px;\n min-width: 0;\n}\n.tb-banner-title {\n font-weight: 600;\n margin: 0 0 2px;\n font-size: 14px;\n}\n.tb-banner-desc {\n margin: 0;\n color: var(--tb-fg-muted);\n}\n.tb-banner-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.tb-notice {\n position: fixed;\n right: 16px;\n bottom: 16px;\n z-index: var(--tb-z);\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n padding: 14px 16px;\n max-width: 360px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-notice-title {\n font-weight: 600;\n margin: 0 0 4px;\n font-size: 14px;\n}\n.tb-notice-desc {\n margin: 0 0 10px;\n color: var(--tb-fg-muted);\n font-size: 13px;\n}\n.tb-notice-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n\n.tb-link {\n color: var(--tb-link);\n text-decoration: none;\n font-size: 13px;\n margin-right: auto;\n}\n.tb-link:hover { text-decoration: underline; }\n\n.tb-btn {\n appearance: none;\n border: 1px solid transparent;\n border-radius: var(--tb-radius);\n padding: 6px 14px;\n font-size: 13px;\n font-weight: 500;\n font-family: inherit;\n cursor: pointer;\n line-height: 1.5;\n transition: background-color 80ms ease;\n white-space: nowrap;\n}\n.tb-btn:focus-visible {\n outline: 2px solid var(--tb-link);\n outline-offset: 2px;\n}\n.tb-btn-primary {\n background: var(--tb-primary-bg);\n color: var(--tb-primary-fg);\n}\n.tb-btn-primary:hover { background: var(--tb-primary-bg-hover); }\n.tb-btn-secondary {\n background: var(--tb-secondary-bg);\n color: var(--tb-secondary-fg);\n border-color: var(--tb-border);\n}\n.tb-btn-secondary:hover { background: var(--tb-secondary-bg-hover); }\n/*\n * Style for Accept All and Reject All on the first banner layer. They MUST\n * look identical — UK ICO and EU EDPB treat unequal visual weight on those\n * buttons as a dark pattern. Customisation should not break this symmetry;\n * if you need to apply brand colours, apply them here, not to one button.\n */\n.tb-btn-equal {\n background: var(--tb-secondary-bg);\n color: var(--tb-secondary-fg);\n border-color: var(--tb-border);\n}\n.tb-btn-equal:hover { background: var(--tb-secondary-bg-hover); }\n.tb-btn-ghost {\n background: transparent;\n color: var(--tb-fg-muted);\n padding: 6px 10px;\n}\n.tb-btn-ghost:hover { color: var(--tb-fg); }\n\n.tb-modal-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(15, 18, 24, 0.5);\n z-index: var(--tb-z);\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 20px;\n animation: tb-fade-in 160ms ease-out;\n}\n.tb-modal {\n background: var(--tb-bg);\n color: var(--tb-fg);\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n box-shadow: var(--tb-shadow);\n width: 100%;\n max-width: 520px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n}\n.tb-modal-head {\n padding: 14px 16px;\n border-bottom: 1px solid var(--tb-border);\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n}\n.tb-modal-title {\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n}\n.tb-modal-body {\n padding: 12px 16px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n.tb-modal-foot {\n padding: 12px 16px;\n border-top: 1px solid var(--tb-border);\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n flex-wrap: wrap;\n}\n\n.tb-cat {\n border: 1px solid var(--tb-border);\n border-radius: var(--tb-radius);\n padding: 12px;\n display: flex;\n gap: 12px;\n align-items: flex-start;\n}\n.tb-cat-text { flex: 1; min-width: 0; }\n.tb-cat-name {\n font-weight: 600;\n margin: 0 0 2px;\n font-size: 13px;\n display: flex;\n align-items: center;\n gap: 6px;\n}\n.tb-cat-desc {\n margin: 0;\n color: var(--tb-fg-muted);\n font-size: 13px;\n}\n.tb-badge {\n display: inline-block;\n font-size: 11px;\n font-weight: 500;\n color: var(--tb-fg-muted);\n background: var(--tb-secondary-bg-hover);\n border: 1px solid var(--tb-border);\n border-radius: 999px;\n padding: 1px 8px;\n}\n\n.tb-switch {\n position: relative;\n display: inline-block;\n width: 32px;\n height: 18px;\n flex-shrink: 0;\n margin-top: 2px;\n}\n.tb-switch input {\n opacity: 0;\n width: 0;\n height: 0;\n position: absolute;\n}\n.tb-switch-track {\n position: absolute;\n inset: 0;\n background: var(--tb-border);\n border-radius: 999px;\n transition: background-color 100ms ease;\n cursor: pointer;\n}\n.tb-switch-thumb {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 14px;\n height: 14px;\n background: var(--tb-bg);\n border-radius: 50%;\n transition: transform 100ms ease;\n}\n.tb-switch input:checked + .tb-switch-track {\n background: var(--tb-primary-bg);\n}\n.tb-switch input:checked + .tb-switch-track .tb-switch-thumb {\n transform: translateX(14px);\n}\n.tb-switch input:disabled + .tb-switch-track {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.tb-switch input:focus-visible + .tb-switch-track {\n outline: 2px solid var(--tb-link);\n outline-offset: 2px;\n}\n\n@keyframes tb-fade-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n@media (max-width: 640px) {\n .tb-banner {\n flex-direction: column;\n align-items: stretch;\n }\n .tb-banner-actions {\n flex-direction: column;\n }\n .tb-banner-actions .tb-btn { width: 100%; }\n}\n`\n\nconst STYLE_ID = 'tickbox-default-styles'\n\nlet injected = false\n\n/**\n * Insert the stylesheet into `<head>` exactly once per page. Safe to call\n * from every component mount — subsequent calls are no-ops. No-op on the\n * server (no `document`).\n */\nexport function injectStyles(): void {\n if (injected) return\n if (typeof document === 'undefined') return\n if (document.getElementById(STYLE_ID)) {\n injected = true\n return\n }\n const el = document.createElement('style')\n el.id = STYLE_ID\n el.textContent = TICKBOX_STYLES\n document.head.appendChild(el)\n injected = true\n}\n","/**\n * Runtime theme fetcher + cache for @tickboxhq/banner-default.\n *\n * The compliance contract (jurisdiction, categories, vendors) stays in the\n * customer's consent.config.ts under PR review. Presentation only — copy,\n * colours, locale — can be edited remotely in the dashboard and fetched\n * by the SDK at runtime so marketing teams can iterate without a deploy.\n *\n * Fetch strategy: read cached theme from localStorage and apply\n * synchronously on mount (no flicker), then refresh in the background.\n * Updated cache is written back; the *next* page load picks up the change.\n * Failed fetches are silent — the banner keeps rendering with whatever\n * cache or code defaults it had.\n */\n\nconst DEFAULT_ENDPOINT = 'https://api.tickbox.dev'\nconst STORAGE_KEY_PREFIX = '__tb_theme_'\n/** Treat cache older than 24h as suspect. We still render with it (no\n * flicker), but we always refetch — same as a CDN with stale-while-revalidate. */\nconst STALE_AFTER_MS = 24 * 60 * 60 * 1000\n\nexport type RemoteTheme = {\n copy?: {\n title?: string\n body?: string\n buttonAccept?: string\n buttonReject?: string\n buttonCustomise?: string\n privacyLink?: string\n }\n colors?: {\n accent?: string\n background?: string\n text?: string\n }\n locale?: string\n}\n\ntype CachedTheme = {\n theme: RemoteTheme\n fetchedAt: number\n}\n\n/** Synchronous read of the last cached theme. Used on mount to render\n * immediately without waiting for a network round-trip. */\nexport function readCachedTheme(siteId: string): RemoteTheme | null {\n if (typeof localStorage === 'undefined') return null\n try {\n const raw = localStorage.getItem(STORAGE_KEY_PREFIX + siteId)\n if (!raw) return null\n const parsed = JSON.parse(raw) as CachedTheme\n if (parsed && typeof parsed === 'object' && parsed.theme) {\n return parsed.theme\n }\n return null\n } catch {\n return null\n }\n}\n\n/** Fetch the latest theme from the API, write to cache. Returns the fresh\n * theme on success, `null` on any failure (caller already rendered the\n * cached one). */\nexport async function refreshTheme(\n siteId: string,\n endpoint: string = DEFAULT_ENDPOINT,\n): Promise<RemoteTheme | null> {\n if (typeof fetch === 'undefined') return null\n const url = `${endpoint.replace(/\\/+$/, '')}/v1/theme/${encodeURIComponent(siteId)}`\n try {\n const res = await fetch(url, { method: 'GET' })\n if (!res.ok) return null\n const body = (await res.json()) as { theme?: RemoteTheme }\n const theme = body?.theme ?? {}\n if (typeof localStorage !== 'undefined') {\n try {\n localStorage.setItem(\n STORAGE_KEY_PREFIX + siteId,\n JSON.stringify({ theme, fetchedAt: Date.now() } satisfies CachedTheme),\n )\n } catch {\n // Storage quota or disabled — fail silently. The banner still\n // renders with whatever's currently in memory.\n }\n }\n return theme\n } catch {\n return null\n }\n}\n\n/** Indicates whether the cached theme is older than the stale-after window.\n * Currently informational; we refetch on every mount regardless. Exposed\n * so future call sites can decide to skip the refetch (e.g. SSR). */\nexport function isStale(siteId: string): boolean {\n if (typeof localStorage === 'undefined') return true\n try {\n const raw = localStorage.getItem(STORAGE_KEY_PREFIX + siteId)\n if (!raw) return true\n const parsed = JSON.parse(raw) as CachedTheme\n return Date.now() - parsed.fetchedAt > STALE_AFTER_MS\n } catch {\n return true\n }\n}\n\n/**\n * Apply a theme's colour overrides as CSS custom properties on the banner\n * root element. The banner's own stylesheet reads these variables (or falls\n * back to defaults if unset), so theming is a matter of setting the right\n * --tb-* values.\n *\n * sanitise() drops anything outside the CSS-colour character set as defence\n * in depth against attribute injection — the values arrive via fetch from\n * api.tickbox.dev, so a compromise of the dashboard could only inject\n * benign CSS at worst.\n */\nexport function applyThemeColors(root: HTMLElement, theme: RemoteTheme | null): void {\n if (!theme?.colors) return\n if (theme.colors.accent)\n root.style.setProperty('--tb-primary-bg', sanitiseColor(theme.colors.accent))\n if (theme.colors.background)\n root.style.setProperty('--tb-bg', sanitiseColor(theme.colors.background))\n if (theme.colors.text) root.style.setProperty('--tb-fg', sanitiseColor(theme.colors.text))\n}\n\nfunction sanitiseColor(v: string): string {\n // Permit common CSS-colour characters: hex digits, color() / rgb() /\n // hsl() / oklch() function tokens. Block <, >, &, \", ' and newlines.\n return v.replace(/[<>&\"'\\n\\r\\\\]/g, '').slice(0, 64)\n}\n\n/**\n * Merge a RemoteTheme's `copy` block into a partial BannerCopy passed by\n * the caller (the customer's `copy` prop on <ConsentBannerDefault>). The\n * remote theme wins where set; code defaults fill in the rest at render\n * time via resolveLocalePack.\n */\nexport function mergeCopy<T extends Record<string, string>>(\n base: Partial<T>,\n remote: RemoteTheme['copy'] | undefined,\n): Partial<T> {\n if (!remote) return base\n const out: Record<string, string> = { ...(base as Record<string, string>) }\n if (remote.title) out.title = remote.title\n if (remote.body) out.description = remote.body\n if (remote.buttonAccept) out.acceptLabel = remote.buttonAccept\n if (remote.buttonReject) out.rejectLabel = remote.buttonReject\n if (remote.buttonCustomise) out.customiseLabel = remote.buttonCustomise\n if (remote.privacyLink) out.policyLinkLabel = remote.privacyLink\n return out as Partial<T>\n}\n"]}