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:
@@ -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",
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -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": {
|
||||
|
||||
45
packages/cookie-banner/src/themed-banner/iife.ts
Normal file
45
packages/cookie-banner/src/themed-banner/iife.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/cookie-banner/src/themed-banner/index.ts
Normal file
23
packages/cookie-banner/src/themed-banner/index.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
269
packages/cookie-banner/src/themed-banner/styles.ts
Normal file
269
packages/cookie-banner/src/themed-banner/styles.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
139
packages/cookie-banner/src/themed-banner/themed-banner.ts
Normal file
139
packages/cookie-banner/src/themed-banner/themed-banner.ts
Normal 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, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user