Improve UI of themed banner

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-21 17:09:30 +04:00
parent b603d04d8d
commit fab95413df
2 changed files with 168 additions and 62 deletions

View File

@@ -23,7 +23,6 @@ export const THEMED_STYLES = `
--_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);
--_btn-radius: var(--probo-btn-radius, 8px);
--_font-size: var(--probo-font-size, 14px);
@@ -40,26 +39,71 @@ export const THEMED_STYLES = `
box-sizing: border-box;
}
.overlay {
.floating {
position: fixed;
inset: 0;
background: var(--_overlay);
z-index: var(--_z-index);
padding: 24px;
display: flex;
align-items: flex-end;
justify-content: center;
padding: 16px;
pointer-events: none;
}
.floating[data-position="bottom-left"] {
bottom: 0;
left: 0;
}
.floating[data-position="bottom-right"] {
bottom: 0;
right: 0;
}
.floating[data-position="bottom-center"] {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.floating[data-position="top-left"] {
top: 0;
left: 0;
}
.floating[data-position="top-right"] {
top: 0;
right: 0;
}
.floating[data-position="top-center"] {
top: 0;
left: 50%;
transform: translateX(-50%);
}
.card {
background: var(--_bg);
border-radius: var(--_radius);
box-shadow: var(--_shadow);
max-width: 520px;
width: 100%;
padding: 24px;
max-height: 85vh;
pointer-events: auto;
}
probo-banner .card {
max-width: 450px;
}
probo-preference-panel .card {
max-width: 520px;
max-height: 75vh;
display: flex;
flex-direction: column;
padding: 0;
}
probo-preference-panel probo-category-list {
overflow-y: auto;
flex: 1;
min-height: 0;
}
.title {
@@ -87,10 +131,10 @@ export const THEMED_STYLES = `
.btn {
flex: 1;
min-width: 0;
padding: 10px 16px;
padding: 8px 10px;
border-radius: var(--_btn-radius);
border: 1px solid var(--_border);
background: var(--_bg);
background: color-mix(in srgb, var(--_text) 8%, var(--_bg));
color: var(--_text);
font-family: var(--_font);
font-size: var(--_font-size);
@@ -101,7 +145,14 @@ export const THEMED_STYLES = `
}
.btn:hover {
border-color: var(--_accent);
opacity: 0.8;
}
.btn-link {
background: transparent;
border: none;
color: var(--_accent);
text-decoration: underline;
}
.btn-primary {
@@ -110,50 +161,53 @@ export const THEMED_STYLES = `
border-color: var(--_accent);
}
.btn-primary:hover {
opacity: 0.9;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
padding: 24px;
border-bottom: 1px solid var(--_border);
}
.panel-back {
.panel-close {
background: none;
border: none;
cursor: pointer;
padding: 4px;
color: var(--_text);
font-family: var(--_font);
color: var(--_text-secondary);
display: flex;
align-items: center;
gap: 4px;
justify-content: center;
}
.panel-back:hover {
color: var(--_accent);
.panel-close:hover {
color: var(--_text);
}
probo-category-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
probo-preference-panel .buttons {
border-top: 1px solid var(--_border);
padding: 20px 24px 24px;
}
probo-category {
display: block;
border: 1px solid var(--_border);
border-radius: 8px;
padding: 12px 16px;
border-bottom: 1px solid var(--_border);
padding: 12px 40px 12px 60px;
position: relative;
}
probo-category:last-child {
border-bottom: none;
}
.category-header {
display: flex;
align-items: center;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}
@@ -181,9 +235,11 @@ export const THEMED_STYLES = `
.toggle {
position: relative;
width: 44px;
height: 24px;
display: inline-block;
width: 34px;
height: 18px;
flex-shrink: 0;
margin-top: 2px;
}
.toggle input {
@@ -197,7 +253,7 @@ export const THEMED_STYLES = `
position: absolute;
inset: 0;
background: var(--_border);
border-radius: 12px;
border-radius: 9px;
cursor: pointer;
transition: background 0.2s;
}
@@ -207,8 +263,8 @@ export const THEMED_STYLES = `
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
transition: transform 0.2s;
@@ -220,7 +276,7 @@ export const THEMED_STYLES = `
}
.toggle input:checked + .toggle-track::after {
transform: translateX(20px);
transform: translateX(16px);
}
.toggle input:disabled + .toggle-track {
@@ -228,41 +284,71 @@ export const THEMED_STYLES = `
cursor: not-allowed;
}
.cookie-toggle {
position: absolute;
left: 36px;
top: 14px;
background: none;
border: none;
cursor: pointer;
padding: 2px;
margin: 0;
color: var(--_text-secondary);
display: flex;
align-items: center;
justify-content: center;
}
.cookie-toggle:hover {
color: var(--_accent);
}
.cookie-toggle svg {
transition: transform 0.2s;
transform: rotate(-90deg);
}
.cookie-toggle.open svg {
transform: rotate(0deg);
}
probo-cookie-list {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--_border);
margin-top: 10px;
background: color-mix(in srgb, var(--_text) 4%, var(--_bg));
border-radius: 8px;
overflow: hidden;
}
.cookie-item {
display: flex;
justify-content: space-between;
align-items: baseline;
flex-direction: column;
gap: 2px;
padding: 10px 12px;
font-size: calc(var(--_font-size) - 2px);
gap: 8px;
border-bottom: 1px solid var(--_border);
}
.cookie-item:last-child {
border-bottom: none;
}
.cookie-name {
font-weight: 500;
font-family: monospace;
flex-shrink: 0;
}
.cookie-duration {
.cookie-detail {
color: var(--_text-secondary);
flex-shrink: 0;
}
.cookie-label {
font-weight: 500;
color: var(--_text);
}
[hidden] {
display: none !important;
}
@media (min-width: 640px) {
.overlay {
align-items: center;
}
}
`;

View File

@@ -17,7 +17,8 @@ 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>`;
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"><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"><polyline points="6 9 12 15 18 9"/></svg>`;
export class ProboThemedBanner extends HTMLElement {
private shadow: ShadowRoot;
@@ -48,33 +49,35 @@ export class ProboThemedBanner extends HTMLElement {
<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="floating" data-position="${this.esc(position)}">
<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>
<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>
</div>
</div>
</probo-banner>
<probo-preference-panel>
<div class="overlay">
<div class="floating" data-position="${this.esc(position)}">
<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>
<button class="panel-close" data-action="back" aria-label="Close">
${CLOSE_ICON}
</button>
</div>
<probo-category-list>
<template>
<button class="cookie-toggle" data-action="toggle-cookies">
${CHEVRON_DOWN}
</button>
<div class="category-header">
<div class="category-info">
<div class="category-name" data-slot="name"></div>
@@ -87,11 +90,12 @@ export class ProboThemedBanner extends HTMLElement {
</label>
</probo-category-toggle>
</div>
<probo-cookie-list>
<probo-cookie-list hidden>
<template>
<div class="cookie-item">
<span class="cookie-name" data-slot="name"></span>
<span class="cookie-duration" data-slot="duration"></span>
<span class="cookie-detail"><span class="cookie-label">Description:</span> <span data-slot="description"></span></span>
<span class="cookie-detail"><span class="cookie-label">Duration:</span> <span data-slot="duration"></span></span>
</div>
</template>
</probo-cookie-list>
@@ -120,6 +124,22 @@ export class ProboThemedBanner extends HTMLElement {
this.shadow.querySelector("[data-action=back]")?.addEventListener("click", () => {
root.setState("banner");
});
this.shadow.addEventListener("click", (e: Event) => {
const btn = (e.target as Element).closest?.("[data-action=toggle-cookies]") as HTMLElement | null;
if (!btn) return;
const category = btn.closest("probo-category");
const cookieList = category?.querySelector("probo-cookie-list") as HTMLElement | null;
if (!cookieList) return;
const open = cookieList.hasAttribute("hidden");
if (open) {
cookieList.removeAttribute("hidden");
btn.classList.add("open");
} else {
cookieList.setAttribute("hidden", "");
btn.classList.remove("open");
}
});
}
private updateDescription(config: BannerConfig): void {