Rename default banner to themed banner

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é <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-16 17:31:14 +04:00
parent 2e066c82ad
commit 1e306e5076
6 changed files with 494 additions and 6 deletions

View File

@@ -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",
}),
]);

View File

@@ -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": {

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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();
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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);
}
}

View File

@@ -0,0 +1,269 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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;
}
}
`;

View File

@@ -0,0 +1,139 @@
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
//
// 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 = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>`;
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] <probo-cookie-banner> requires banner-id and base-url attributes");
return;
}
const position = this.getAttribute("position") ?? "bottom-left";
this.shadow.innerHTML = `
<style>${THEMED_STYLES}</style>
<probo-cookie-banner-root banner-id="${this.esc(bannerId)}" base-url="${this.esc(baseUrl)}">
<probo-banner>
<div class="overlay">
<div class="card" role="dialog" aria-label="Cookie consent">
<p class="title">Cookie Preferences</p>
<p class="description" data-description>
We use cookies to improve your experience and analyze site traffic.
</p>
<div class="buttons">
<probo-reject-button><button class="btn">Reject all</button></probo-reject-button>
<probo-customize-button><button class="btn">Customize</button></probo-customize-button>
<probo-accept-button><button class="btn btn-primary">Accept all</button></probo-accept-button>
</div>
</div>
</div>
</probo-banner>
<probo-preference-panel>
<div class="overlay">
<div class="card" role="dialog" aria-label="Cookie preferences">
<div class="panel-header">
<button class="panel-back" data-action="back">
${BACK_ARROW} Back
</button>
<p class="title" style="margin:0">Preferences</p>
<div style="width:60px"></div>
</div>
<probo-category-list>
<template>
<div class="category-header">
<div class="category-info">
<div class="category-name" data-slot="name"></div>
<div class="category-description" data-slot="description"></div>
</div>
<probo-category-toggle>
<label class="toggle">
<input type="checkbox">
<span class="toggle-track"></span>
</label>
</probo-category-toggle>
</div>
<probo-cookie-list>
<template>
<div class="cookie-item">
<span class="cookie-name" data-slot="name"></span>
<span class="cookie-duration" data-slot="duration"></span>
</div>
</template>
</probo-cookie-list>
</template>
</probo-category-list>
<div class="buttons">
<probo-save-button>
<button class="btn btn-primary" style="flex:1">Save preferences</button>
</probo-save-button>
</div>
</div>
</div>
</probo-preference-panel>
<probo-settings-button position="${this.esc(position)}"></probo-settings-button>
</probo-cookie-banner-root>
`;
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 += ` <a href="${this.esc(config.privacy_policy_url)}" target="_blank" rel="noopener">Privacy Policy</a>`;
}
el.innerHTML = html;
}
private esc(str: string): string {
return str.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
}