From 1e306e5076e0881ff4ab82c5c9045e67bf2153a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Thu, 16 Apr 2026 17:31:14 +0400 Subject: [PATCH] Rename default banner to themed banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename src/default/ to src/themed-banner/, update class names (ProboDefaultBanner → ProboThemedBanner), style constant (DEFAULT_STYLES → THEMED_STYLES), registration function (registerDefault → registerThemedBanner), and adjust build entrypoints and package.json exports accordingly. Signed-off-by: Émile Ré --- packages/cookie-banner/build.mjs | 19 +- packages/cookie-banner/package.json | 5 + .../cookie-banner/src/themed-banner/iife.ts | 45 +++ .../cookie-banner/src/themed-banner/index.ts | 23 ++ .../cookie-banner/src/themed-banner/styles.ts | 269 ++++++++++++++++++ .../src/themed-banner/themed-banner.ts | 139 +++++++++ 6 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 packages/cookie-banner/src/themed-banner/iife.ts create mode 100644 packages/cookie-banner/src/themed-banner/index.ts create mode 100644 packages/cookie-banner/src/themed-banner/styles.ts create mode 100644 packages/cookie-banner/src/themed-banner/themed-banner.ts diff --git a/packages/cookie-banner/build.mjs b/packages/cookie-banner/build.mjs index d791eba8b..f6e8c0bf4 100644 --- a/packages/cookie-banner/build.mjs +++ b/packages/cookie-banner/build.mjs @@ -15,7 +15,6 @@ import * as esbuild from "esbuild"; const shared = { - entryPoints: ["src/index.ts"], bundle: true, target: "es2020", }; @@ -23,14 +22,22 @@ const shared = { await Promise.all([ esbuild.build({ ...shared, + entryPoints: ["src/index.ts"], + outfile: "dist/cookie-banner.mjs", + format: "esm", + }), + esbuild.build({ + ...shared, + entryPoints: ["src/themed-banner/index.ts"], + outfile: "dist/cookie-banner-themed.mjs", + format: "esm", + }), + esbuild.build({ + ...shared, + entryPoints: ["src/themed-banner/iife.ts"], outfile: "dist/cookie-banner.iife.js", format: "iife", globalName: "ProboCookieBanner", minify: true, }), - esbuild.build({ - ...shared, - outfile: "dist/cookie-banner.mjs", - format: "esm", - }), ]); diff --git a/packages/cookie-banner/package.json b/packages/cookie-banner/package.json index 216b2a3b3..da47ff1ce 100644 --- a/packages/cookie-banner/package.json +++ b/packages/cookie-banner/package.json @@ -12,6 +12,11 @@ "import": "./dist/cookie-banner.mjs", "default": "./dist/cookie-banner.mjs" }, + "./themed-banner": { + "types": "./dist/themed-banner/index.d.ts", + "import": "./dist/cookie-banner-themed.mjs", + "default": "./dist/cookie-banner-themed.mjs" + }, "./iife": "./dist/cookie-banner.iife.js" }, "scripts": { diff --git a/packages/cookie-banner/src/themed-banner/iife.ts b/packages/cookie-banner/src/themed-banner/iife.ts new file mode 100644 index 000000000..209ad3f2c --- /dev/null +++ b/packages/cookie-banner/src/themed-banner/iife.ts @@ -0,0 +1,45 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { registerThemedBanner } from "./index"; + +registerThemedBanner(); + +const script = document.currentScript as HTMLScriptElement | null; + +if (script) { + const bannerId = script.getAttribute("data-banner-id"); + const baseUrl = script.getAttribute("data-base-url"); + + if (bannerId && baseUrl) { + const mount = (): void => { + const el = document.createElement("probo-cookie-banner"); + el.setAttribute("banner-id", bannerId); + el.setAttribute("base-url", baseUrl); + + const position = script.getAttribute("data-position"); + if (position) { + el.setAttribute("position", position); + } + + document.body.appendChild(el); + }; + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", mount); + } else { + mount(); + } + } +} diff --git a/packages/cookie-banner/src/themed-banner/index.ts b/packages/cookie-banner/src/themed-banner/index.ts new file mode 100644 index 000000000..7c1397532 --- /dev/null +++ b/packages/cookie-banner/src/themed-banner/index.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { ProboThemedBanner } from "./themed-banner"; + +export { ProboThemedBanner }; + +export function registerThemedBanner(): void { + if (!customElements.get("probo-cookie-banner")) { + customElements.define("probo-cookie-banner", ProboThemedBanner); + } +} diff --git a/packages/cookie-banner/src/themed-banner/styles.ts b/packages/cookie-banner/src/themed-banner/styles.ts new file mode 100644 index 000000000..d236b9b24 --- /dev/null +++ b/packages/cookie-banner/src/themed-banner/styles.ts @@ -0,0 +1,269 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +export const THEMED_STYLES = ` + :host { + --_font: var(--probo-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif); + --_bg: var(--probo-bg, #ffffff); + --_text: var(--probo-text, #1a1a1a); + --_text-secondary: var(--probo-text-secondary, #555555); + --_border: var(--probo-border, #e0e0e0); + --_radius: var(--probo-radius, 12px); + --_shadow: var(--probo-shadow, 0 4px 24px rgba(0, 0, 0, 0.12)); + --_accent: var(--probo-accent, #1a1a1a); + --_accent-text: var(--probo-accent-text, #ffffff); + --_overlay: var(--probo-overlay, rgba(0, 0, 0, 0.4)); + --_z-index: var(--probo-z-index, 2147483646); + + all: initial; + font-family: var(--_font); + color: var(--_text); + font-size: 14px; + line-height: 1.5; + box-sizing: border-box; + } + + *, *::before, *::after { + box-sizing: border-box; + } + + .overlay { + position: fixed; + inset: 0; + background: var(--_overlay); + z-index: var(--_z-index); + display: flex; + align-items: flex-end; + justify-content: center; + padding: 16px; + } + + .card { + background: var(--_bg); + border-radius: var(--_radius); + box-shadow: var(--_shadow); + max-width: 520px; + width: 100%; + padding: 24px; + max-height: 85vh; + overflow-y: auto; + } + + .title { + font-size: 16px; + font-weight: 600; + margin: 0 0 8px; + } + + .description { + color: var(--_text-secondary); + margin: 0 0 20px; + font-size: 14px; + } + + .description a { + color: var(--_accent); + text-decoration: underline; + } + + .buttons { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + .btn { + flex: 1; + min-width: 0; + padding: 10px 16px; + border-radius: 8px; + border: 1px solid var(--_border); + background: var(--_bg); + color: var(--_text); + font-family: var(--_font); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, border-color 0.15s; + white-space: nowrap; + } + + .btn:hover { + border-color: var(--_accent); + } + + .btn-primary { + background: var(--_accent); + color: var(--_accent-text); + border-color: var(--_accent); + } + + .btn-primary:hover { + opacity: 0.9; + } + + .panel-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + } + + .panel-back { + background: none; + border: none; + cursor: pointer; + padding: 4px; + color: var(--_text); + font-family: var(--_font); + font-size: 14px; + display: flex; + align-items: center; + gap: 4px; + } + + .panel-back:hover { + color: var(--_accent); + } + + probo-category-list { + display: flex; + flex-direction: column; + gap: 12px; + margin-bottom: 20px; + } + + probo-category { + display: block; + border: 1px solid var(--_border); + border-radius: 8px; + padding: 12px 16px; + } + + .category-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + } + + .category-info { + flex: 1; + min-width: 0; + } + + .category-name { + font-weight: 500; + font-size: 14px; + } + + .category-description { + color: var(--_text-secondary); + font-size: 13px; + margin-top: 2px; + } + + .category-required { + font-size: 12px; + color: var(--_text-secondary); + font-style: italic; + } + + .toggle { + position: relative; + width: 44px; + height: 24px; + flex-shrink: 0; + } + + .toggle input { + opacity: 0; + width: 0; + height: 0; + position: absolute; + } + + .toggle-track { + position: absolute; + inset: 0; + background: var(--_border); + border-radius: 12px; + cursor: pointer; + transition: background 0.2s; + } + + .toggle-track::after { + content: ""; + position: absolute; + top: 2px; + left: 2px; + width: 20px; + height: 20px; + background: white; + border-radius: 50%; + transition: transform 0.2s; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + } + + .toggle input:checked + .toggle-track { + background: var(--_accent); + } + + .toggle input:checked + .toggle-track::after { + transform: translateX(20px); + } + + .toggle input:disabled + .toggle-track { + opacity: 0.5; + cursor: not-allowed; + } + + probo-cookie-list { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--_border); + } + + .cookie-item { + display: flex; + justify-content: space-between; + align-items: baseline; + font-size: 12px; + gap: 8px; + } + + .cookie-name { + font-weight: 500; + font-family: monospace; + flex-shrink: 0; + } + + .cookie-duration { + color: var(--_text-secondary); + flex-shrink: 0; + } + + [hidden] { + display: none !important; + } + + @media (min-width: 640px) { + .overlay { + align-items: center; + } + } +`; diff --git a/packages/cookie-banner/src/themed-banner/themed-banner.ts b/packages/cookie-banner/src/themed-banner/themed-banner.ts new file mode 100644 index 000000000..a98f798ce --- /dev/null +++ b/packages/cookie-banner/src/themed-banner/themed-banner.ts @@ -0,0 +1,139 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { registerComponents } from "../components"; +import type { ProboCookieBannerRoot } from "../components/cookie-banner-root"; +import type { BannerConfig } from "../client"; +import { THEMED_STYLES } from "./styles"; + +const BACK_ARROW = ``; + +export class ProboThemedBanner extends HTMLElement { + private shadow: ShadowRoot; + + constructor() { + super(); + this.shadow = this.attachShadow({ mode: "open" }); + } + + static get observedAttributes(): string[] { + return ["banner-id", "base-url"]; + } + + connectedCallback(): void { + registerComponents(); + + const bannerId = this.getAttribute("banner-id"); + const baseUrl = this.getAttribute("base-url"); + + if (!bannerId || !baseUrl) { + console.warn("[probo] requires banner-id and base-url attributes"); + return; + } + + const position = this.getAttribute("position") ?? "bottom-left"; + + this.shadow.innerHTML = ` + + + +
+ +
+
+ + +
+ +
+
+ + +
+ `; + + const root = this.shadow.querySelector("probo-cookie-banner-root") as ProboCookieBannerRoot; + + root.addEventListener("probo-ready", (e: Event) => { + const config = (e as CustomEvent).detail.config as BannerConfig; + this.updateDescription(config); + }); + + this.shadow.querySelector("[data-action=back]")?.addEventListener("click", () => { + root.setState("banner"); + }); + } + + private updateDescription(config: BannerConfig): void { + const el = this.shadow.querySelector("[data-description]"); + if (!el) return; + + let html = "We use cookies to improve your experience and analyze site traffic."; + if (config.privacy_policy_url) { + html += ` Privacy Policy`; + } + el.innerHTML = html; + } + + private esc(str: string): string { + return str.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">"); + } +}