feat(ui): expose app theme contract and public components
This commit is contained in:
@@ -0,0 +1,372 @@
|
||||
component CookieConsent {
|
||||
props {
|
||||
class: string = ""
|
||||
}
|
||||
state bannerOpen = false
|
||||
state preferencesOpen = false
|
||||
|
||||
state analyticsCookies = false
|
||||
state personalizationCookies = false
|
||||
state marketingCookies = false
|
||||
|
||||
view {
|
||||
<div class="{class}" class:hidden="!bannerOpen">
|
||||
<!-- Cookie consent banner -->
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="false"
|
||||
aria-labelledby="cookie-consent-title"
|
||||
aria-describedby="cookie-consent-description"
|
||||
class="fixed inset-x-0 bottom-0 z-[80] p-4 sm:p-5 lg:p-6 {class}"
|
||||
class:hidden="!bannerOpen || preferencesOpen"
|
||||
class:block="bannerOpen && !preferencesOpen"
|
||||
>
|
||||
<div
|
||||
class="mx-auto w-full max-w-7xl overflow-hidden rounded-3xl border border-slate-200 bg-white/95 shadow-2xl shadow-slate-950/20 backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/95 dark:shadow-black/40"
|
||||
>
|
||||
<div class="grid items-center gap-6 p-5 sm:p-6 lg:grid-cols-[minmax(0,1fr)_auto] lg:p-7">
|
||||
|
||||
<!-- Message -->
|
||||
<div class="flex items-start gap-4">
|
||||
<span
|
||||
class="grid h-11 w-11 shrink-0 place-items-center rounded-2xl bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300"
|
||||
>
|
||||
<span class="icon-[lucide--cookie] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h2
|
||||
id="cookie-consent-title"
|
||||
class="text-base font-bold text-slate-950 dark:text-white"
|
||||
>
|
||||
Your privacy matters
|
||||
</h2>
|
||||
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-emerald-100 px-2.5 py-1 text-[10px] font-bold uppercase tracking-wide text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300"
|
||||
>
|
||||
Privacy first
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p
|
||||
id="cookie-consent-description"
|
||||
class="mt-2 max-w-3xl text-sm leading-6 text-slate-600 dark:text-slate-400"
|
||||
>
|
||||
We use essential cookies to operate WrNexus. With your
|
||||
permission, we also use analytics and personalization cookies
|
||||
to improve your experience.
|
||||
</p>
|
||||
|
||||
<div class="mt-3 flex flex-wrap items-center gap-x-4 gap-y-2">
|
||||
<a
|
||||
href="/cookies"
|
||||
class="inline-flex items-center gap-1.5 text-xs font-semibold text-indigo-600 transition hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
Cookie policy
|
||||
|
||||
<span class="icon-[lucide--arrow-up-right] h-3.5 w-3.5" aria-hidden="true" ></span>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="/privacy"
|
||||
class="inline-flex items-center gap-1.5 text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
|
||||
>
|
||||
Privacy policy
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="grid gap-2 sm:grid-cols-3 lg:flex lg:items-center">
|
||||
<button
|
||||
type="button"
|
||||
@click=" analyticsCookies = false; personalizationCookies = false; marketingCookies = false; bannerOpen = false "
|
||||
data-cookie-action="essential"
|
||||
class="inline-flex h-11 items-center justify-center rounded-xl border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
Essential only
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="preferencesOpen = true"
|
||||
class="inline-flex h-11 items-center justify-center gap-2 rounded-xl border border-indigo-200 bg-indigo-50 px-4 text-sm font-semibold text-indigo-700 transition hover:border-indigo-300 hover:bg-indigo-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-indigo-300 dark:hover:border-indigo-500/30 dark:hover:bg-indigo-500/15"
|
||||
>
|
||||
<span class="icon-[lucide--settings-2] h-4 w-4" aria-hidden="true" ></span>
|
||||
|
||||
Preferences
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click=" analyticsCookies = true; personalizationCookies = true; marketingCookies = true; bannerOpen = false "
|
||||
data-cookie-action="accept-all"
|
||||
class="inline-flex h-11 items-center justify-center gap-2 rounded-xl bg-indigo-600 px-5 text-sm font-bold text-white shadow-lg shadow-indigo-600/20 transition hover:-translate-y-0.5 hover:bg-indigo-500 hover:shadow-xl focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-900"
|
||||
>
|
||||
Accept all
|
||||
|
||||
<span class="icon-[lucide--check] h-4 w-4" aria-hidden="true" ></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Preferences backdrop -->
|
||||
<button type="button" @click="preferencesOpen = false" aria-label="Close cookie preferences" class="fixed inset-0 z-[90] bg-slate-950/50 backdrop-blur-sm" class:hidden="!preferencesOpen" class:block="preferencesOpen" ></button>
|
||||
|
||||
<!-- Preferences modal -->
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="cookie-preferences-title"
|
||||
aria-describedby="cookie-preferences-description"
|
||||
class="fixed inset-0 z-[100] overflow-y-auto p-4 sm:p-6"
|
||||
class:hidden="!preferencesOpen"
|
||||
class:block="preferencesOpen"
|
||||
>
|
||||
<div class="flex min-h-full items-end justify-center sm:items-center">
|
||||
<div
|
||||
class="relative w-full max-w-2xl overflow-hidden rounded-t-3xl border border-slate-200 bg-white shadow-2xl shadow-slate-950/30 dark:border-white/10 dark:bg-slate-900 dark:shadow-black/50 sm:rounded-3xl"
|
||||
>
|
||||
<!-- Modal header -->
|
||||
<header class="border-b border-slate-200 px-5 py-5 dark:border-white/10 sm:px-6">
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex items-start gap-4">
|
||||
<span
|
||||
class="grid h-11 w-11 shrink-0 place-items-center rounded-2xl bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300"
|
||||
>
|
||||
<span class="icon-[lucide--sliders-horizontal] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<h2
|
||||
id="cookie-preferences-title"
|
||||
class="text-lg font-bold tracking-tight text-slate-950 dark:text-white"
|
||||
>
|
||||
Cookie preferences
|
||||
</h2>
|
||||
|
||||
<p
|
||||
id="cookie-preferences-description"
|
||||
class="mt-1 text-sm leading-6 text-slate-500 dark:text-slate-400"
|
||||
>
|
||||
Choose which optional cookies WrNexus may use.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click="preferencesOpen = false"
|
||||
aria-label="Close cookie preferences"
|
||||
class="grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-slate-200 bg-white text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:border-white/10 dark:bg-white/5 dark:text-slate-400 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
<span class="icon-[lucide--x] h-4.5 w-4.5" aria-hidden="true" ></span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Preference groups -->
|
||||
<div class="max-h-[60dvh] space-y-3 overflow-y-auto p-5 sm:p-6">
|
||||
|
||||
<!-- Essential -->
|
||||
<article
|
||||
class="rounded-2xl bg-emerald-500/10 p-4 ring-1 ring-inset ring-emerald-500/15 transition dark:bg-emerald-500/10 dark:ring-emerald-400/10"
|
||||
>
|
||||
<div class="flex items-start justify-between gap-4">
|
||||
<div class="flex items-start gap-3">
|
||||
<span
|
||||
class="grid h-10 w-10 shrink-0 place-items-center rounded-xl bg-emerald-500/15 text-emerald-700 dark:bg-emerald-400/10 dark:text-emerald-300"
|
||||
>
|
||||
<span class="icon-[lucide--shield-check] h-4.5 w-4.5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
|
||||
Essential cookies
|
||||
</h3>
|
||||
|
||||
<span
|
||||
class="rounded-full bg-emerald-500/15 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wide text-emerald-700 dark:bg-emerald-400/10 dark:text-emerald-300"
|
||||
>
|
||||
Always active
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p class="mt-1.5 text-xs leading-5 text-slate-600 dark:text-slate-400">
|
||||
Required for authentication, security, sessions, preferences, and core website functionality.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="relative inline-flex h-6 w-11 shrink-0 cursor-not-allowed rounded-full bg-emerald-500"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="absolute right-1 top-1 h-4 w-4 rounded-full bg-white shadow-sm" ></span>
|
||||
</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Analytics -->
|
||||
<article
|
||||
class="rounded-2xl p-4 transition-all duration-200 {analyticsCookies ? 'bg-indigo-50 ring-1 ring-inset ring-indigo-200 dark:bg-indigo-500/10 dark:ring-indigo-500/20' : 'bg-slate-50 ring-1 ring-inset ring-slate-200 dark:bg-white/5 dark:ring-white/10' }"
|
||||
>
|
||||
<label class="flex cursor-pointer items-start justify-between gap-4">
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
class="grid h-10 w-10 shrink-0 place-items-center rounded-xl transition-all duration-200 {analyticsCookies ? 'bg-indigo-100 text-indigo-700 dark:bg-indigo-500/15 dark:text-indigo-300' : 'bg-slate-200 text-slate-500 dark:bg-white/10 dark:text-slate-400' }"
|
||||
>
|
||||
<span class="icon-[lucide--chart-no-axes-combined] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-slate-950 dark:text-white">
|
||||
Analytics cookies
|
||||
</span>
|
||||
|
||||
<span class="mt-1 block text-xs leading-5 text-slate-600 dark:text-slate-400">
|
||||
Help us understand website usage, performance, and navigation patterns.
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="relative mt-0.5 shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="analytics"
|
||||
checked="{analyticsCookies}"
|
||||
@change="analyticsCookies = event.target.checked"
|
||||
class="peer sr-only"
|
||||
/>
|
||||
|
||||
<span class="block h-6 w-11 rounded-full bg-slate-300 transition-colors duration-200 peer-checked:bg-indigo-600 peer-focus-visible:ring-4 peer-focus-visible:ring-indigo-500/20 dark:bg-slate-700" ></span>
|
||||
|
||||
<span class="absolute left-1 top-1 h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200 peer-checked:translate-x-5" ></span>
|
||||
</span>
|
||||
</label>
|
||||
</article>
|
||||
|
||||
<!-- Personalization -->
|
||||
<article
|
||||
class="rounded-2xl p-4 transition-all duration-200 {personalizationCookies ? 'bg-violet-50 ring-1 ring-inset ring-violet-200 dark:bg-violet-500/10 dark:ring-violet-500/20' : 'bg-slate-50 ring-1 ring-inset ring-slate-200 dark:bg-white/5 dark:ring-white/10' }"
|
||||
>
|
||||
<label class="flex cursor-pointer items-start justify-between gap-4">
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
class="grid h-10 w-10 shrink-0 place-items-center rounded-xl transition-all duration-200 {personalizationCookies ? 'bg-violet-100 text-violet-700 dark:bg-violet-500/15 dark:text-violet-300' : 'bg-slate-200 text-slate-500 dark:bg-white/10 dark:text-slate-400' }"
|
||||
>
|
||||
<span class="icon-[lucide--sparkles] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-slate-950 dark:text-white">
|
||||
Personalization cookies
|
||||
</span>
|
||||
|
||||
<span class="mt-1 block text-xs leading-5 text-slate-600 dark:text-slate-400">
|
||||
Remember your choices and provide a more relevant website experience.
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="relative mt-0.5 shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="personalization"
|
||||
checked="{personalizationCookies}"
|
||||
@change="personalizationCookies = event.target.checked"
|
||||
class="peer sr-only"
|
||||
/>
|
||||
|
||||
<span class="block h-6 w-11 rounded-full bg-slate-300 transition-colors duration-200 peer-checked:bg-violet-600 peer-focus-visible:ring-4 peer-focus-visible:ring-violet-500/20 dark:bg-slate-700" ></span>
|
||||
|
||||
<span class="absolute left-1 top-1 h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200 peer-checked:translate-x-5" ></span>
|
||||
</span>
|
||||
</label>
|
||||
</article>
|
||||
|
||||
<!-- Marketing -->
|
||||
<article
|
||||
class="rounded-2xl p-4 transition-all duration-200 {marketingCookies ? 'bg-rose-50 ring-1 ring-inset ring-rose-200 dark:bg-rose-500/10 dark:ring-rose-500/20' : 'bg-slate-50 ring-1 ring-inset ring-slate-200 dark:bg-white/5 dark:ring-white/10' }"
|
||||
>
|
||||
<label class="flex cursor-pointer items-start justify-between gap-4">
|
||||
<span class="flex min-w-0 items-start gap-3">
|
||||
<span
|
||||
class="grid h-10 w-10 shrink-0 place-items-center rounded-xl transition-all duration-200 {marketingCookies ? 'bg-rose-100 text-rose-700 dark:bg-rose-500/15 dark:text-rose-300' : 'bg-slate-200 text-slate-500 dark:bg-white/10 dark:text-slate-400' }"
|
||||
>
|
||||
<span class="icon-[lucide--megaphone] h-5 w-5" aria-hidden="true" ></span>
|
||||
</span>
|
||||
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-slate-950 dark:text-white">
|
||||
Marketing cookies
|
||||
</span>
|
||||
|
||||
<span class="mt-1 block text-xs leading-5 text-slate-600 dark:text-slate-400">
|
||||
Measure campaigns and show relevant WrNexus content across supported services.
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span class="relative mt-0.5 shrink-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="marketing"
|
||||
checked="{marketingCookies}"
|
||||
@change="marketingCookies = event.target.checked"
|
||||
class="peer sr-only"
|
||||
/>
|
||||
|
||||
<span class="block h-6 w-11 rounded-full bg-slate-300 transition-colors duration-200 peer-checked:bg-rose-600 peer-focus-visible:ring-4 peer-focus-visible:ring-rose-500/20 dark:bg-slate-700" ></span>
|
||||
|
||||
<span class="absolute left-1 top-1 h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200 peer-checked:translate-x-5" ></span>
|
||||
</span>
|
||||
</label>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- Modal footer -->
|
||||
<footer
|
||||
class="border-t border-slate-200 bg-slate-50 px-5 py-4 dark:border-white/10 dark:bg-white/[0.02] sm:px-6"
|
||||
>
|
||||
<div class="grid gap-2 sm:grid-cols-3">
|
||||
<button
|
||||
type="button"
|
||||
@click=" analyticsCookies = false; personalizationCookies = false; marketingCookies = false "
|
||||
class="inline-flex h-11 items-center justify-center rounded-xl border border-slate-200 bg-white px-4 text-sm font-semibold text-slate-700 transition hover:border-slate-300 hover:bg-slate-100 hover:text-slate-950 dark:border-white/10 dark:bg-white/5 dark:text-slate-200 dark:hover:bg-white/10 dark:hover:text-white"
|
||||
>
|
||||
Reject optional
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click=" analyticsCookies = true; personalizationCookies = true; marketingCookies = true "
|
||||
class="inline-flex h-11 items-center justify-center rounded-xl border border-indigo-200 bg-indigo-50 px-4 text-sm font-semibold text-indigo-700 transition hover:border-indigo-300 hover:bg-indigo-100 dark:border-indigo-500/20 dark:bg-indigo-500/10 dark:text-indigo-300 dark:hover:bg-indigo-500/15"
|
||||
>
|
||||
Enable all
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
@click=" preferencesOpen = false; bannerOpen = false "
|
||||
data-cookie-action="save-preferences"
|
||||
class="inline-flex h-11 items-center justify-center gap-2 rounded-xl bg-indigo-600 px-4 text-sm font-bold text-white shadow-lg shadow-indigo-600/20 transition hover:-translate-y-0.5 hover:bg-indigo-500 hover:shadow-xl"
|
||||
>
|
||||
<span class="icon-[lucide--save] h-4 w-4" aria-hidden="true" ></span>
|
||||
|
||||
Save preferences
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user