@tickboxhq/banner-default 0.0.17 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MGAWC5P3.js → chunk-HCLSY5CT.js} +65 -3
- package/dist/chunk-HCLSY5CT.js.map +1 -0
- package/dist/react/index.d.ts +10 -0
- package/dist/react/index.js +62 -18
- package/dist/react/index.js.map +1 -1
- package/dist/vue/index.d.ts +34 -3
- package/dist/vue/index.js +42 -11
- package/dist/vue/index.js.map +1 -1
- package/package.json +4 -4
- package/dist/chunk-MGAWC5P3.js.map +0 -1
|
@@ -553,6 +553,68 @@ function injectStyles() {
|
|
|
553
553
|
injected = true;
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
556
|
+
// src/shared/theme.ts
|
|
557
|
+
var DEFAULT_ENDPOINT = "https://api.tickbox.dev";
|
|
558
|
+
var STORAGE_KEY_PREFIX = "__tb_theme_";
|
|
559
|
+
function readCachedTheme(siteId) {
|
|
560
|
+
if (typeof localStorage === "undefined") return null;
|
|
561
|
+
try {
|
|
562
|
+
const raw = localStorage.getItem(STORAGE_KEY_PREFIX + siteId);
|
|
563
|
+
if (!raw) return null;
|
|
564
|
+
const parsed = JSON.parse(raw);
|
|
565
|
+
if (parsed && typeof parsed === "object" && parsed.theme) {
|
|
566
|
+
return parsed.theme;
|
|
567
|
+
}
|
|
568
|
+
return null;
|
|
569
|
+
} catch {
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
async function refreshTheme(siteId, endpoint = DEFAULT_ENDPOINT) {
|
|
574
|
+
if (typeof fetch === "undefined") return null;
|
|
575
|
+
const url = `${endpoint.replace(/\/+$/, "")}/v1/theme/${encodeURIComponent(siteId)}`;
|
|
576
|
+
try {
|
|
577
|
+
const res = await fetch(url, { method: "GET" });
|
|
578
|
+
if (!res.ok) return null;
|
|
579
|
+
const body = await res.json();
|
|
580
|
+
const theme = body?.theme ?? {};
|
|
581
|
+
if (typeof localStorage !== "undefined") {
|
|
582
|
+
try {
|
|
583
|
+
localStorage.setItem(
|
|
584
|
+
STORAGE_KEY_PREFIX + siteId,
|
|
585
|
+
JSON.stringify({ theme, fetchedAt: Date.now() })
|
|
586
|
+
);
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return theme;
|
|
591
|
+
} catch {
|
|
592
|
+
return null;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
function applyThemeColors(root, theme) {
|
|
596
|
+
if (!theme?.colors) return;
|
|
597
|
+
if (theme.colors.accent)
|
|
598
|
+
root.style.setProperty("--tb-primary-bg", sanitiseColor(theme.colors.accent));
|
|
599
|
+
if (theme.colors.background)
|
|
600
|
+
root.style.setProperty("--tb-bg", sanitiseColor(theme.colors.background));
|
|
601
|
+
if (theme.colors.text) root.style.setProperty("--tb-fg", sanitiseColor(theme.colors.text));
|
|
602
|
+
}
|
|
603
|
+
function sanitiseColor(v) {
|
|
604
|
+
return v.replace(/[<>&"'\n\r\\]/g, "").slice(0, 64);
|
|
605
|
+
}
|
|
606
|
+
function mergeCopy(base, remote) {
|
|
607
|
+
if (!remote) return base;
|
|
608
|
+
const out = { ...base };
|
|
609
|
+
if (remote.title) out.title = remote.title;
|
|
610
|
+
if (remote.body) out.description = remote.body;
|
|
611
|
+
if (remote.buttonAccept) out.acceptLabel = remote.buttonAccept;
|
|
612
|
+
if (remote.buttonReject) out.rejectLabel = remote.buttonReject;
|
|
613
|
+
if (remote.buttonCustomise) out.customiseLabel = remote.buttonCustomise;
|
|
614
|
+
if (remote.privacyLink) out.policyLinkLabel = remote.privacyLink;
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export { applyThemeColors, injectStyles, locales, mergeCopy, readCachedTheme, refreshTheme, resolveLocalePack };
|
|
619
|
+
//# sourceMappingURL=chunk-HCLSY5CT.js.map
|
|
620
|
+
//# sourceMappingURL=chunk-HCLSY5CT.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-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"]}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -27,6 +27,16 @@ type ConsentBannerDefaultProps = {
|
|
|
27
27
|
* `prefers-color-scheme`.
|
|
28
28
|
*/
|
|
29
29
|
theme?: 'light' | 'dark';
|
|
30
|
+
/**
|
|
31
|
+
* Site identifier from app.tickbox.dev. When set, the banner fetches its
|
|
32
|
+
* remote presentation theme (copy + colours) on mount from
|
|
33
|
+
* cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last
|
|
34
|
+
* fetched theme is cached in localStorage so the next page load renders
|
|
35
|
+
* synchronously with no flicker.
|
|
36
|
+
*/
|
|
37
|
+
siteId?: string;
|
|
38
|
+
/** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
|
|
39
|
+
cloudEndpoint?: string;
|
|
30
40
|
};
|
|
31
41
|
/**
|
|
32
42
|
* Drop-in styled consent banner. Mounts itself only when the headless
|
package/dist/react/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { locales, resolveLocalePack } from '../chunk-
|
|
1
|
+
import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-HCLSY5CT.js';
|
|
2
|
+
export { locales, resolveLocalePack } from '../chunk-HCLSY5CT.js';
|
|
3
3
|
import { ConsentBanner, ConsentNotice } from '@tickboxhq/react';
|
|
4
|
-
import { useState, useEffect,
|
|
4
|
+
import { useState, useEffect, useRef, useId } from 'react';
|
|
5
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
function ConsentBannerDefault(props) {
|
|
@@ -11,31 +11,60 @@ function BannerInner({
|
|
|
11
11
|
api,
|
|
12
12
|
props
|
|
13
13
|
}) {
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const [remoteTheme, setRemoteTheme] = useState(
|
|
15
|
+
() => props.siteId ? readCachedTheme(props.siteId) : null
|
|
16
|
+
);
|
|
16
17
|
useEffect(() => {
|
|
17
18
|
injectStyles();
|
|
18
|
-
|
|
19
|
+
if (!props.siteId) return;
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
void (async () => {
|
|
22
|
+
const fresh = await refreshTheme(props.siteId, props.cloudEndpoint);
|
|
23
|
+
if (!cancelled && fresh) setRemoteTheme(fresh);
|
|
24
|
+
})();
|
|
25
|
+
return () => {
|
|
26
|
+
cancelled = true;
|
|
27
|
+
};
|
|
28
|
+
}, [props.siteId, props.cloudEndpoint]);
|
|
29
|
+
const mergedCopyOverrides = mergeCopy(props.copy ?? {}, remoteTheme?.copy);
|
|
30
|
+
const locale = remoteTheme?.locale ?? props.locale;
|
|
31
|
+
const copy = { ...resolveLocalePack(locale).banner, ...mergedCopyOverrides };
|
|
32
|
+
const [showModal, setShowModal] = useState(false);
|
|
33
|
+
const rootRef = useRef(null);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (rootRef.current) applyThemeColors(rootRef.current, remoteTheme);
|
|
36
|
+
}, [remoteTheme]);
|
|
19
37
|
const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
|
|
20
38
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
21
|
-
/* @__PURE__ */ jsxs(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
/* @__PURE__ */ jsxs(
|
|
40
|
+
"div",
|
|
41
|
+
{
|
|
42
|
+
ref: rootRef,
|
|
43
|
+
className: "tb-root tb-banner",
|
|
44
|
+
role: "region",
|
|
45
|
+
"aria-label": copy.title,
|
|
46
|
+
...themeAttrs,
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("div", { className: "tb-banner-text", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("p", { className: "tb-banner-title", children: copy.title }),
|
|
50
|
+
/* @__PURE__ */ jsx("p", { className: "tb-banner-desc", children: copy.description })
|
|
51
|
+
] }),
|
|
52
|
+
/* @__PURE__ */ jsxs("div", { className: "tb-banner-actions", children: [
|
|
53
|
+
props.policyUrl && /* @__PURE__ */ jsx("a", { className: "tb-link", href: props.policyUrl, children: copy.policyLinkLabel }),
|
|
54
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.denyAll(), children: copy.rejectLabel }),
|
|
55
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-ghost", onClick: () => setShowModal(true), children: copy.customiseLabel }),
|
|
56
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.grantAll(), children: copy.acceptLabel })
|
|
57
|
+
] })
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
),
|
|
33
61
|
showModal && /* @__PURE__ */ jsx(
|
|
34
62
|
CustomiseModal,
|
|
35
63
|
{
|
|
36
64
|
api,
|
|
37
65
|
copy,
|
|
38
66
|
theme: props.theme,
|
|
67
|
+
remoteTheme,
|
|
39
68
|
onClose: () => setShowModal(false)
|
|
40
69
|
}
|
|
41
70
|
)
|
|
@@ -45,6 +74,7 @@ function CustomiseModal({
|
|
|
45
74
|
api,
|
|
46
75
|
copy,
|
|
47
76
|
theme,
|
|
77
|
+
remoteTheme,
|
|
48
78
|
onClose
|
|
49
79
|
}) {
|
|
50
80
|
const titleId = useId();
|
|
@@ -66,10 +96,21 @@ function CustomiseModal({
|
|
|
66
96
|
};
|
|
67
97
|
}, [onClose]);
|
|
68
98
|
const themeAttrs = theme ? { "data-tb-theme": theme } : {};
|
|
99
|
+
const modalStyle = {};
|
|
100
|
+
if (remoteTheme?.colors?.accent) {
|
|
101
|
+
modalStyle["--tb-primary-bg"] = sanitiseColor(remoteTheme.colors.accent);
|
|
102
|
+
}
|
|
103
|
+
if (remoteTheme?.colors?.background) {
|
|
104
|
+
modalStyle["--tb-bg"] = sanitiseColor(remoteTheme.colors.background);
|
|
105
|
+
}
|
|
106
|
+
if (remoteTheme?.colors?.text) {
|
|
107
|
+
modalStyle["--tb-fg"] = sanitiseColor(remoteTheme.colors.text);
|
|
108
|
+
}
|
|
69
109
|
return /* @__PURE__ */ jsx(
|
|
70
110
|
"div",
|
|
71
111
|
{
|
|
72
112
|
className: "tb-root tb-modal-backdrop",
|
|
113
|
+
style: modalStyle,
|
|
73
114
|
onClick: onClose,
|
|
74
115
|
onKeyDown: (e) => {
|
|
75
116
|
if (e.key === "Escape") onClose();
|
|
@@ -157,6 +198,9 @@ function trapFocus(e, container) {
|
|
|
157
198
|
first.focus();
|
|
158
199
|
}
|
|
159
200
|
}
|
|
201
|
+
function sanitiseColor(v) {
|
|
202
|
+
return v.replace(/[<>&"'\n\r\\]/g, "").slice(0, 64);
|
|
203
|
+
}
|
|
160
204
|
function ConsentNoticeDefault(props) {
|
|
161
205
|
return /* @__PURE__ */ jsx(ConsentNotice, { children: (api) => /* @__PURE__ */ jsx(NoticeInner, { api, props }) });
|
|
162
206
|
}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/banner.tsx","../../src/react/notice.tsx"],"names":["jsx","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'\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\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 const copy: BannerCopy = { ...resolveLocalePack(props.locale).banner, ...props.copy }\n const [showModal, setShowModal] = useState(false)\n\n useEffect(() => {\n injectStyles()\n }, [])\n\n const themeAttrs = props.theme ? { 'data-tb-theme': props.theme } : {}\n\n return (\n <>\n <div className=\"tb-root tb-banner\" role=\"region\" aria-label={copy.title} {...themeAttrs}>\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 onClose={() => setShowModal(false)}\n />\n )}\n </>\n )\n}\n\nfunction CustomiseModal({\n api,\n copy,\n theme,\n onClose,\n}: {\n api: ConsentApi\n copy: BannerCopy\n theme?: 'light' | 'dark'\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 return (\n <div\n className=\"tb-root tb-modal-backdrop\"\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","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","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\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"]}
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -13,6 +13,16 @@ type ConsentBannerDefaultProps = {
|
|
|
13
13
|
copy?: Partial<BannerCopy>;
|
|
14
14
|
policyUrl?: string;
|
|
15
15
|
theme?: 'light' | 'dark';
|
|
16
|
+
/**
|
|
17
|
+
* Site identifier from app.tickbox.dev. When set, the banner fetches its
|
|
18
|
+
* remote presentation theme (copy + colours) on mount from
|
|
19
|
+
* cloudEndpoint/v1/theme/{siteId} and applies it on top of `copy`. Last
|
|
20
|
+
* fetched theme is cached in localStorage so the next page load renders
|
|
21
|
+
* synchronously with no flicker.
|
|
22
|
+
*/
|
|
23
|
+
siteId?: string;
|
|
24
|
+
/** Defaults to https://api.tickbox.dev. Override only for self-hosting. */
|
|
25
|
+
cloudEndpoint?: string;
|
|
16
26
|
};
|
|
17
27
|
/**
|
|
18
28
|
* Drop-in styled consent banner for Vue. Mounts only when the headless
|
|
@@ -31,7 +41,10 @@ type ConsentBannerDefaultProps = {
|
|
|
31
41
|
* import config from './consent.config'
|
|
32
42
|
* </script>
|
|
33
43
|
* <template>
|
|
34
|
-
* <ConsentBannerDefault
|
|
44
|
+
* <ConsentBannerDefault
|
|
45
|
+
* :site-id="config.cloud?.siteId"
|
|
46
|
+
* :policy-url="config.policy?.url"
|
|
47
|
+
* />
|
|
35
48
|
* </template>
|
|
36
49
|
* ```
|
|
37
50
|
*/
|
|
@@ -52,6 +65,14 @@ declare const ConsentBannerDefault: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
52
65
|
type: PropType<"light" | "dark" | undefined>;
|
|
53
66
|
default: undefined;
|
|
54
67
|
};
|
|
68
|
+
siteId: {
|
|
69
|
+
type: PropType<string | undefined>;
|
|
70
|
+
default: undefined;
|
|
71
|
+
};
|
|
72
|
+
cloudEndpoint: {
|
|
73
|
+
type: PropType<string | undefined>;
|
|
74
|
+
default: undefined;
|
|
75
|
+
};
|
|
55
76
|
}>, () => vue.VNode<vue.RendererNode, vue.RendererElement, {
|
|
56
77
|
[key: string]: any;
|
|
57
78
|
}>, {}, {}, {}, vue.ComponentOptionsMixin, vue.ComponentOptionsMixin, {}, string, vue.PublicProps, Readonly<vue.ExtractPropTypes<{
|
|
@@ -71,11 +92,21 @@ declare const ConsentBannerDefault: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
71
92
|
type: PropType<"light" | "dark" | undefined>;
|
|
72
93
|
default: undefined;
|
|
73
94
|
};
|
|
95
|
+
siteId: {
|
|
96
|
+
type: PropType<string | undefined>;
|
|
97
|
+
default: undefined;
|
|
98
|
+
};
|
|
99
|
+
cloudEndpoint: {
|
|
100
|
+
type: PropType<string | undefined>;
|
|
101
|
+
default: undefined;
|
|
102
|
+
};
|
|
74
103
|
}>> & Readonly<{}>, {
|
|
75
|
-
locale: string | undefined;
|
|
76
104
|
copy: Partial<BannerCopy>;
|
|
105
|
+
locale: string | undefined;
|
|
77
106
|
policyUrl: string | undefined;
|
|
78
107
|
theme: "light" | "dark" | undefined;
|
|
108
|
+
siteId: string | undefined;
|
|
109
|
+
cloudEndpoint: string | undefined;
|
|
79
110
|
}, {}, {}, {}, string, vue.ComponentProvideOptions, true, {}, any>;
|
|
80
111
|
|
|
81
112
|
type ConsentNoticeDefaultProps = {
|
|
@@ -135,8 +166,8 @@ declare const ConsentNoticeDefault: vue.DefineComponent<vue.ExtractPropTypes<{
|
|
|
135
166
|
default: undefined;
|
|
136
167
|
};
|
|
137
168
|
}>> & Readonly<{}>, {
|
|
138
|
-
locale: string | undefined;
|
|
139
169
|
copy: Partial<NoticeCopy>;
|
|
170
|
+
locale: string | undefined;
|
|
140
171
|
policyUrl: string | undefined;
|
|
141
172
|
theme: "light" | "dark" | undefined;
|
|
142
173
|
optOutCategoryId: string;
|
package/dist/vue/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { injectStyles, resolveLocalePack } from '../chunk-
|
|
2
|
-
export { locales, resolveLocalePack } from '../chunk-
|
|
1
|
+
import { readCachedTheme, injectStyles, refreshTheme, mergeCopy, resolveLocalePack, applyThemeColors } from '../chunk-HCLSY5CT.js';
|
|
2
|
+
export { locales, resolveLocalePack } from '../chunk-HCLSY5CT.js';
|
|
3
3
|
import { ConsentBanner, ConsentNotice } from '@tickboxhq/vue';
|
|
4
|
-
import { defineComponent, onMounted, h,
|
|
4
|
+
import { defineComponent, ref, onMounted, h, watch } from 'vue';
|
|
5
5
|
|
|
6
6
|
var ConsentBannerDefault = defineComponent({
|
|
7
7
|
name: "ConsentBannerDefault",
|
|
@@ -9,17 +9,26 @@ var ConsentBannerDefault = defineComponent({
|
|
|
9
9
|
locale: { type: String, default: void 0 },
|
|
10
10
|
copy: { type: Object, default: () => ({}) },
|
|
11
11
|
policyUrl: { type: String, default: void 0 },
|
|
12
|
-
theme: { type: String, default: void 0 }
|
|
12
|
+
theme: { type: String, default: void 0 },
|
|
13
|
+
siteId: { type: String, default: void 0 },
|
|
14
|
+
cloudEndpoint: { type: String, default: void 0 }
|
|
13
15
|
},
|
|
14
16
|
setup(props) {
|
|
15
|
-
|
|
17
|
+
const remoteTheme = ref(props.siteId ? readCachedTheme(props.siteId) : null);
|
|
18
|
+
onMounted(async () => {
|
|
19
|
+
injectStyles();
|
|
20
|
+
if (!props.siteId) return;
|
|
21
|
+
const fresh = await refreshTheme(props.siteId, props.cloudEndpoint);
|
|
22
|
+
if (fresh) remoteTheme.value = fresh;
|
|
23
|
+
});
|
|
16
24
|
return () => h(ConsentBanner, null, {
|
|
17
25
|
default: (api) => h(BannerInner, {
|
|
18
26
|
api,
|
|
19
|
-
locale: props.locale,
|
|
20
|
-
userCopy: props.copy ?? {},
|
|
27
|
+
locale: remoteTheme.value?.locale ?? props.locale,
|
|
28
|
+
userCopy: mergeCopy(props.copy ?? {}, remoteTheme.value?.copy),
|
|
21
29
|
policyUrl: props.policyUrl,
|
|
22
|
-
theme: props.theme
|
|
30
|
+
theme: props.theme,
|
|
31
|
+
remoteTheme: remoteTheme.value
|
|
23
32
|
})
|
|
24
33
|
});
|
|
25
34
|
}
|
|
@@ -31,10 +40,17 @@ var BannerInner = defineComponent({
|
|
|
31
40
|
locale: { type: String, default: void 0 },
|
|
32
41
|
userCopy: { type: Object, required: true },
|
|
33
42
|
policyUrl: { type: String, default: void 0 },
|
|
34
|
-
theme: { type: String, default: void 0 }
|
|
43
|
+
theme: { type: String, default: void 0 },
|
|
44
|
+
remoteTheme: { type: Object, default: null }
|
|
35
45
|
},
|
|
36
46
|
setup(props) {
|
|
37
47
|
const showModal = ref(false);
|
|
48
|
+
const rootRef = ref(null);
|
|
49
|
+
const applyColors = () => {
|
|
50
|
+
if (rootRef.value) applyThemeColors(rootRef.value, props.remoteTheme);
|
|
51
|
+
};
|
|
52
|
+
onMounted(applyColors);
|
|
53
|
+
watch(() => props.remoteTheme, applyColors, { deep: true });
|
|
38
54
|
return () => {
|
|
39
55
|
const copy = {
|
|
40
56
|
...resolveLocalePack(props.locale).banner,
|
|
@@ -45,6 +61,7 @@ var BannerInner = defineComponent({
|
|
|
45
61
|
h(
|
|
46
62
|
"div",
|
|
47
63
|
{
|
|
64
|
+
ref: rootRef,
|
|
48
65
|
class: "tb-root tb-banner",
|
|
49
66
|
role: "region",
|
|
50
67
|
"aria-label": copy.title,
|
|
@@ -89,20 +106,31 @@ var BannerInner = defineComponent({
|
|
|
89
106
|
])
|
|
90
107
|
]
|
|
91
108
|
),
|
|
92
|
-
showModal.value ? renderModal(props.api, copy, props.theme, () => {
|
|
109
|
+
showModal.value ? renderModal(props.api, copy, props.theme, props.remoteTheme, () => {
|
|
93
110
|
showModal.value = false;
|
|
94
111
|
}) : null
|
|
95
112
|
]);
|
|
96
113
|
};
|
|
97
114
|
}
|
|
98
115
|
});
|
|
99
|
-
function renderModal(api, copy, theme, onClose) {
|
|
116
|
+
function renderModal(api, copy, theme, remoteTheme, onClose) {
|
|
100
117
|
const themeAttrs = theme ? { "data-tb-theme": theme } : {};
|
|
118
|
+
const inlineStyle = {};
|
|
119
|
+
if (remoteTheme?.colors?.accent) {
|
|
120
|
+
inlineStyle["--tb-primary-bg"] = sanitiseColor(remoteTheme.colors.accent);
|
|
121
|
+
}
|
|
122
|
+
if (remoteTheme?.colors?.background) {
|
|
123
|
+
inlineStyle["--tb-bg"] = sanitiseColor(remoteTheme.colors.background);
|
|
124
|
+
}
|
|
125
|
+
if (remoteTheme?.colors?.text) {
|
|
126
|
+
inlineStyle["--tb-fg"] = sanitiseColor(remoteTheme.colors.text);
|
|
127
|
+
}
|
|
101
128
|
return h(
|
|
102
129
|
"div",
|
|
103
130
|
{
|
|
104
131
|
class: "tb-root tb-modal-backdrop",
|
|
105
132
|
role: "presentation",
|
|
133
|
+
style: inlineStyle,
|
|
106
134
|
onClick: onClose,
|
|
107
135
|
onKeydown: (e) => {
|
|
108
136
|
if (e.key === "Escape") onClose();
|
|
@@ -192,6 +220,9 @@ function renderModal(api, copy, theme, onClose) {
|
|
|
192
220
|
]
|
|
193
221
|
);
|
|
194
222
|
}
|
|
223
|
+
function sanitiseColor(v) {
|
|
224
|
+
return v.replace(/[<>&"'\n\r\\]/g, "").slice(0, 64);
|
|
225
|
+
}
|
|
195
226
|
var ConsentNoticeDefault = defineComponent({
|
|
196
227
|
name: "ConsentNoticeDefault",
|
|
197
228
|
props: {
|
package/dist/vue/index.js.map
CHANGED
|
@@ -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 } from 'vue'\nimport type { BannerCopy } from '../shared/copy.js'\nimport { resolveLocalePack } from '../shared/locales/index.js'\nimport { injectStyles } from '../shared/styles.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\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 :policy-url=\"config.policy?.url\" />\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 },\n setup(props) {\n onMounted(() => injectStyles())\n return () =>\n h(ConsentBanner, null, {\n default: (api: unknown) =>\n h(BannerInner, {\n api: api as ConsentSlotApi,\n locale: props.locale,\n userCopy: props.copy ?? {},\n policyUrl: props.policyUrl,\n theme: props.theme,\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 },\n setup(props) {\n const showModal = ref(false)\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 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, () => {\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 onClose: () => void,\n) {\n const themeAttrs = theme ? { 'data-tb-theme': theme } : {}\n\n return h(\n 'div',\n {\n class: 'tb-root tb-modal-backdrop',\n role: 'presentation',\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","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","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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tickboxhq/banner-default",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Drop-in styled consent banner and notice components for Tickbox",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://tickbox.dev",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@tickboxhq/core": "0.0.
|
|
32
|
+
"@tickboxhq/core": "0.0.18"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
"typescript": "^5.7.2",
|
|
62
62
|
"vitest": "^2.1.8",
|
|
63
63
|
"vue": "^3.5.13",
|
|
64
|
-
"@tickboxhq/
|
|
65
|
-
"@tickboxhq/
|
|
64
|
+
"@tickboxhq/react": "0.0.17",
|
|
65
|
+
"@tickboxhq/vue": "0.0.17"
|
|
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"],"names":["banner","notice"],"mappings":"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","file":"chunk-MGAWC5P3.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"]}
|