release: WRNexusJS 0.5.12

This commit is contained in:
2026-07-30 18:59:01 +05:30
parent f37303b22d
commit d64e899993
462 changed files with 75432 additions and 2570 deletions
+23 -1
View File
@@ -7,7 +7,29 @@ component Columns {
maxWidth = "xl"
class = ""
}
view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--columns wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
<div
data-ui-component="Columns"
data-size='{size}'
data-color='{color}'
data-columns='{columns}'
data-gap='{gap}'
class='grid w-full grid-cols-1 items-start {class}'
class:max-w-3xl='maxWidth === "md"'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl"'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:md:grid-cols-2='columns >= 2'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:gap-3='gap === "sm"'
class:gap-6='gap === "md"'
class:gap-10='gap === "lg"'
class:gap-14='gap === "xl"'
>
<slot></slot>
</div>
}
}
+31 -5
View File
@@ -1,7 +1,5 @@
component Image {
props {
@event load = function
@event error = function
size = "default"
color = "primary"
src = ""
@@ -12,10 +10,38 @@ component Image {
rounded = false
class = ""
}
view {
<figure class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--image {rounded ? 'wire-next--rounded' : ''} {class}">
<img src="{src}" alt="{alt}" width="{width}" height="{height}" loading="{loading}" />
<slot />
<figure
data-ui-component="Image"
class='relative m-0 overflow-hidden bg-[var(--wire-color-surface-soft)] {class}'
class:rounded-2xl='rounded'
>
{#if src}
<img
src='{src}'
alt='{alt}'
width='{width}'
height='{height}'
loading='{loading}'
decoding="async"
class="block h-auto w-full object-cover transition duration-300"
class:aspect-square='size === "square"'
class:aspect-video='size === "video"'
class:aspect-[4/3]='size === "landscape"'
class:aspect-[3/4]='size === "portrait"'
/>
{:else}
<div
class="flex min-h-48 w-full items-center justify-center text-[var(--wire-color-text-muted)]"
role="img"
aria-label='{alt || "Image placeholder"}'
>
<span class="icon-[lucide--image] size-8" aria-hidden="true"></span>
</div>
{/if}
<slot></slot>
</figure>
}
}
+33 -4
View File
@@ -1,8 +1,5 @@
component Link {
props {
@event click = function
@event focus = function
@event blur = function
size = "default"
color = "primary"
label = "Link"
@@ -12,7 +9,39 @@ component Link {
external = false
class = ""
}
view {
<a {...attrs} href="{href}" target="{target}" rel="{rel}" class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--link {class}"><slot>{label}</slot>{#if external}<span aria-hidden="true">↗</span>{/if}</a>
<a
data-ui-component="Link"
href='{href}'
target='{target}'
rel='{external ? (rel || "noopener noreferrer") : rel}'
class='inline-flex min-w-0 items-center gap-1.5 rounded-md font-semibold underline-offset-4 outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--wire-color-background)] {class}'
class:text-xs='size === "xs"'
class:text-sm='size === "sm" || size === "default"'
class:text-base='size === "md"'
class:text-lg='size === "lg"'
class:text-[var(--wire-color-primary)]='color === "primary"'
class:hover:text-[var(--wire-color-primary-hover)]='color === "primary"'
class:text-[var(--wire-color-secondary)]='color === "secondary"'
class:hover:text-[var(--wire-color-secondary-hover)]='color === "secondary"'
class:text-[var(--wire-color-success)]='color === "success"'
class:text-[var(--wire-color-warning-text)]='color === "warning"'
class:text-[var(--wire-color-danger)]='color === "danger"'
class:text-[var(--wire-color-info)]='color === "info"'
class:text-[var(--wire-color-text)]='color === "neutral"'
class:hover:underline='external === false'
>
<span class="truncate">{label}</span>
{#if external}
<span
class="icon-[lucide--external-link] size-3.5 shrink-0"
aria-hidden="true"
></span>
{/if}
<slot></slot>
</a>
}
}
+93 -7
View File
@@ -1,7 +1,5 @@
component List {
props {
@event select = function
@event click = function
size = "default"
color = "primary"
title = "List"
@@ -10,12 +8,100 @@ component List {
variant = "default"
class = ""
}
view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--list wire-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section
data-ui-component="List"
aria-label='{title}'
class='w-full {class}'
>
{#if title || description}
<header class="mb-4">
{#if title}
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3>
{/if}
{#if description}
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
{/if}
</header>
{/if}
<ul
class="overflow-hidden"
class:divide-y='variant === "default" || variant === "divided"'
class:divide-[var(--wire-color-border)]='variant === "default" || variant === "divided"'
class:rounded-2xl='variant === "card"'
class:border='variant === "card"'
class:border-[var(--wire-color-border)]='variant === "card"'
class:bg-[var(--wire-color-surface-raised)]='variant === "card"'
class:space-y-3='variant === "separated"'
>
{#each items as item, index}
<li
class="group min-w-0"
class:rounded-xl='variant === "separated"'
class:border='variant === "separated"'
class:border-[var(--wire-color-border)]='variant === "separated"'
class:bg-[var(--wire-color-surface-raised)]='variant === "separated"'
>
{#if item.href}
<a
href='{item.href}'
class="flex min-w-0 items-start gap-3 px-4 py-3 outline-none transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
class:px-3='size === "sm"'
class:py-2.5='size === "sm"'
class:px-5='size === "lg"'
class:py-4='size === "lg"'
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
>
{#if item.icon}
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
</span>
{/if}
{#if item.imageSrc}
<img src='{item.imageSrc}' alt='{item.imageAlt || ""}' class="size-12 shrink-0 rounded-xl object-cover" loading="lazy" />
{/if}
<div class="min-w-0 flex-1">
<div class="flex items-start justify-between gap-3">
<p class="truncate font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p>
{#if item.meta}
<span class="shrink-0 text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span>
{/if}
</div>
{#if item.description}
<p class="mt-1 line-clamp-2 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p>
{/if}
{#if item.badge}
<span class="mt-2 inline-flex rounded-full bg-[var(--wire-color-primary-soft)] px-2.5 py-1 text-xs font-semibold text-[var(--wire-color-primary)]">{item.badge}</span>
{/if}
</div>
<span class="icon-[lucide--chevron-right] mt-1 size-4 shrink-0 text-[var(--wire-color-text-muted)] transition group-hover:translate-x-0.5 group-hover:text-[var(--wire-color-primary)]" aria-hidden="true"></span>
</a>
{:else}
<div class="flex min-w-0 items-start gap-3 px-4 py-3">
{#if item.icon}
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
<span class='{item.icon + " size-5"}' aria-hidden="true"></span>
</span>
{/if}
<div class="min-w-0 flex-1">
<p class="font-semibold text-[var(--wire-color-text)]">{item.title || item.label}</p>
{#if item.description}
<p class="mt-1 text-sm leading-5 text-[var(--wire-color-text-muted)]">{item.description}</p>
{/if}
</div>
</div>
{/if}
</li>
{:empty}
<li class="rounded-xl border border-dashed border-[var(--wire-color-border)] p-6 text-center text-sm text-[var(--wire-color-text-muted)]">
No items available.
</li>
{/each}
</ul>
<slot></slot>
</section>
}
}
+71 -9
View File
@@ -1,9 +1,5 @@
component Map {
props {
@event ready = function
@event move = function
@event zoom = function
@event markerClick = function
size = "default"
color = "primary"
title = "Map"
@@ -12,12 +8,78 @@ component Map {
variant = "default"
class = ""
}
view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--map wire-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section
data-ui-component="Map"
aria-label='{title}'
class='overflow-hidden rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] shadow-sm {class}'
>
{#if title || description}
<header class="flex items-start justify-between gap-4 border-b border-[var(--wire-color-border)] p-5">
<div>
{#if title}
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{title}</h3>
{/if}
{#if description}
<p class="mt-1 text-sm leading-6 text-[var(--wire-color-text-muted)]">{description}</p>
{/if}
</div>
<span class="flex size-10 shrink-0 items-center justify-center rounded-xl bg-[var(--wire-color-primary-soft)] text-[var(--wire-color-primary)]">
<span class="icon-[lucide--map] size-5" aria-hidden="true"></span>
</span>
</header>
{/if}
<div
class="relative isolate overflow-hidden bg-[var(--wire-color-surface-soft)]"
class:h-64='size === "sm"'
class:h-80='size === "default" || size === "md"'
class:h-[28rem]='size === "lg"'
>
<div
class="pointer-events-none absolute inset-0 opacity-50"
style="background-image: linear-gradient(var(--wire-color-border) 1px, transparent 1px), linear-gradient(90deg, var(--wire-color-border) 1px, transparent 1px); background-size: 32px 32px;"
aria-hidden="true"
></div>
<div class="absolute inset-0 flex items-center justify-center p-6">
<slot></slot>
</div>
{#if items.length > 0}
{#each items as item, index}
<button
type="button"
aria-label='{item.label || item.title || "Map marker"}'
class="absolute inline-flex size-10 items-center justify-center rounded-full border-4 border-[var(--wire-color-surface-raised)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-lg transition hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
style='left: {item.x || (20 + index * 12)}%; top: {item.y || (30 + (index % 3) * 18)}%;'
@click='event.currentTarget.dispatchEvent(new CustomEvent("markerClick", { bubbles: true, detail: item })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: item }))'
>
<span class='{item.icon || "icon-[lucide--map-pin]"}' aria-hidden="true"></span>
</button>
{/each}
{/if}
<div class="absolute bottom-4 right-4 flex flex-col gap-2">
<button
type="button"
aria-label="Zoom in"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "in" } }))'
>
<span class="icon-[lucide--plus] size-4" aria-hidden="true"></span>
</button>
<button
type="button"
aria-label="Zoom out"
class="inline-flex size-10 items-center justify-center rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] text-[var(--wire-color-text)] shadow-sm transition hover:bg-[var(--wire-color-surface-soft)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='event.currentTarget.dispatchEvent(new CustomEvent("zoom", { bubbles: true, detail: { direction: "out" } }))'
>
<span class="icon-[lucide--minus] size-4" aria-hidden="true"></span>
</button>
</div>
</div>
</section>
}
}
+97 -7
View File
@@ -1,7 +1,5 @@
component Marquee {
props {
@event pause = function
@event resume = function
size = "default"
color = "primary"
title = "Marquee"
@@ -10,12 +8,104 @@ component Marquee {
variant = "default"
class = ""
}
state paused = false
view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--marquee wire-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section
data-ui-component="Marquee"
aria-label='{title}'
class='overflow-hidden border-y border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] {class}'
>
<div class="flex items-stretch">
{#if title}
<div class="relative z-10 flex shrink-0 items-center gap-2 border-r border-[var(--wire-color-border)] bg-[var(--wire-color-primary)] px-4 py-3 text-[var(--wire-color-on-primary)] sm:px-5">
<span class="icon-[lucide--megaphone] size-4" aria-hidden="true"></span>
<span class="text-sm font-bold">{title}</span>
</div>
{/if}
<div
class="group relative min-w-0 flex-1 overflow-hidden"
@mouseenter='paused = true; event.currentTarget.dispatchEvent(new CustomEvent("pause", { bubbles: true }))'
@mouseleave='paused = false; event.currentTarget.dispatchEvent(new CustomEvent("resume", { bubbles: true }))'
@focusin='paused = true'
@focusout='paused = false'
>
<div
class="wire-marquee-track flex w-max min-w-full items-center"
class:wire-marquee-paused='paused'
>
{#each items as item}
<a
href='{item.href || "#"}'
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)] transition hover:bg-[var(--wire-color-primary-soft)] hover:text-[var(--wire-color-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
>
<span
class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]"
class:bg-[var(--wire-color-danger)]='item.color === "danger"'
class:bg-[var(--wire-color-warning)]='item.color === "warning"'
class:bg-[var(--wire-color-success)]='item.color === "success"'
aria-hidden="true"
></span>
<span>{item.label || item.title}</span>
{#if item.meta}
<span class="text-xs text-[var(--wire-color-text-muted)]">{item.meta}</span>
{/if}
</a>
{:empty}
<p class="px-5 py-3 text-sm text-[var(--wire-color-text-muted)]">{description || "No announcements available."}</p>
{/each}
{#if items.length > 0}
{#each items as item}
<a
href='{item.href || "#"}'
aria-hidden="true"
tabindex="-1"
class="flex shrink-0 items-center gap-3 px-5 py-3 text-sm font-medium text-[var(--wire-color-text)]"
>
<span class="size-2 shrink-0 rounded-full bg-[var(--wire-color-primary)]" aria-hidden="true"></span>
<span>{item.label || item.title}</span>
</a>
{/each}
{/if}
</div>
</div>
<button
type="button"
aria-label='{paused ? "Resume announcements" : "Pause announcements"}'
class="flex shrink-0 items-center justify-center border-l border-[var(--wire-color-border)] px-4 text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--wire-color-focus)]"
@click='paused = !paused; event.currentTarget.dispatchEvent(new CustomEvent(paused ? "pause" : "resume", { bubbles: true }))'
>
<span class="icon-[lucide--pause] size-4" data-show='!paused' aria-hidden="true"></span>
<span class="icon-[lucide--play] size-4" data-show='paused' aria-hidden="true"></span>
</button>
</div>
<slot></slot>
</section>
}
style {
.wire-marquee-track {
animation: wire-marquee 32s linear infinite;
}
.wire-marquee-paused {
animation-play-state: paused;
}
@keyframes wire-marquee {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
.wire-marquee-track {
animation: none;
}
}
}
}
+61 -6
View File
@@ -1,10 +1,5 @@
component SearchBox {
props {
@event input = function
@event change = function
@event search = function
@event submit = function
@event clear = function
size = "default"
color = "primary"
label = "Search Box"
@@ -19,7 +14,67 @@ component SearchBox {
required = false
class = ""
}
state query = value
view {
<label class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--field wire-next--search-box {class}"><span>{label}</span><input {...attrs} type="{type}" name="{name}" value="{value}" placeholder="{placeholder}" min="{min}" max="{max}" step="{step}" disabled="{disabled}" required="{required}" /></label>
<form
data-ui-component="SearchBox"
role="search"
class='w-full {class}'
@submit='event.preventDefault(); event.currentTarget.dispatchEvent(new CustomEvent("search", { bubbles: true, detail: { value: query, name: name } }))'
>
<label
class="mb-2 block text-sm font-semibold text-[var(--wire-color-text)]"
class:sr-only='label === ""'
>
{label}
</label>
<div class="relative flex items-center">
<span
class="icon-[lucide--search] pointer-events-none absolute left-4 size-5 text-[var(--wire-color-input-placeholder)]"
aria-hidden="true"
></span>
<input
name='{name}'
type='{type}'
value='{query}'
placeholder='{placeholder}'
min='{min}'
max='{max}'
step='{step}'
disabled='{disabled}'
required='{required}'
autocomplete="off"
class="w-full rounded-xl border border-[var(--wire-color-input-border)] bg-[var(--wire-color-input-background)] pl-12 pr-24 text-[var(--wire-color-input-text)] outline-none transition placeholder:text-[var(--wire-color-input-placeholder)] hover:border-[var(--wire-color-input-border-hover)] focus:border-[var(--wire-color-input-border-focus)] focus:ring-2 focus:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60"
class:h-10='size === "sm"'
class:h-12='size === "default" || size === "md"'
class:h-14='size === "lg"'
@input='query = event.target.value'
@change='query = event.target.value'
/>
<button
type="button"
aria-label="Clear search"
data-show='query.length > 0 && !disabled'
class="absolute right-12 inline-flex size-8 items-center justify-center rounded-lg text-[var(--wire-color-text-muted)] transition hover:bg-[var(--wire-color-surface-soft)] hover:text-[var(--wire-color-text)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
@click='query = ""; event.currentTarget.parentElement.querySelector("input")?.focus(); event.currentTarget.dispatchEvent(new CustomEvent("clear", { bubbles: true, detail: { value: "", name: name } }))'
>
<span class="icon-[lucide--x] size-4" aria-hidden="true"></span>
</button>
<button
type="submit"
aria-label='{label || "Search"}'
disabled='{disabled}'
class="absolute right-2 inline-flex size-9 items-center justify-center rounded-lg bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] transition hover:bg-[var(--wire-color-primary-hover)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)] disabled:cursor-not-allowed disabled:opacity-60"
>
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span>
</button>
</div>
</form>
}
}
+77 -6
View File
@@ -1,7 +1,5 @@
component Tabs {
props {
@event change = function
@event select = function
size = "default"
color = "primary"
label = "Tabs"
@@ -10,10 +8,83 @@ component Tabs {
orientation = "horizontal"
class = ""
}
state activeValue = active || (items[0] ? (items[0].value || items[0].id || "0") : "")
view {
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--tabs wire-next--{orientation} {class}" aria-label="{label}">
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
<slot />
</nav>
<section
data-ui-component="Tabs"
class='w-full {class}'
class:flex='orientation === "vertical"'
class:items-start='orientation === "vertical"'
class:gap-6='orientation === "vertical"'
>
<div
role="tablist"
aria-label='{label}'
aria-orientation='{orientation}'
class="flex min-w-0 gap-1 overflow-x-auto rounded-xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-soft)] p-1"
class:flex-col='orientation === "vertical"'
class:w-56='orientation === "vertical"'
class:shrink-0='orientation === "vertical"'
>
{#each items as item, index}
<button
type="button"
role="tab"
id='tab-{item.value || item.id || index}'
aria-selected='{activeValue === (item.value || item.id || String(index))}'
aria-controls='panel-{item.value || item.id || index}'
tabindex='{activeValue === (item.value || item.id || String(index)) ? "0" : "-1"}'
disabled='{item.disabled || false}'
class="inline-flex min-w-max flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-sm font-semibold outline-none transition disabled:cursor-not-allowed disabled:opacity-50"
class:bg-[var(--wire-color-surface-raised)]='activeValue === (item.value || item.id || String(index))'
class:text-[var(--wire-color-primary)]='activeValue === (item.value || item.id || String(index))'
class:shadow-sm='activeValue === (item.value || item.id || String(index))'
class:text-[var(--wire-color-text-muted)]='activeValue !== (item.value || item.id || String(index))'
class:hover:text-[var(--wire-color-text)]='activeValue !== (item.value || item.id || String(index))'
class:justify-start='orientation === "vertical"'
class:px-3='size === "sm"'
class:py-2='size === "sm"'
class:px-5='size === "lg"'
class:py-3='size === "lg"'
@click='activeValue = item.value || item.id || String(index); event.currentTarget.dispatchEvent(new CustomEvent("change", { bubbles: true, detail: { item: item, index: index, value: activeValue } })); event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index, value: activeValue } }))'
>
{#if item.icon}
<span class='{item.icon + " size-4"}' aria-hidden="true"></span>
{/if}
<span>{item.label || item.title}</span>
{#if item.badge}
<span class="rounded-full bg-[var(--wire-color-primary-soft)] px-2 py-0.5 text-xs text-[var(--wire-color-primary)]">{item.badge}</span>
{/if}
</button>
{/each}
</div>
<div class="min-w-0 flex-1 pt-4" class:pt-0='orientation === "vertical"'>
{#each items as item, index}
<div
role="tabpanel"
id='panel-{item.value || item.id || index}'
aria-labelledby='tab-{item.value || item.id || index}'
tabindex="0"
data-show='activeValue === (item.value || item.id || String(index))'
class="rounded-xl outline-none focus-visible:ring-2 focus-visible:ring-[var(--wire-color-focus)]"
>
{#if item.title && item.title !== item.label}
<h3 class="text-lg font-bold text-[var(--wire-color-text)]">{item.title}</h3>
{/if}
{#if item.description}
<p class="mt-2 leading-7 text-[var(--wire-color-text-muted)]">{item.description}</p>
{/if}
{#if item.content}
<div class="mt-4 text-[var(--wire-color-text)]">{item.content}</div>
{/if}
</div>
{/each}
<slot></slot>
</div>
</section>
}
}
+69 -6
View File
@@ -1,6 +1,5 @@
component Timeline {
props {
@event select = function
size = "default"
color = "primary"
title = "Timeline"
@@ -9,12 +8,76 @@ component Timeline {
variant = "default"
class = ""
}
view {
<section class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--timeline wire-next--variant-{variant} {class}">
{#if title}<strong>{title}</strong>{/if}
{#if description}<p>{description}</p>{/if}
{#if items}<div class="wire-next__items">{#each items as item}<span>{item.label}</span>{/each}</div>{/if}
<slot />
<section
data-ui-component="Timeline"
aria-label='{title}'
class='w-full {class}'
>
{#if title || description}
<header class="mb-6">
{#if title}
<h3 class="text-xl font-bold text-[var(--wire-color-text)]">{title}</h3>
{/if}
{#if description}
<p class="mt-2 max-w-2xl leading-7 text-[var(--wire-color-text-muted)]">{description}</p>
{/if}
</header>
{/if}
<ol class="relative ml-4 border-l border-[var(--wire-color-border)]">
{#each items as item, index}
<li
class="relative pb-8 pl-8 last:pb-0"
@click='event.currentTarget.dispatchEvent(new CustomEvent("select", { bubbles: true, detail: { item: item, index: index } }))'
>
<span
class="absolute -left-[1.05rem] top-0 flex size-8 items-center justify-center rounded-full border-4 border-[var(--wire-color-background)] bg-[var(--wire-color-primary)] text-[var(--wire-color-on-primary)] shadow-sm"
class:bg-[var(--wire-color-success)]='item.status === "complete" || item.status === "success"'
class:bg-[var(--wire-color-warning)]='item.status === "warning" || item.status === "pending"'
class:bg-[var(--wire-color-danger)]='item.status === "danger" || item.status === "failed"'
>
<span class='{item.icon || "icon-[lucide--circle]"}' aria-hidden="true"></span>
</span>
<article
class="rounded-2xl border border-[var(--wire-color-border)] bg-[var(--wire-color-surface-raised)] p-5 shadow-sm"
class:border-transparent='variant === "minimal"'
class:bg-transparent='variant === "minimal"'
class:p-0='variant === "minimal"'
class:shadow-none='variant === "minimal"'
>
<div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
<div>
<h4 class="font-bold text-[var(--wire-color-text)]">{item.title || item.label}</h4>
{#if item.subtitle}
<p class="mt-1 text-sm font-medium text-[var(--wire-color-primary)]">{item.subtitle}</p>
{/if}
</div>
{#if item.date || item.meta}
<time class="shrink-0 text-sm text-[var(--wire-color-text-muted)]">{item.date || item.meta}</time>
{/if}
</div>
{#if item.description}
<p class="mt-3 text-sm leading-6 text-[var(--wire-color-text-muted)]">{item.description}</p>
{/if}
{#if item.href}
<a href='{item.href}' class="mt-4 inline-flex items-center gap-2 text-sm font-semibold text-[var(--wire-color-primary)] hover:text-[var(--wire-color-primary-hover)]">
<span>{item.actionLabel || "View details"}</span>
<span class="icon-[lucide--arrow-right] size-4" aria-hidden="true"></span>
</a>
{/if}
</article>
</li>
{:empty}
<li class="pl-8 text-sm text-[var(--wire-color-text-muted)]">No timeline items available.</li>
{/each}
</ol>
<slot></slot>
</section>
}
}
+63 -1
View File
@@ -7,7 +7,69 @@ component Typography {
maxWidth = "xl"
class = ""
}
view {
<article class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--typography wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></article>
<div
data-ui-component="Typography"
class='wire-typography text-[var(--wire-color-text)] {class}'
class:max-w-3xl='maxWidth === "md"'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl"'
class:max-w-none='maxWidth === "full"'
class:text-sm='size === "sm"'
class:text-base='size === "default" || size === "md"'
class:text-lg='size === "lg"'
class:columns-1='columns === 1'
class:md:columns-2='columns === 2'
class:lg:columns-3='columns === 3'
class:gap-4='gap === "sm"'
class:gap-8='gap === "md"'
class:gap-12='gap === "lg"'
>
<slot></slot>
</div>
}
style {
.wire-typography :where(h1, h2, h3, h4) {
color: var(--wire-color-text);
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.2;
break-after: avoid;
}
.wire-typography :where(h1) { font-size: clamp(2rem, 4vw, 3.5rem); margin: 0 0 1.5rem; }
.wire-typography :where(h2) { font-size: clamp(1.5rem, 3vw, 2.25rem); margin: 2.5rem 0 1rem; }
.wire-typography :where(h3) { font-size: 1.35rem; margin: 2rem 0 0.75rem; }
.wire-typography :where(p, ul, ol, blockquote, pre, table) { margin: 1rem 0; }
.wire-typography :where(p, li) { color: var(--wire-color-text-muted); line-height: 1.8; }
.wire-typography :where(a) { color: var(--wire-color-primary); font-weight: 600; text-underline-offset: 0.2em; }
.wire-typography :where(a:hover) { color: var(--wire-color-primary-hover); text-decoration: underline; }
.wire-typography :where(ul, ol) { padding-left: 1.4rem; }
.wire-typography :where(ul) { list-style: disc; }
.wire-typography :where(ol) { list-style: decimal; }
.wire-typography :where(blockquote) {
border-left: 4px solid var(--wire-color-primary);
background: var(--wire-color-primary-soft);
border-radius: 0 0.75rem 0.75rem 0;
padding: 1rem 1.25rem;
color: var(--wire-color-text);
}
.wire-typography :where(code) {
border-radius: 0.35rem;
background: var(--wire-color-surface-soft);
padding: 0.15rem 0.35rem;
font-size: 0.9em;
}
.wire-typography :where(pre) {
overflow-x: auto;
border: 1px solid var(--wire-color-border);
border-radius: 1rem;
background: var(--wire-color-surface-raised);
padding: 1rem;
}
.wire-typography :where(img) { border-radius: 1rem; }
.wire-typography :where(hr) { border-color: var(--wire-color-border); margin: 2rem 0; }
}
}
+222 -132
View File
@@ -39,221 +39,311 @@ component Card {
class = ""
}
state visible = true
state selectedNav = activeNav
state dismissed = false
functions {
function chooseNavigation(item, index) {
previousValue = selectedNav
selectedNav = item.value || item.label
$emit("navigate", {
value: selectedNav,
previousValue: previousValue,
function dispatchCardNavigation(sourceEvent, item, index, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("navigate", true, false, {
component: "Card",
item: item,
index: index
})
root.dispatchEvent(customEvent)
}
function chooseMobileNavigation(sourceEvent) {
previousValue = selectedNav
selectedNav = sourceEvent.target.value
$emit("navigate", {
value: selectedNav,
previousValue: previousValue
function dispatchCardNavigationValue(sourceEvent, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("navigate", true, false, {
component: "Card",
value: sourceEvent.currentTarget.value
})
root.dispatchEvent(customEvent)
}
function chooseAction(action, index) {
$emit("action", {
function dispatchCardHeaderAction(sourceEvent, action, index, root, customEvent) {
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) {
return
}
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("action", true, false, {
component: "Card",
action: action,
index: index
})
root.dispatchEvent(customEvent)
}
function dismissCard(sourceEvent) {
panel = sourceEvent.currentTarget.closest(".wire-next__card-panel")
if (panel) {
panel.setAttribute("hidden", "")
function dismissCard(sourceEvent, root, customEvent) {
dismissed = true
root = sourceEvent.currentTarget.closest("[data-wrn-card]")
if (!root) {
return
}
visible = false
$emit("dismiss", {
customEvent = document.createEvent("CustomEvent")
customEvent.initCustomEvent("dismiss", true, false, {
component: "Card",
title: title
})
root.dispatchEvent(customEvent)
}
}
view {
<section
<div
{...attrs}
class="wire-next wire-next--card {class}"
data-size="{size}"
data-color="{color}"
data-variant="{variant}"
data-layout="{layout}"
data-align="{align}"
data-hover="{hover}"
data-image-position="{imagePosition}"
data-group="{items.length > 0}"
aria-label="{ariaLabel || title}"
data-ui-component="Card"
data-wrn-card
data-size='{size}'
data-color='{color}'
data-variant='{variant}'
data-layout='{layout}'
data-hover='{hover}'
data-align='{align}'
class='wire-next wire-next--card wire-next--color-{color} wire-next--size-{size} wire-next--variant-{variant} {class}'
>
{#if items.length > 0}
<div class="wire-next__card-group">
{#each items as item}
<article class="wire-next__card-panel">
{#if item.imageSrc}
{#each items as item, itemIndex}
<article
class="wire-next__card-panel"
aria-label='{item.ariaLabel || item.title || item.label || "Card item"}'
data-index='{itemIndex}'
>
{#if item.imageSrc || item.image}
<div class="wire-next__card-media">
<img src="{item.imageSrc}" alt="{item.imageAlt || ''}" loading="lazy" />
<img
src='{item.imageSrc || item.image}'
alt='{item.imageAlt || item.alt || ""}'
loading='{item.loading || "lazy"}'
decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { item: item, index: itemIndex } }))'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { item: item, index: itemIndex } }))'
/>
</div>
{/if}
<div class="wire-next__card-body">
{#if item.title}<h3>{item.title}</h3>{/if}
{#if item.description}<p>{item.description}</p>{/if}
<div class="wire-next__card-content">
{#if item.title || item.label}
<h3>{item.title || item.label}</h3>
{/if}
{#if item.subtitle}
<p class="wire-next__card-subtitle">{item.subtitle}</p>
{/if}
{#if item.description}
<p class="wire-next__card-description">{item.description}</p>
{/if}
{#if item.actionLabel || item.href}
<a
href='{item.actionHref || item.href || "#"}'
class="wire-next__card-action"
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { item: item, index: itemIndex } }))'
>
<span>{item.actionLabel || "Learn more"}</span>
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
</a>
{/if}
</div>
{#if item.footer}<footer class="wire-next__card-footer">{item.footer}</footer>{/if}
</article>
{/each}
</div>
{:else if visible}
<article class="wire-next__card-panel">
{#if imagePosition === "overlay" && imageSrc}
<div class="wire-next__card-media wire-next__card-media--overlay">
{:else}
<article
class="wire-next__card-panel"
aria-label='{ariaLabel || title || "Card"}'
hidden='{dismissed}'
>
{#if imageSrc && imagePosition === "overlay"}
<div class="wire-next__card-overlay">
<img
src="{imageSrc}"
alt="{imageAlt}"
src='{imageSrc}'
alt='{imageAlt}'
loading="lazy"
@load="$emit('load', { src: imageSrc })"
@error="$emit('error', { src: imageSrc })"
decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
/>
<div class="wire-next__card-overlay">
{#if title}<h3>{title}</h3>{/if}
{#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
{#if description}<p>{description}</p>{/if}
{#if footer}<small>{footer}</small>{/if}
</div>
<div class="wire-next__card-overlay-shade" aria-hidden="true"></div>
</div>
{:else}
{#if header || headerActions.length > 0 || dismissible}
{/if}
{#if imageSrc && (imagePosition === "top" || imagePosition === "left")}
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
<img
src='{imageSrc}'
alt='{imageAlt}'
loading="lazy"
decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
/>
</div>
{/if}
<div class="wire-next__card-content">
{#if header || title || subtitle || headerActions.length > 0 || dismissible}
<header class="wire-next__card-header">
<span>{header}</span>
<div class="wire-next__card-heading">
{#if header}
<p class="wire-next__card-eyebrow">{header}</p>
{/if}
{#if title}
<h3>{title}</h3>
{/if}
{#if subtitle}
<p class="wire-next__card-subtitle">{subtitle}</p>
{/if}
</div>
{#if headerActions.length > 0 || dismissible}
<span class="wire-next__card-header-actions">
{#each headerActions as action, index}
<div class="wire-next__card-header-actions">
{#each headerActions as headerAction, actionIndex}
<button
type="button"
aria-label="{action.ariaLabel || action.label}"
title="{action.label}"
@click="chooseAction(action, index)"
aria-label='{headerAction.ariaLabel || headerAction.label || "Card action"}'
@click='dispatchCardHeaderAction(event, headerAction, actionIndex)'
>
{#if action.icon}<span class="{action.icon}" aria-hidden="true"></span>{/if}
{#if !action.icon}<span>{action.label}</span>{/if}
{#if headerAction.icon}
<span class='{headerAction.icon}' aria-hidden="true"></span>
{/if}
{#if headerAction.label && !headerAction.icon}
<span>{headerAction.label}</span>
{/if}
</button>
{/each}
{#if dismissible}
<button type="button" aria-label="Close card" @click="dismissCard(event)">
<button
type="button"
aria-label="Dismiss card"
@click='dismissCard(event)'
>
<span class="icon-[lucide--x]" aria-hidden="true"></span>
</button>
{/if}
</span>
</div>
{/if}
</header>
{/if}
{#if navigation.length > 0}
<nav class="wire-next__card-navigation" aria-label="{ariaLabel || title}">
<div class="wire-next__card-tabs">
{#each navigation as item, index}
<button
type="button"
data-active="{selectedNav === (item.value || item.label)}"
aria-current="{selectedNav === (item.value || item.label) ? 'page' : ''}"
@click="chooseNavigation(item, index)"
>
{item.label}
</button>
{/each}
</div>
<nav aria-label='{ariaLabel || title || "Card navigation"}' class="wire-next__card-navigation">
{#if mobileNavigation}
<select
class="wire-next__card-navigation-select"
aria-label="{ariaLabel || title}"
value="{selectedNav}"
@change="chooseMobileNavigation(event)"
aria-label='{ariaLabel || title || "Card navigation"}'
@change='dispatchCardNavigationValue(event)'
>
{#each navigation as item}
{#each navigation as navItem}
<option
value="{item.value || item.label}"
selected="{selectedNav === (item.value || item.label)}"
value='{navItem.value || navItem.href || navItem.label}'
selected='{activeNav === navItem.value || activeNav === navItem.href}'
>
{item.label}
{navItem.label}
</option>
{/each}
</select>
{/if}
<div class="wire-next__card-tabs" data-mobile-navigation='{mobileNavigation}'>
{#each navigation as navItem, navIndex}
<button
type="button"
aria-pressed='{activeNav === navItem.value || activeNav === navItem.href}'
@click='dispatchCardNavigation(event, navItem, navIndex)'
>
{#if navItem.icon}
<span class='{navItem.icon}' aria-hidden="true"></span>
{/if}
<span>{navItem.label}</span>
{#if navItem.badge}
<span class="wire-next__card-tab-badge">{navItem.badge}</span>
{/if}
</button>
{/each}
</div>
</nav>
{/if}
{#if imageSrc && imagePosition !== "bottom"}
<div class="wire-next__card-media">
<img
src="{imageSrc}"
alt="{imageAlt}"
loading="lazy"
@load="$emit('load', { src: imageSrc })"
@error="$emit('error', { src: imageSrc })"
/>
</div>
{/if}
{#if alertTitle || alertDescription}
<aside class="wire-next__card-alert" role="status">
{#if alertTitle}<strong>{alertTitle}</strong>{/if}
{#if alertDescription}<span>{alertDescription}</span>{/if}
</aside>
<div class="wire-next__card-alert" role="status">
{#if alertTitle}
<strong>{alertTitle}</strong>
{/if}
{#if alertDescription}
<p>{alertDescription}</p>
{/if}
</div>
{/if}
<div
class="wire-next__card-body"
data-scrollable="{scrollable}"
style="--wire-card-max-height: {maxHeight}"
data-scrollable='{scrollable}'
style='max-height: {scrollable ? maxHeight : "none"};'
>
{#if empty}
<div class="wire-next__card-empty">
<span class="{emptyIcon}" aria-hidden="true"></span>
<strong>{emptyTitle}</strong>
<span class='{emptyIcon}' aria-hidden="true"></span>
<p>{emptyTitle}</p>
</div>
{:else}
{#if title}<h3>{title}</h3>{/if}
{#if subtitle}<span class="wire-next__card-subtitle">{subtitle}</span>{/if}
{#if description}<p>{description}</p>{/if}
<slot />
{#if actionLabel}
<a
class="wire-next__card-action"
href="{actionHref || '#'}"
@click="$emit('action', { label: actionLabel, href: actionHref })"
>
{actionLabel}<span class="icon-[lucide--chevron-right]" aria-hidden="true"></span>
</a>
{#if description}
<p class="wire-next__card-description">{description}</p>
{/if}
<slot></slot>
{/if}
</div>
{#if footer}<footer class="wire-next__card-footer">{footer}</footer>{/if}
{#if imageSrc && imagePosition === "bottom"}
<div class="wire-next__card-media">
<img
src="{imageSrc}"
alt="{imageAlt}"
loading="lazy"
@load="$emit('load', { src: imageSrc })"
@error="$emit('error', { src: imageSrc })"
/>
</div>
{#if footer || actionLabel}
<footer class="wire-next__card-footer">
{#if footer}
<span>{footer}</span>
{/if}
{#if actionLabel}
<a
href='{actionHref || "#"}'
class="wire-next__card-action"
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { href: actionHref, label: actionLabel } }))'
>
<span>{actionLabel}</span>
<span class="icon-[lucide--arrow-right]" aria-hidden="true"></span>
</a>
{/if}
</footer>
{/if}
</div>
{#if imageSrc && (imagePosition === "bottom" || imagePosition === "right")}
<div class="wire-next__card-media wire-next__card-media--{imagePosition}">
<img
src='{imageSrc}'
alt='{imageAlt}'
loading="lazy"
decoding="async"
@load='event.currentTarget.dispatchEvent(new CustomEvent("load", { bubbles: true, detail: { src: imageSrc } }))'
@error='event.currentTarget.dispatchEvent(new CustomEvent("error", { bubbles: true, detail: { src: imageSrc } }))'
/>
</div>
{/if}
</article>
{/if}
</section>
</div>
}
}
+34 -1
View File
@@ -7,7 +7,40 @@ component Container {
maxWidth = "xl"
class = ""
}
view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--container wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
<div
data-ui-component="Container"
data-size='{size}'
data-color='{color}'
data-columns='{columns}'
data-gap='{gap}'
data-max-width='{maxWidth}'
class='mx-auto w-full {class}'
class:max-w-3xl='maxWidth === "md"'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl" || maxWidth === "wide"'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:px-4='size === "compact" || size === "default"'
class:px-5='size === "comfortable"'
class:px-6='size === "spacious"'
class:sm:px-6='size !== "compact"'
class:lg:px-8='size === "default" || size === "comfortable" || size === "spacious"'
class:grid='columns > 1'
class:grid-cols-1='columns > 1'
class:sm:grid-cols-2='columns === 2 || columns === 3 || columns === 4 || columns === 5 || columns === 6'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:lg:grid-cols-5='columns === 5'
class:lg:grid-cols-6='columns === 6'
class:gap-2='gap === "xs"'
class:gap-3='gap === "sm"'
class:gap-5='gap === "md"'
class:gap-8='gap === "lg"'
class:gap-10='gap === "xl"'
>
<slot></slot>
</div>
}
}
+58 -1
View File
@@ -6,7 +6,64 @@ component Divider {
orientation = "horizontal"
class = ""
}
view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--divider wire-next--divider-{orientation} {class}" role="separator" aria-orientation="{orientation}">{#if label}<span>{label}</span>{/if}</div>
<div
{...attrs}
data-ui-component="Divider"
data-size='{size}'
data-color='{color}'
data-orientation='{orientation}'
class='w-full {class}'
class:h-full='orientation === "vertical"'
class:w-auto='orientation === "vertical"'
>
{#if orientation === "vertical"}
<div
role="separator"
aria-orientation="vertical"
class="mx-3 h-full min-h-6 border-l border-[var(--wire-color-border)]"
class:border-l-2='size === "lg"'
class:border-[var(--wire-color-primary)]='color === "primary"'
class:border-[var(--wire-color-secondary)]='color === "secondary"'
class:border-[var(--wire-color-success)]='color === "success"'
class:border-[var(--wire-color-warning)]='color === "warning"'
class:border-[var(--wire-color-danger)]='color === "danger"'
></div>
{:else}
<div
role="separator"
aria-orientation="horizontal"
class="flex w-full items-center gap-4"
>
<span
class="h-px flex-1 bg-[var(--wire-color-border)]"
class:h-0.5='size === "lg"'
class:bg-[var(--wire-color-primary)]='color === "primary" && label !== ""'
class:bg-[var(--wire-color-secondary)]='color === "secondary" && label !== ""'
class:bg-[var(--wire-color-success)]='color === "success" && label !== ""'
class:bg-[var(--wire-color-warning)]='color === "warning" && label !== ""'
class:bg-[var(--wire-color-danger)]='color === "danger" && label !== ""'
aria-hidden="true"
></span>
{#if label}
<span class="shrink-0 text-xs font-bold uppercase tracking-[0.16em] text-[var(--wire-color-text-muted)]">
{label}
</span>
<span
class="h-px flex-1 bg-[var(--wire-color-border)]"
class:h-0.5='size === "lg"'
class:bg-[var(--wire-color-primary)]='color === "primary"'
class:bg-[var(--wire-color-secondary)]='color === "secondary"'
class:bg-[var(--wire-color-success)]='color === "success"'
class:bg-[var(--wire-color-warning)]='color === "warning"'
class:bg-[var(--wire-color-danger)]='color === "danger"'
aria-hidden="true"
></span>
{/if}
</div>
{/if}
</div>
}
}
+28 -1
View File
@@ -7,7 +7,34 @@ component Grid {
maxWidth = "xl"
class = ""
}
view {
<div class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--grid wire-next--gap-{gap} wire-next--columns-{columns} wire-next--max-{maxWidth} {class}"><slot /></div>
<div
data-ui-component="Grid"
data-size='{size}'
data-color='{color}'
data-columns='{columns}'
data-gap='{gap}'
class='grid w-full grid-cols-1 {class}'
class:max-w-3xl='maxWidth === "md"'
class:max-w-5xl='maxWidth === "lg"'
class:max-w-7xl='maxWidth === "xl"'
class:max-w-screen-2xl='maxWidth === "2xl"'
class:max-w-none='maxWidth === "full"'
class:sm:grid-cols-2='columns >= 2'
class:lg:grid-cols-3='columns === 3'
class:lg:grid-cols-4='columns === 4'
class:lg:grid-cols-5='columns === 5'
class:lg:grid-cols-6='columns === 6'
class:gap-2='gap === "xs"'
class:gap-3='gap === "sm"'
class:gap-5='gap === "md"'
class:gap-8='gap === "lg"'
class:gap-10='gap === "xl"'
class:items-start='size === "compact"'
class:items-stretch='size !== "compact"'
>
<slot></slot>
</div>
}
}