Add "Powered by Probo" branding to cookie banner SDK

Renders a subtle branded link at the bottom of both the banner
and preference panel, conditionally hidden via show_branding
config flag. Extracts all icon and HTML template constants into
a shared html.ts module.

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-22 13:02:07 +04:00
parent 3994ab1d8b
commit 7102c17b19
6 changed files with 78 additions and 16 deletions

View File

@@ -12,6 +12,8 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { LOCK_ICON } from "./html";
const ATTR_CATEGORY = "data-cookie-consent";
const ATTR_SRC = "data-src";
const ATTR_HREF = "data-href";
@@ -39,7 +41,6 @@ const VISUAL_TAGS = new Set([
"OBJECT",
]);
const LOCK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`;
const PLACEHOLDER_STYLES = `
[${ATTR_HIDDEN}] {

View File

@@ -43,6 +43,7 @@ export interface BannerConfig {
privacy_policy_url: string;
consent_expiry_days: number;
consent_mode: "OPT_IN" | "OPT_OUT";
show_branding: boolean;
categories: Category[];
}

View File

@@ -12,11 +12,10 @@
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
import { COOKIE_ICON } from "../html";
import type { ProboRootElement } from "./base";
import type { ProboCookieBannerRoot } from "./cookie-banner-root";
const COOKIE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="8" cy="9" r="1" fill="currentColor"/><circle cx="15" cy="11" r="1" fill="currentColor"/><circle cx="10" cy="15" r="1" fill="currentColor"/><circle cx="13" cy="7" r="1" fill="currentColor"/></svg>`;
export class ProboSettingsButton extends HTMLElement {
private shadow: ShadowRoot;
private root: ProboRootElement | null = null;

View File

@@ -0,0 +1,25 @@
// 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 CLOSE_ICON = `<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" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
export const CHEVRON_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>`;
export const COOKIE_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><circle cx="8" cy="9" r="1" fill="currentColor"/><circle cx="15" cy="11" r="1" fill="currentColor"/><circle cx="10" cy="15" r="1" fill="currentColor"/><circle cx="13" cy="7" r="1" fill="currentColor"/></svg>`;
export const LOCK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`;
const PROBO_LOGO = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 25 24" fill="none" aria-hidden="true"><g clip-path="url(#pc)"><rect width="24" height="24" x=".85" fill="currentColor" rx="5.14"/><path stroke="#fff" stroke-width="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"/><path stroke="url(#pg)" stroke-width="3.53" d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"/><path fill="#fff" d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z"/></g><defs><linearGradient id="pg" x1="18.77" x2="1.2" y1="13.56" y2="13.56" gradientUnits="userSpaceOnUse"><stop stop-color="#101E1C" stop-opacity="0"/><stop offset="1" stop-color="#fff"/></linearGradient><clipPath id="pc"><rect width="24" height="24" x=".85" fill="#fff" rx="5.14"/></clipPath></defs></svg>`;
export const BRANDING = `<div class="branding" data-branding><a href="https://www.getprobo.com/cookie-policy" target="_blank" rel="noopener noreferrer">Powered by ${PROBO_LOGO} Probo</a></div>`;

View File

@@ -84,12 +84,16 @@ export const THEMED_STYLES = `
border-radius: var(--_radius);
box-shadow: var(--_shadow);
width: 100%;
padding: 24px;
pointer-events: auto;
}
probo-banner .card {
max-width: 450px;
padding: 24px 24px 12px 24px;
}
probo-banner .buttons {
padding-bottom: 12px;
}
probo-preference-panel .card {
@@ -97,7 +101,6 @@ export const THEMED_STYLES = `
max-height: 75vh;
display: flex;
flex-direction: column;
padding: 0;
}
probo-preference-panel probo-category-list {
@@ -188,9 +191,13 @@ export const THEMED_STYLES = `
flex-direction: column;
}
probo-preference-panel .buttons {
probo-preference-panel .footer {
border-top: 1px solid var(--_border);
padding: 20px 24px 24px;
padding: 10px 24px;
}
probo-preference-panel .buttons {
padding: 10px 0;
}
probo-category {
@@ -347,6 +354,28 @@ export const THEMED_STYLES = `
color: var(--_text);
}
.branding {
text-align: center;
}
.branding a {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: calc(var(--_font-size) - 2px);
font-weight: 400;
color: var(--_text-secondary);
text-decoration: none;
}
.branding a:hover {
color: var(--_text);
}
.branding svg {
flex-shrink: 0;
}
[hidden] {
display: none !important;
}

View File

@@ -15,11 +15,9 @@
import { registerComponents } from "../components";
import type { ProboCookieBannerRoot } from "../components/cookie-banner-root";
import type { BannerConfig } from "../client";
import { BRANDING, CHEVRON_DOWN, CLOSE_ICON } from "../html";
import { THEMED_STYLES } from "./styles";
const CLOSE_ICON = `<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" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
const CHEVRON_DOWN = `<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg>`;
export class ProboThemedBanner extends HTMLElement {
private shadow: ShadowRoot;
@@ -62,6 +60,7 @@ export class ProboThemedBanner extends HTMLElement {
<probo-reject-button><button class="btn">Reject all</button></probo-reject-button>
<probo-customize-button><button class="btn btn-link">Customize</button></probo-customize-button>
</div>
${BRANDING}
</div>
</div>
</probo-banner>
@@ -103,12 +102,15 @@ export class ProboThemedBanner extends HTMLElement {
</probo-cookie-list>
</template>
</probo-category-list>
<div class="buttons">
<probo-accept-button><button class="btn btn-primary">Accept all</button></probo-accept-button>
<probo-reject-button><button class="btn">Reject all</button></probo-reject-button>
<probo-save-button>
<button class="btn btn-link" style="flex:1">Save preferences</button>
</probo-save-button>
<div class="footer">
<div class="buttons">
<probo-accept-button><button class="btn btn-primary">Accept all</button></probo-accept-button>
<probo-reject-button><button class="btn">Reject all</button></probo-reject-button>
<probo-save-button>
<button class="btn btn-link" style="flex:1">Save preferences</button>
</probo-save-button>
</div>
${BRANDING}
</div>
</div>
</div>
@@ -123,6 +125,11 @@ export class ProboThemedBanner extends HTMLElement {
root.addEventListener("probo-ready", (e: Event) => {
const config = (e as CustomEvent).detail.config as BannerConfig;
this.updateDescription(config);
if (!config.show_branding) {
this.shadow.querySelectorAll("[data-branding]").forEach(el => {
(el as HTMLElement).setAttribute("hidden", "");
});
}
});
this.shadow.querySelector("[data-action=back]")?.addEventListener("click", () => {