Files
Clintchiz 027873459d
Quality / quality (windows-latest) (push) Waiting to run
Quality / quality (ubuntu-latest) (push) Failing after 9m57s
fix(ui): collapse empty page header slots
2026-08-25 15:56:57 +05:30

705 lines
19 KiB
Plaintext

import Breadcrumb from "./Breadcrumb.wrn"
import Button from "./button.wrn"
component PageHeader {
props {
eyebrow: string = ""
title: string = ""
description: string = ""
icon: string = ""
id: string = "page-title"
align: string = "left"
centered: boolean = false
compact: boolean = false
size: string = "default"
maxWidth: string = "xl"
showBreadcrumbs: boolean = false
breadcrumbs: unknown[] = []
breadcrumbParent: string = ""
breadcrumbParentHref: string = ""
breadcrumbCurrent: string = ""
primaryLabel: string = ""
primaryHref: string = ""
primaryIcon: string = ""
secondaryLabel: string = ""
secondaryHref: string = ""
secondaryIcon: string = ""
color: string = "primary"
variant: string = "default"
borderBottom: boolean = true
class: string = ""
}
view {
<section
{...attrs}
data-ui-component="PageHeader"
data-size='{size}'
data-align='{centered ? "center" : align}'
data-color='{color}'
data-variant='{variant}'
data-compact='{compact ? "true" : "false"}'
data-max-width='{maxWidth}'
data-border-bottom='{borderBottom ? "true" : "false"}'
aria-labelledby='{id}'
class='wrn-page-header {class}'
>
<div
class="wrn-page-header__decoration"
aria-hidden="true"
>
<span
class="wrn-page-header__glow wrn-page-header__glow--one"
>
</span>
<span
class="wrn-page-header__glow wrn-page-header__glow--two"
>
</span>
<span
class="wrn-page-header__grid-pattern"
>
</span>
</div>
<div
class="wrn-page-header__inner"
>
{#if showBreadcrumbs && breadcrumbs.length > 0}
<div
class="wrn-page-header__breadcrumb"
>
<Breadcrumb
label="Breadcrumb"
items='{breadcrumbs}'
active='{breadcrumbs[breadcrumbs.length - 1]?.value || breadcrumbs[breadcrumbs.length - 1]?.label || breadcrumbs[breadcrumbs.length - 1]?.title || ""}'
color='{color}'
size='{compact ? "sm" : "default"}'
variant='{variant === "solid" ? "contrast" : "minimal"}'
/>
</div>
{:else if showBreadcrumbs && breadcrumbParent}
<div
class="wrn-page-header__breadcrumb"
>
<Breadcrumb
label="Breadcrumb"
items='{[
{
label: breadcrumbParent,
href: breadcrumbParentHref,
value: breadcrumbParent
},
{
label: breadcrumbCurrent || title,
value: breadcrumbCurrent || title,
current: true
}
]}'
active='{breadcrumbCurrent || title}'
color='{color}'
size='{compact ? "sm" : "default"}'
variant='{variant === "solid" ? "contrast" : "minimal"}'
/>
</div>
{:else if showBreadcrumbs && (breadcrumbCurrent || title)}
<div
class="wrn-page-header__breadcrumb"
>
<Breadcrumb
label="Breadcrumb"
items='{[
{
label: breadcrumbCurrent || title,
value: breadcrumbCurrent || title,
current: true
}
]}'
active='{breadcrumbCurrent || title}'
color='{color}'
size='{compact ? "sm" : "default"}'
variant='{variant === "solid" ? "contrast" : "minimal"}'
/>
</div>
{/if}
<div
class="wrn-page-header__layout"
>
<div
class="wrn-page-header__content"
>
<div
class="wrn-page-header__intro"
>
{#if icon}
<span
class="wrn-page-header__icon-wrap"
>
{#if icon === "sparkles"}
<span
class="icon-[lucide--sparkles] wrn-page-header__icon"
aria-hidden="true"
>
</span>
{:else if icon === "component"}
<span
class="icon-[lucide--component] wrn-page-header__icon"
aria-hidden="true"
>
</span>
{:else}
<span
class='{icon} wrn-page-header__icon'
aria-hidden="true"
>
</span>
{/if}
</span>
{/if}
<div
class="wrn-page-header__copy"
>
{#if eyebrow}
<div
class="wrn-page-header__eyebrow-row"
>
<span
class="wrn-page-header__eyebrow-mark"
aria-hidden="true"
>
</span>
<p
class="wrn-page-header__eyebrow"
>
{eyebrow}
</p>
</div>
{/if}
{#if title}
<h1
id='{id}'
class="wrn-page-header__title"
>
{title}
</h1>
{/if}
{#if description}
<p
class="wrn-page-header__description"
>
{description}
</p>
{/if}
<div
class="wrn-page-header__meta"
>
<slot
name="meta"
>
</slot>
</div>
</div>
</div>
</div>
{#if primaryLabel || secondaryLabel}
<div
class="wrn-page-header__actions"
>
{#if secondaryLabel}
<Button
label='{secondaryLabel}'
href='{secondaryHref || "#"}'
icon='{secondaryIcon}'
size='{compact ? "sm" : "default"}'
variant="outline"
color='{color}'
class="wrn-page-header__action wrn-page-header__action--secondary"
/>
{/if}
{#if primaryLabel}
<Button
label='{primaryLabel}'
href='{primaryHref || "#"}'
icon='{primaryIcon}'
size='{compact ? "sm" : "default"}'
variant="default"
color='{color}'
class="wrn-page-header__action wrn-page-header__action--primary"
/>
{/if}
<slot
name="actions"
>
</slot>
</div>
{:else}
<div
class="wrn-page-header__actions wrn-page-header__actions--slot-only"
>
<slot
name="actions"
>
</slot>
</div>
{/if}
</div>
<div
class="wrn-page-header__body"
>
<slot></slot>
</div>
</div>
</section>
}
style {
.wrn-page-header {
--page-header-accent: var(--wrn-color-primary);
--page-header-soft: var(--wrn-color-primary-soft);
--page-header-muted: var(--wrn-color-primary-muted);
--page-header-accent-text: var(--wrn-color-primary-text);
position: relative;
isolation: isolate;
width: 100%;
overflow: hidden;
color: var(--wrn-color-text);
background: var(--wrn-color-background);
}
.wrn-page-header[data-color="secondary"] {
--page-header-accent: var(--wrn-color-secondary);
--page-header-soft: var(--wrn-color-secondary-soft);
--page-header-muted: var(--wrn-color-secondary-muted);
--page-header-accent-text: var(--wrn-color-secondary-text);
}
.wrn-page-header[data-color="info"] {
--page-header-accent: var(--wrn-color-info);
--page-header-soft: var(--wrn-color-info-soft);
--page-header-muted: var(--wrn-color-info-muted);
--page-header-accent-text: var(--wrn-color-info-text);
}
.wrn-page-header[data-color="success"] {
--page-header-accent: var(--wrn-color-success);
--page-header-soft: var(--wrn-color-success-soft);
--page-header-muted: var(--wrn-color-success-muted);
--page-header-accent-text: var(--wrn-color-success-text);
}
.wrn-page-header[data-color="warning"] {
--page-header-accent: var(--wrn-color-warning);
--page-header-soft: var(--wrn-color-warning-soft);
--page-header-muted: var(--wrn-color-warning-muted);
--page-header-accent-text: var(--wrn-color-warning-text);
}
.wrn-page-header[data-color="danger"] {
--page-header-accent: var(--wrn-color-danger);
--page-header-soft: var(--wrn-color-danger-soft);
--page-header-muted: var(--wrn-color-danger-muted);
--page-header-accent-text: var(--wrn-color-danger-text);
}
.wrn-page-header[data-variant="soft"] {
background: var(--wrn-color-surface);
}
.wrn-page-header[data-variant="raised"] {
background: var(--wrn-color-surface-raised);
box-shadow: var(--wrn-shadow-sm);
}
.wrn-page-header[data-variant="tinted"] {
background:
linear-gradient(135deg, var(--page-header-soft), transparent 72%),
var(--wrn-color-background);
}
.wrn-page-header[data-variant="solid"] {
color: #ffffff;
background: var(--page-header-accent);
}
.wrn-page-header[data-variant="minimal"] {
background: transparent;
}
.wrn-page-header[data-border-bottom="true"] {
border-bottom: 1px solid var(--wrn-color-border);
}
.wrn-page-header[data-variant="solid"][data-border-bottom="true"] {
border-bottom-color: rgba(255, 255, 255, 0.18);
}
.wrn-page-header__decoration {
position: absolute;
inset: 0;
z-index: -1;
overflow: hidden;
pointer-events: none;
}
.wrn-page-header__glow {
position: absolute;
display: block;
border-radius: 999px;
filter: blur(60px);
opacity: 0.17;
}
.wrn-page-header__glow--one {
top: -9rem;
right: -5rem;
width: 24rem;
height: 24rem;
background: var(--page-header-accent);
}
.wrn-page-header__glow--two {
bottom: -10rem;
left: 18%;
width: 18rem;
height: 18rem;
background: var(--page-header-muted);
opacity: 0.13;
}
.wrn-page-header__grid-pattern {
position: absolute;
inset: 0;
opacity: 0.2;
background-image:
linear-gradient(to right, var(--wrn-color-border) 1px, transparent 1px),
linear-gradient(to bottom, var(--wrn-color-border) 1px, transparent 1px);
background-size: 32px 32px;
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0.35), transparent 78%);
}
.wrn-page-header[data-variant="minimal"] .wrn-page-header__decoration,
.wrn-page-header[data-compact="true"] .wrn-page-header__grid-pattern {
display: none;
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__grid-pattern {
opacity: 0.1;
background-image:
linear-gradient(to right, rgba(255, 255, 255, 0.28) 1px, transparent 1px),
linear-gradient(to bottom, rgba(255, 255, 255, 0.28) 1px, transparent 1px);
}
.wrn-page-header__inner {
position: relative;
width: 100%;
max-width: 80rem;
margin-inline: auto;
padding: 1.25rem 1rem 2.75rem;
}
.wrn-page-header[data-max-width="compact"] .wrn-page-header__inner,
.wrn-page-header[data-max-width="lg"] .wrn-page-header__inner {
max-width: 64rem;
}
.wrn-page-header[data-max-width="wide"] .wrn-page-header__inner,
.wrn-page-header[data-max-width="2xl"] .wrn-page-header__inner {
max-width: 90rem;
}
.wrn-page-header[data-max-width="full"] .wrn-page-header__inner {
max-width: none;
}
.wrn-page-header[data-compact="true"] .wrn-page-header__inner {
padding-top: 1rem;
padding-bottom: 1.75rem;
}
.wrn-page-header[data-size="lg"] .wrn-page-header__inner {
padding-bottom: 3.5rem;
}
.wrn-page-header__breadcrumb {
display: flex;
align-items: center;
min-width: 0;
margin-bottom: 1.75rem;
padding-bottom: 0.9rem;
border-bottom: 1px solid var(--wrn-color-border);
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__breadcrumb {
border-bottom-color: rgba(255, 255, 255, 0.18);
}
.wrn-page-header[data-compact="true"] .wrn-page-header__breadcrumb {
margin-bottom: 1.25rem;
padding-bottom: 0.7rem;
}
.wrn-page-header__layout {
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 1.75rem;
align-items: start;
}
.wrn-page-header__content,
.wrn-page-header__copy {
min-width: 0;
}
.wrn-page-header__intro {
display: flex;
align-items: flex-start;
gap: 1rem;
min-width: 0;
}
.wrn-page-header__icon-wrap {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
width: 3.25rem;
height: 3.25rem;
margin-top: 0.15rem;
color: var(--page-header-accent);
background: var(--page-header-soft);
border: 1px solid var(--page-header-muted);
border-radius: 1rem;
box-shadow: var(--wrn-shadow-sm);
}
.wrn-page-header[data-compact="true"] .wrn-page-header__icon-wrap {
width: 2.75rem;
height: 2.75rem;
border-radius: 0.8rem;
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__icon-wrap {
color: #ffffff;
background: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.22);
}
.wrn-page-header__icon {
width: 1.35rem;
height: 1.35rem;
}
.wrn-page-header__eyebrow-row {
display: flex;
align-items: center;
gap: 0.65rem;
margin-bottom: 0.65rem;
}
.wrn-page-header__eyebrow-mark {
width: 1.75rem;
height: 2px;
flex: 0 0 auto;
background: var(--page-header-accent);
border-radius: 999px;
}
.wrn-page-header__eyebrow {
margin: 0;
color: var(--page-header-accent-text);
font-size: 0.72rem;
font-weight: 800;
line-height: 1.3;
letter-spacing: 0.16em;
text-transform: uppercase;
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__eyebrow,
.wrn-page-header[data-variant="solid"] .wrn-page-header__title,
.wrn-page-header[data-variant="solid"] .wrn-page-header__description {
color: #ffffff;
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__eyebrow-mark {
background: rgba(255, 255, 255, 0.8);
}
.wrn-page-header__title {
max-width: 54rem;
margin: 0;
color: var(--wrn-color-text);
font-size: clamp(2rem, 4vw, 3.75rem);
font-weight: 800;
line-height: 1.08;
letter-spacing: -0.035em;
text-wrap: balance;
}
.wrn-page-header[data-compact="true"] .wrn-page-header__title {
font-size: clamp(1.75rem, 3vw, 2.65rem);
}
.wrn-page-header[data-size="lg"] .wrn-page-header__title {
font-size: clamp(2.35rem, 5vw, 4.5rem);
}
.wrn-page-header__description {
max-width: 48rem;
margin: 0.9rem 0 0;
color: var(--wrn-color-text-muted);
font-size: 1rem;
line-height: 1.75;
text-wrap: pretty;
}
.wrn-page-header[data-size="lg"] .wrn-page-header__description {
font-size: 1.075rem;
}
.wrn-page-header[data-variant="solid"] .wrn-page-header__description {
opacity: 0.82;
}
.wrn-page-header__meta:empty,
.wrn-page-header__body:empty,
.wrn-page-header__actions--slot-only:empty {
display: none;
}
/* Compiled empty slots retain inert template markers, so :empty alone
cannot collapse their spacing in the rendered document. */
.wrn-page-header__meta:not(:has(> :not(template))),
.wrn-page-header__body:not(:has(> :not(template))),
.wrn-page-header__actions--slot-only:not(:has(> :not(template))) {
display: none;
}
.wrn-page-header__meta {
margin-top: 1.1rem;
}
.wrn-page-header__actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.75rem;
min-width: 0;
}
/*
* Slot content is app-authored, so a bare `p { color: ... }` in the app
* stylesheet beats anything this container merely passes down by
* inheritance. State it, at a specificity the app can still override.
*/
.wrn-page-header__body {
color: var(--wrn-color-text);
margin-top: 1.75rem;
}
.wrn-page-header[data-align="center"] .wrn-page-header__layout {
justify-items: center;
text-align: center;
}
.wrn-page-header[data-align="center"] .wrn-page-header__intro {
flex-direction: column;
align-items: center;
}
.wrn-page-header[data-align="center"] .wrn-page-header__eyebrow-row,
.wrn-page-header[data-align="center"] .wrn-page-header__actions {
justify-content: center;
}
.wrn-page-header[data-align="center"] .wrn-page-header__title,
.wrn-page-header[data-align="center"] .wrn-page-header__description {
margin-inline: auto;
}
.wrn-page-header[data-align="right"] .wrn-page-header__layout {
justify-items: end;
text-align: right;
}
.wrn-page-header[data-align="right"] .wrn-page-header__intro {
flex-direction: row-reverse;
}
.wrn-page-header[data-align="right"] .wrn-page-header__eyebrow-row,
.wrn-page-header[data-align="right"] .wrn-page-header__actions {
justify-content: flex-end;
}
.wrn-page-header[data-align="right"] .wrn-page-header__title,
.wrn-page-header[data-align="right"] .wrn-page-header__description {
margin-left: auto;
}
@media (min-width: 640px) {
.wrn-page-header__inner {
padding-inline: 1.5rem;
}
.wrn-page-header__intro {
gap: 1.25rem;
}
}
@media (min-width: 1024px) {
.wrn-page-header__inner {
padding-inline: 2rem;
}
.wrn-page-header__layout {
grid-template-columns: minmax(0, 1fr) auto;
align-items: start;
gap: 2.5rem;
}
.wrn-page-header[data-align="center"] .wrn-page-header__layout,
.wrn-page-header[data-align="right"] .wrn-page-header__layout {
grid-template-columns: minmax(0, 1fr);
}
.wrn-page-header__actions {
align-self: start;
justify-self: end;
justify-content: flex-end;
padding-top: 0;
padding-bottom: 0;
}
}
@media (max-width: 639px) {
.wrn-page-header__intro {
flex-direction: column;
}
.wrn-page-header__actions {
display: grid;
grid-template-columns: minmax(0, 1fr);
}
.wrn-page-header__action {
width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
.wrn-page-header__glow {
filter: blur(52px);
}
}
}
}