@tickboxhq/banner-default 0.0.12 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -98,6 +98,22 @@ The full set: `--tb-bg`, `--tb-fg`, `--tb-fg-muted`, `--tb-border`, `--tb-shadow
98
98
 
99
99
  Light/dark follows `prefers-color-scheme`. Pass `theme="light"` or `theme="dark"` to override.
100
100
 
101
+ ### Equal prominence — read this before re-styling
102
+
103
+ Accept All and Reject All on the first banner layer use the `.tb-btn-equal` class and look identical by default. This is deliberate. UK ICO and EU EDPB guidance treats unequal visual weight on those buttons as a dark pattern, and ICO has fined sites for it.
104
+
105
+ If you want to add brand colours, apply them to `.tb-btn-equal` so both buttons change together:
106
+
107
+ ```css
108
+ .tb-root .tb-btn-equal {
109
+ background: #0070c4;
110
+ color: #fff;
111
+ border-color: #0070c4;
112
+ }
113
+ ```
114
+
115
+ Do NOT use `--tb-primary-bg` to brand the Accept button alone — `--tb-primary-bg` is reserved for the modal Save button (a second-layer action where the user has already engaged with the customise flow, so equal-prominence rules don't apply the same way). Overriding it won't affect the first-layer banner buttons.
116
+
101
117
  ## Banner vs notice
102
118
 
103
119
  Use the banner when you have any `consent`-mode categories — most EU sites. It's a bottom bar with Accept all / Reject all / Customise, and Customise opens a modal with per-category toggles.
@@ -180,6 +180,18 @@ var TICKBOX_STYLES = `
180
180
  border-color: var(--tb-border);
181
181
  }
182
182
  .tb-btn-secondary:hover { background: var(--tb-secondary-bg-hover); }
183
+ /*
184
+ * Style for Accept All and Reject All on the first banner layer. They MUST
185
+ * look identical \u2014 UK ICO and EU EDPB treat unequal visual weight on those
186
+ * buttons as a dark pattern. Customisation should not break this symmetry;
187
+ * if you need to apply brand colours, apply them here, not to one button.
188
+ */
189
+ .tb-btn-equal {
190
+ background: var(--tb-secondary-bg);
191
+ color: var(--tb-secondary-fg);
192
+ border-color: var(--tb-border);
193
+ }
194
+ .tb-btn-equal:hover { background: var(--tb-secondary-bg-hover); }
183
195
  .tb-btn-ghost {
184
196
  background: transparent;
185
197
  color: var(--tb-fg-muted);
@@ -352,5 +364,5 @@ function injectStyles() {
352
364
  }
353
365
 
354
366
  export { DEFAULT_BANNER_COPY, DEFAULT_NOTICE_COPY, injectStyles };
355
- //# sourceMappingURL=chunk-WJSFVAK4.js.map
356
- //# sourceMappingURL=chunk-WJSFVAK4.js.map
367
+ //# sourceMappingURL=chunk-DGDSIXCT.js.map
368
+ //# sourceMappingURL=chunk-DGDSIXCT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/copy.ts","../src/shared/styles.ts"],"names":[],"mappings":"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","file":"chunk-DGDSIXCT.js","sourcesContent":["export type BannerCopy = {\n title: string\n description: string\n acceptLabel: string\n rejectLabel: string\n customiseLabel: string\n saveLabel: string\n closeLabel: string\n policyLinkLabel: string\n requiredBadge: string\n}\n\nexport type NoticeCopy = {\n title: string\n description: string\n acknowledgeLabel: string\n optOutLabel: string\n policyLinkLabel: string\n}\n\nexport const DEFAULT_BANNER_COPY: 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 DEFAULT_NOTICE_COPY: 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","/**\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 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 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"]}
@@ -1,4 +1,4 @@
1
- import { DEFAULT_BANNER_COPY, injectStyles, DEFAULT_NOTICE_COPY } from '../chunk-WJSFVAK4.js';
1
+ import { DEFAULT_BANNER_COPY, injectStyles, DEFAULT_NOTICE_COPY } from '../chunk-DGDSIXCT.js';
2
2
  import { ConsentBanner, ConsentNotice } from '@tickboxhq/react';
3
3
  import { useState, useEffect, useId, useRef } from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -24,17 +24,9 @@ function BannerInner({
24
24
  ] }),
25
25
  /* @__PURE__ */ jsxs("div", { className: "tb-banner-actions", children: [
26
26
  props.policyUrl && /* @__PURE__ */ jsx("a", { className: "tb-link", href: props.policyUrl, children: copy.policyLinkLabel }),
27
- /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-secondary", onClick: () => api.denyAll(), children: copy.rejectLabel }),
28
- /* @__PURE__ */ jsx(
29
- "button",
30
- {
31
- type: "button",
32
- className: "tb-btn tb-btn-secondary",
33
- onClick: () => setShowModal(true),
34
- children: copy.customiseLabel
35
- }
36
- ),
37
- /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-primary", onClick: () => api.grantAll(), children: copy.acceptLabel })
27
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.denyAll(), children: copy.rejectLabel }),
28
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-ghost", onClick: () => setShowModal(true), children: copy.customiseLabel }),
29
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.grantAll(), children: copy.acceptLabel })
38
30
  ] })
39
31
  ] }),
40
32
  showModal && /* @__PURE__ */ jsx(
@@ -137,7 +129,7 @@ function CustomiseModal({
137
129
  ] }, cat.id);
138
130
  }) }),
139
131
  /* @__PURE__ */ jsxs("div", { className: "tb-modal-foot", children: [
140
- /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-secondary", onClick: () => api.denyAll(), children: copy.rejectLabel }),
132
+ /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-equal", onClick: () => api.denyAll(), children: copy.rejectLabel }),
141
133
  /* @__PURE__ */ jsx("button", { type: "button", className: "tb-btn tb-btn-primary", onClick: () => api.save(), children: copy.saveLabel })
142
134
  ] })
143
135
  ]
@@ -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, DEFAULT_BANNER_COPY } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * Override individual labels and copy strings. Anything you don't pass\n * falls back to the English defaults.\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 = { ...DEFAULT_BANNER_COPY, ...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-secondary\" onClick={() => api.denyAll()}>\n {copy.rejectLabel}\n </button>\n <button\n type=\"button\"\n className=\"tb-btn tb-btn-secondary\"\n onClick={() => setShowModal(true)}\n >\n {copy.customiseLabel}\n </button>\n <button type=\"button\" className=\"tb-btn tb-btn-primary\" 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-secondary\" 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 { DEFAULT_NOTICE_COPY, type NoticeCopy } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\n /**\n * Override individual labels and copy strings. Anything you don't pass\n * falls back to the English defaults.\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 = { ...DEFAULT_NOTICE_COPY, ...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, DEFAULT_BANNER_COPY } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentBannerDefaultProps = {\n /**\n * Override individual labels and copy strings. Anything you don't pass\n * falls back to the English defaults.\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 = { ...DEFAULT_BANNER_COPY, ...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 { DEFAULT_NOTICE_COPY, type NoticeCopy } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\n /**\n * Override individual labels and copy strings. Anything you don't pass\n * falls back to the English defaults.\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 = { ...DEFAULT_NOTICE_COPY, ...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"]}
@@ -12,6 +12,11 @@ type ConsentBannerDefaultProps = {
12
12
  * `<ConsentBanner>` says it should be open. "Customise" opens a modal
13
13
  * with per-category toggles.
14
14
  *
15
+ * Equal-prominence design: Accept All and Reject All use identical button
16
+ * styling. UK ICO and EU EDPB guidance treats unequal visual weight on
17
+ * those buttons as a dark pattern. Customise is rendered as a ghost button
18
+ * so the two consent paths stay symmetrical.
19
+ *
15
20
  * @example
16
21
  * ```vue
17
22
  * <script setup lang="ts">
package/dist/vue/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { injectStyles, DEFAULT_BANNER_COPY, DEFAULT_NOTICE_COPY } from '../chunk-WJSFVAK4.js';
1
+ import { injectStyles, DEFAULT_BANNER_COPY, DEFAULT_NOTICE_COPY } from '../chunk-DGDSIXCT.js';
2
2
  import { ConsentBanner, ConsentNotice } from '@tickboxhq/vue';
3
- import { defineComponent, onMounted, ref, h } from 'vue';
3
+ import { defineComponent, onMounted, h, ref } from 'vue';
4
4
 
5
5
  var ConsentBannerDefault = defineComponent({
6
6
  name: "ConsentBannerDefault",
@@ -11,68 +11,84 @@ var ConsentBannerDefault = defineComponent({
11
11
  },
12
12
  setup(props) {
13
13
  onMounted(() => injectStyles());
14
- const showModal = ref(false);
15
14
  return () => h(ConsentBanner, null, {
16
- default: (api) => renderBanner(api, props, showModal)
15
+ default: (api) => h(BannerInner, {
16
+ api,
17
+ userCopy: props.copy ?? {},
18
+ policyUrl: props.policyUrl,
19
+ theme: props.theme
20
+ })
17
21
  });
18
22
  }
19
23
  });
20
- function renderBanner(api, props, showModal) {
21
- const copy = { ...DEFAULT_BANNER_COPY, ...props.copy ?? {} };
22
- const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
23
- return h("div", null, [
24
- h(
25
- "div",
26
- {
27
- class: "tb-root tb-banner",
28
- role: "region",
29
- "aria-label": copy.title,
30
- ...themeAttrs
31
- },
32
- [
33
- h("div", { class: "tb-banner-text" }, [
34
- h("p", { class: "tb-banner-title" }, copy.title),
35
- h("p", { class: "tb-banner-desc" }, copy.description)
36
- ]),
37
- h("div", { class: "tb-banner-actions" }, [
38
- props.policyUrl ? h("a", { class: "tb-link", href: props.policyUrl }, copy.policyLinkLabel) : null,
39
- h(
40
- "button",
41
- {
42
- type: "button",
43
- class: "tb-btn tb-btn-secondary",
44
- onClick: () => api.denyAll()
45
- },
46
- copy.rejectLabel
47
- ),
48
- h(
49
- "button",
50
- {
51
- type: "button",
52
- class: "tb-btn tb-btn-secondary",
53
- onClick: () => {
54
- showModal.value = true;
55
- }
56
- },
57
- copy.customiseLabel
58
- ),
59
- h(
60
- "button",
61
- {
62
- type: "button",
63
- class: "tb-btn tb-btn-primary",
64
- onClick: () => api.grantAll()
65
- },
66
- copy.acceptLabel
67
- )
68
- ])
69
- ]
70
- ),
71
- showModal.value ? renderModal(api, copy, props.theme, () => {
72
- showModal.value = false;
73
- }) : null
74
- ]);
75
- }
24
+ var BannerInner = defineComponent({
25
+ name: "ConsentBannerDefaultInner",
26
+ props: {
27
+ api: { type: Object, required: true },
28
+ userCopy: { type: Object, required: true },
29
+ policyUrl: { type: String, default: void 0 },
30
+ theme: { type: String, default: void 0 }
31
+ },
32
+ setup(props) {
33
+ const showModal = ref(false);
34
+ return () => {
35
+ const copy = { ...DEFAULT_BANNER_COPY, ...props.userCopy };
36
+ const themeAttrs = props.theme ? { "data-tb-theme": props.theme } : {};
37
+ return h("div", null, [
38
+ h(
39
+ "div",
40
+ {
41
+ class: "tb-root tb-banner",
42
+ role: "region",
43
+ "aria-label": copy.title,
44
+ ...themeAttrs
45
+ },
46
+ [
47
+ h("div", { class: "tb-banner-text" }, [
48
+ h("p", { class: "tb-banner-title" }, copy.title),
49
+ h("p", { class: "tb-banner-desc" }, copy.description)
50
+ ]),
51
+ h("div", { class: "tb-banner-actions" }, [
52
+ props.policyUrl ? h("a", { class: "tb-link", href: props.policyUrl }, copy.policyLinkLabel) : null,
53
+ h(
54
+ "button",
55
+ {
56
+ type: "button",
57
+ class: "tb-btn tb-btn-equal",
58
+ onClick: () => props.api.denyAll()
59
+ },
60
+ copy.rejectLabel
61
+ ),
62
+ h(
63
+ "button",
64
+ {
65
+ type: "button",
66
+ class: "tb-btn tb-btn-ghost",
67
+ onClick: () => {
68
+ showModal.value = true;
69
+ }
70
+ },
71
+ copy.customiseLabel
72
+ ),
73
+ h(
74
+ "button",
75
+ {
76
+ type: "button",
77
+ class: "tb-btn tb-btn-equal",
78
+ onClick: () => props.api.grantAll()
79
+ },
80
+ copy.acceptLabel
81
+ )
82
+ ])
83
+ ]
84
+ ),
85
+ showModal.value ? renderModal(props.api, copy, props.theme, () => {
86
+ showModal.value = false;
87
+ }) : null
88
+ ]);
89
+ };
90
+ }
91
+ });
76
92
  function renderModal(api, copy, theme, onClose) {
77
93
  const themeAttrs = theme ? { "data-tb-theme": theme } : {};
78
94
  return h(
@@ -114,8 +130,8 @@ function renderModal(api, copy, theme, onClose) {
114
130
  h(
115
131
  "div",
116
132
  { class: "tb-modal-body" },
117
- api.resolved.value.map((cat) => {
118
- const checked = api.decisions.value[cat.id] === true;
133
+ api.resolved.map((cat) => {
134
+ const checked = api.decisions[cat.id] === true;
119
135
  const id = `tb-cat-${cat.id}`;
120
136
  return h("div", { key: cat.id, class: "tb-cat" }, [
121
137
  h("div", { class: "tb-cat-text" }, [
@@ -149,7 +165,7 @@ function renderModal(api, copy, theme, onClose) {
149
165
  "button",
150
166
  {
151
167
  type: "button",
152
- class: "tb-btn tb-btn-secondary",
168
+ class: "tb-btn tb-btn-equal",
153
169
  onClick: () => api.denyAll()
154
170
  },
155
171
  copy.rejectLabel
@@ -208,7 +224,7 @@ function renderNotice(api, props) {
208
224
  type: "button",
209
225
  class: "tb-btn tb-btn-secondary",
210
226
  onClick: () => {
211
- if (api.resolved.value.some((r) => r.id === optOutId)) {
227
+ if (api.resolved.some((r) => r.id === optOutId)) {
212
228
  api.deny(optOutId);
213
229
  }
214
230
  api.save();
@@ -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 { ConsentApi } from '@tickboxhq/vue'\nimport { ConsentBanner } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted, ref } from 'vue'\nimport { type BannerCopy, DEFAULT_BANNER_COPY } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentBannerDefaultProps = {\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 * @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 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 const showModal = ref(false)\n return () =>\n h(ConsentBanner, null, {\n default: (api: unknown) => renderBanner(api as ConsentApi, props, showModal),\n })\n },\n})\n\nfunction renderBanner(\n api: ConsentApi,\n props: ConsentBannerDefaultProps,\n showModal: ReturnType<typeof ref<boolean>>,\n) {\n const copy: BannerCopy = { ...DEFAULT_BANNER_COPY, ...(props.copy ?? {}) }\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-secondary',\n onClick: () => api.denyAll(),\n },\n copy.rejectLabel,\n ),\n h(\n 'button',\n {\n type: 'button',\n class: 'tb-btn tb-btn-secondary',\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-primary',\n onClick: () => api.grantAll(),\n },\n copy.acceptLabel,\n ),\n ]),\n ],\n ),\n showModal.value\n ? renderModal(api, copy, props.theme, () => {\n showModal.value = false\n })\n : null,\n ])\n}\n\nfunction renderModal(\n api: ConsentApi,\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.value.map((cat) => {\n const checked = api.decisions.value[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-secondary',\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 { ConsentApi } from '@tickboxhq/vue'\nimport { ConsentNotice } from '@tickboxhq/vue'\nimport { type PropType, defineComponent, h, onMounted } from 'vue'\nimport { DEFAULT_NOTICE_COPY, type NoticeCopy } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\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 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 ConsentApi, props),\n })\n },\n})\n\nfunction renderNotice(api: ConsentApi, props: ConsentNoticeDefaultProps) {\n const copy: NoticeCopy = { ...DEFAULT_NOTICE_COPY, ...(props.copy ?? {}) }\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.value.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 } from 'vue'\nimport { type BannerCopy, DEFAULT_BANNER_COPY } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentBannerDefaultProps = {\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 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 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 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 = { ...DEFAULT_BANNER_COPY, ...props.userCopy }\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 { DEFAULT_NOTICE_COPY, type NoticeCopy } from '../shared/copy.js'\nimport { injectStyles } from '../shared/styles.js'\n\nexport type ConsentNoticeDefaultProps = {\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 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 = { ...DEFAULT_NOTICE_COPY, ...(props.copy ?? {}) }\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.12",
3
+ "version": "0.0.13",
4
4
  "description": "Drop-in styled consent banner and notice components for Tickbox",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tickbox.dev",
@@ -29,13 +29,13 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@tickboxhq/core": "0.0.12"
32
+ "@tickboxhq/core": "0.0.13"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": "^18.0.0 || ^19.0.0",
36
36
  "vue": "^3.4.0",
37
- "@tickboxhq/react": "0.0.12",
38
- "@tickboxhq/vue": "0.0.12"
37
+ "@tickboxhq/vue": "0.0.13",
38
+ "@tickboxhq/react": "0.0.13"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@tickboxhq/react": {
@@ -61,8 +61,8 @@
61
61
  "typescript": "^5.7.2",
62
62
  "vitest": "^2.1.8",
63
63
  "vue": "^3.5.13",
64
- "@tickboxhq/react": "0.0.12",
65
- "@tickboxhq/vue": "0.0.12"
64
+ "@tickboxhq/react": "0.0.13",
65
+ "@tickboxhq/vue": "0.0.13"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsup",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/shared/copy.ts","../src/shared/styles.ts"],"names":[],"mappings":"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","file":"chunk-WJSFVAK4.js","sourcesContent":["export type BannerCopy = {\n title: string\n description: string\n acceptLabel: string\n rejectLabel: string\n customiseLabel: string\n saveLabel: string\n closeLabel: string\n policyLinkLabel: string\n requiredBadge: string\n}\n\nexport type NoticeCopy = {\n title: string\n description: string\n acknowledgeLabel: string\n optOutLabel: string\n policyLinkLabel: string\n}\n\nexport const DEFAULT_BANNER_COPY: 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 DEFAULT_NOTICE_COPY: 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","/**\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 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 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.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"]}