feat: support WRN imports and dynamic public shell

This commit is contained in:
2026-07-21 11:15:06 +05:30
parent 0013c0771d
commit 2ca2d02b22
82 changed files with 959 additions and 174 deletions
+68 -18
View File
@@ -1,10 +1,27 @@
component PublicFooter {
types {
interface PublicFooterLink {
label: string
href: string
ariaLabel?: string
target?: string
icon?: string
}
interface PublicFooterColumn {
title: string
links: PublicFooterLink[]
}
}
props {
class: string = ""
homeHref: string = "/"
brandName: string = "WrNexus"
brandTagline: string = "Identity Cloud"
brandAriaLabel: string = "WrNexus home"
brandIcon: string = "icon-[lucide--blocks]"
brandDescription: string = "Secure authentication, user management, organizations, authorization, and enterprise identity for modern applications."
statusLabel: string = "All systems operational"
statusHref: string = "/status"
newsletterEyebrow: string = "WrNexus updates"
newsletterTitle: string = "Identity insights delivered to your inbox"
newsletterDescription: string = "Get product updates, security guidance, implementation strategies, and practical identity architecture resources."
@@ -12,11 +29,27 @@ component PublicFooter {
newsletterButtonLabel: string = "Subscribe"
newsletterPlaceholder: string = "Enter your work email"
newsletterSuccessMessage: string = "Thanks. Please check your inbox to confirm your subscription."
newsletterPrivacyLabel: string = "privacy policy"
newsletterPrivacyHref: string = "/privacy"
newsletterFinePrintPrefix: string = "No spam. Unsubscribe at any time. Read our"
newsletterFinePrintSuffix: string = "."
newsletterEmailLabel: string = "Work email address"
newsletterEmailName: string = "email"
copyrightText: string = "© 2026 WrNexus. All rights reserved."
attributionText: string = "Built by WorkRoot Workspace."
showNewsletter: boolean = true
showSocialLinks: boolean = true
showThemeToggle: boolean = true
showStatus: boolean = true
showCookiePreferences: boolean = true
cookiePreferencesLabel: string = "Cookie preferences"
themeLabel: string = "Theme"
themeToggleLabel: string = "Toggle color theme"
legalTitle: string = "Legal"
socialLinks: PublicFooterLink[] = []
navigationColumns: PublicFooterColumn[] = []
legalLinks: PublicFooterLink[] = []
navigationAriaLabel: string = "Footer navigation"
}
state email = ""
state newsletterSubmitted = false
@@ -81,7 +114,7 @@ component PublicFooter {
class="sr-only"
for="footer-newsletter-email"
>
Work email address
{newsletterEmailLabel}
</label>
<div class="relative min-w-0 flex-1">
@@ -89,7 +122,7 @@ component PublicFooter {
<input
id="footer-newsletter-email"
name="email"
name="{newsletterEmailName}"
type="email"
autocomplete="email"
required
@@ -113,13 +146,13 @@ component PublicFooter {
<p class="mt-3 flex items-start gap-2 text-xs leading-5 text-slate-500 dark:text-slate-400">
<span class="icon-[lucide--shield-check] mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden="true" ></span>
No spam. Unsubscribe at any time. Read our
{newsletterFinePrintPrefix}
<a
href="/privacy"
href="{newsletterPrivacyHref}"
class="font-semibold text-indigo-600 hover:text-indigo-800 dark:text-indigo-400 dark:hover:text-indigo-300"
>
privacy policy
</a>.
{newsletterPrivacyLabel}
</a>{newsletterFinePrintSuffix}
</p>
<div
@@ -148,7 +181,7 @@ component PublicFooter {
<span
class="grid h-11 w-11 place-items-center rounded-2xl bg-indigo-600 text-white shadow-lg shadow-indigo-600/20 transition group-hover:-translate-y-0.5 group-hover:bg-indigo-500"
>
<span class="icon-[lucide--blocks] h-5 w-5" aria-hidden="true" ></span>
<span class="{brandIcon} h-5 w-5" aria-hidden="true" ></span>
</span>
<span>
@@ -165,13 +198,12 @@ component PublicFooter {
</a>
<p class="mt-5 max-w-sm text-sm leading-6 text-slate-600 dark:text-slate-400">
Secure authentication, user management, organizations,
authorization, and enterprise identity for modern applications.
{brandDescription}
</p>
<div class="mt-6 flex flex-wrap items-center gap-2">
<div data-show="showStatus" class="mt-6 flex flex-wrap items-center gap-2">
<a
href="/status"
href="{statusHref}"
class="inline-flex items-center gap-2 rounded-xl border border-emerald-200 bg-emerald-50 px-3 py-2 text-xs font-semibold text-emerald-800 transition hover:border-emerald-300 hover:bg-emerald-100 dark:border-emerald-500/20 dark:bg-emerald-500/10 dark:text-emerald-300 dark:hover:border-emerald-500/30"
>
<span class="relative flex h-2 w-2">
@@ -179,12 +211,17 @@ component PublicFooter {
<span class="relative inline-flex h-2 w-2 rounded-full bg-emerald-500"></span>
</span>
All systems operational
{statusLabel}
</a>
</div>
<!-- Social links -->
<div data-show="showSocialLinks" class="mt-6 flex items-center gap-2">
{#if socialLinks.length > 0}
{#each socialLinks as item}
<a href="{item.href}" target="{item.target || '_blank'}" rel="noopener noreferrer" aria-label="{item.ariaLabel || item.label}" class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-500 transition hover:border-indigo-300 hover:text-indigo-700 dark:border-white/10 dark:bg-white/5 dark:text-slate-400 dark:hover:border-indigo-500/30 dark:hover:text-indigo-300"><span class="{item.icon || 'icon-[lucide--external-link]'} h-4.5 w-4.5" aria-hidden="true"></span><span class="sr-only">{item.label}</span></a>
{/each}
{:else}
<a
href="https://github.com/wrnexus"
target="_blank"
@@ -224,15 +261,21 @@ component PublicFooter {
>
<span class="icon-[lucide--youtube] h-4.5 w-4.5" aria-hidden="true" ></span>
</a>
{/if}
</div>
</div>
<!-- Navigation columns -->
<nav
aria-label="Footer navigation"
aria-label="{navigationAriaLabel}"
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-3 xl:grid-cols-5"
>
<slot name="navigation">
{#if navigationColumns.length > 0}
{#each navigationColumns as column}
<div><h3 class="text-sm font-bold text-slate-950 dark:text-white">{column.title}</h3><ul class="mt-4 space-y-3">{#each column.links as item}<li><a href="{item.href}" target="{item.target || '_self'}" aria-label="{item.ariaLabel || item.label}" class="footer-link">{item.label}</a></li>{/each}</ul></div>
{/each}
{:else}
<!-- Product -->
<div>
<h3 class="text-sm font-bold text-slate-950 dark:text-white">
@@ -547,6 +590,7 @@ component PublicFooter {
</li>
</ul>
</div>
{/if}
</slot>
</nav>
</div>
@@ -570,6 +614,9 @@ component PublicFooter {
</div>
<div class="flex flex-wrap items-center gap-x-5 gap-y-3">
{#if legalLinks.length > 0}
{#each legalLinks as item}<a href="{item.href}" target="{item.target || '_self'}" class="footer-bottom-link">{item.label}</a>{/each}
{:else}
<a
href="/privacy"
class="footer-bottom-link"
@@ -595,27 +642,30 @@ component PublicFooter {
href="/legal"
class="footer-bottom-link"
>
Legal
{legalTitle}
</a>
{/if}
<button data-show="showThemeToggle"
<button data-show="showCookiePreferences"
type="button"
data-open-cookie-preferences
class="footer-bottom-link"
>
Cookie preferences
{cookiePreferencesLabel}
</button>
<button
<button data-show="showThemeToggle"
type="button"
data-wire-theme-toggle
aria-label="{themeToggleLabel}"
title="{themeToggleLabel}"
class="inline-flex items-center gap-2 rounded-lg text-xs font-semibold text-slate-500 transition hover:text-indigo-700 dark:text-slate-400 dark:hover:text-indigo-300"
>
<span class="icon-[lucide--moon] h-3.5 w-3.5 dark:hidden" aria-hidden="true" ></span>
<span class="icon-[lucide--sun] hidden h-3.5 w-3.5 dark:block" aria-hidden="true" ></span>
Theme
{themeLabel}
</button>
</div>
</div>
+50 -9
View File
@@ -1,4 +1,15 @@
component PublicHeader {
types {
interface PublicHeaderNavigationItem {
label: string
href: string
ariaLabel?: string
target?: string
}
interface PublicHeaderActionItem extends PublicHeaderNavigationItem {
variant?: "primary" | "secondary" | "ghost"
}
}
props {
class: string = ""
homeHref: string = "/"
@@ -22,6 +33,14 @@ component PublicHeader {
solutionsLabel: string = "Solutions"
developersLabel: string = "Developers"
resourcesLabel: string = "Resources"
navigationAriaLabel: string = "Main navigation"
themeToggleLabel: string = "Toggle color theme"
mobileMenuOpenLabel: string = "Open navigation menu"
mobileMenuCloseLabel: string = "Close navigation menu"
navigationItems: PublicHeaderNavigationItem[] = []
actionItems: PublicHeaderActionItem[] = []
showNavigation: boolean = true
showMobileThemeToggle: boolean = true
}
state activeMenu = ""
state mobileMenuOpen = false
@@ -71,10 +90,16 @@ component PublicHeader {
<!-- Desktop navigation -->
<nav
aria-label="Main navigation"
aria-label="{navigationAriaLabel}"
data-show="showNavigation"
class="hidden items-center gap-1 lg:flex"
>
<slot name="navigation">
{#if navigationItems.length > 0}
{#each navigationItems as item}
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="inline-flex h-10 items-center rounded-xl px-3.5 text-sm font-semibold text-slate-700 transition hover:bg-slate-100 hover:text-slate-950 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 dark:text-slate-300 dark:hover:bg-white/5 dark:hover:text-white">{item.label}</a>
{/each}
{:else}
<!-- Product -->
<button
type="button"
@@ -171,12 +196,18 @@ component PublicHeader {
>
{pricingLabel}
</a>
{/if}
</slot>
</nav>
<!-- Desktop actions -->
<div class="hidden items-center gap-2 lg:flex">
<slot name="actions">
{#if actionItems.length > 0}
{#each actionItems as item}
<a href="{item.href}" aria-label="{item.ariaLabel || item.label}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}{#if item.variant === 'primary'}<span aria-hidden="true">→</span>{/if}</a>
{/each}
{:else}
<a data-show="showStatus"
href="{statusHref}"
class="mr-1 inline-flex items-center gap-2 rounded-xl px-3 py-2 text-xs font-semibold text-slate-500 transition hover:bg-slate-100 hover:text-slate-950 dark:text-slate-400 dark:hover:bg-white/5 dark:hover:text-white"
@@ -192,8 +223,8 @@ component PublicHeader {
<button data-show="showThemeToggle"
type="button"
data-wire-theme-toggle
aria-label="Toggle color theme"
title="Toggle color theme"
aria-label="{themeToggleLabel}"
title="{themeToggleLabel}"
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm 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-300 dark:hover:border-white/20 dark:hover:bg-white/10 dark:hover:text-white"
>
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
@@ -201,7 +232,7 @@ component PublicHeader {
<span class="icon-[lucide--sun] hidden h-4 w-4 dark:block" aria-hidden="true" ></span>
<span class="sr-only">
Toggle color theme
{themeToggleLabel}
</span>
</button>
@@ -220,16 +251,17 @@ component PublicHeader {
<span class="icon-[lucide--arrow-right] h-4 w-4 transition-transform group-hover:translate-x-0.5" aria-hidden="true" ></span>
</a>
{/if}
</slot>
</div>
<!-- Mobile actions -->
<div class="flex items-center gap-2 lg:hidden">
<button
<button data-show="showThemeToggle && showMobileThemeToggle"
type="button"
data-wire-theme-toggle
aria-label="Toggle color theme"
title="Toggle color theme"
aria-label="{themeToggleLabel}"
title="{themeToggleLabel}"
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-600 shadow-sm 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-300 dark:hover:bg-white/10 dark:hover:text-white"
>
<span class="icon-[lucide--moon] h-4 w-4 dark:hidden" aria-hidden="true" ></span>
@@ -240,7 +272,7 @@ component PublicHeader {
<button
type="button"
@click="mobileMenuOpen = !mobileMenuOpen"
aria-label="{mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}"
aria-label="{mobileMenuOpen ? mobileMenuCloseLabel : mobileMenuOpenLabel}"
aria-expanded="{mobileMenuOpen}"
aria-controls="public-mobile-navigation"
class="grid h-10 w-10 place-items-center rounded-xl border border-slate-200 bg-white text-slate-700 shadow-sm 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-200 dark:hover:bg-white/10 dark:hover:text-white"
@@ -258,7 +290,7 @@ component PublicHeader {
</div>
<!-- Outside-click backdrop -->
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="Close navigation menu" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
<button type="button" data-show="activeMenu !== ''" @click="activeMenu = ''" aria-label="{mobileMenuCloseLabel}" class="fixed inset-0 z-40 hidden cursor-default bg-transparent lg:block" class:hidden="activeMenu === ''" class:block="activeMenu !== ''" ></button>
<!-- Desktop mega menus -->
<div
@@ -302,7 +334,16 @@ component PublicHeader {
class:hidden="!mobileMenuOpen"
class:block="mobileMenuOpen"
>
{#if navigationItems.length > 0 || actionItems.length > 0}
<nav aria-label="{navigationAriaLabel}" class="border-t border-slate-200 bg-white px-5 py-5 shadow-xl dark:border-white/10 dark:bg-slate-950">
<div class="grid gap-2">
{#each navigationItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="rounded-xl px-4 py-3 text-sm font-semibold text-slate-700 hover:bg-slate-100 dark:text-slate-200 dark:hover:bg-white/5">{item.label}</a>{/each}
{#each actionItems as item}<a href="{item.href}" target="{item.target || '_self'}" class="wire-public-header-action wire-public-header-action--{item.variant || 'secondary'}">{item.label}</a>{/each}
</div>
</nav>
{:else}
<PublicMobileNavigation />
{/if}
</div>
</header>
}