Files
WRNexusJS/packages/ui/components/card.wrn
T
2026-07-29 12:51:10 +05:30

260 lines
8.6 KiB
Plaintext

component Card {
props {
@event click = function
@event action = function
@event navigate = function
@event dismiss = function
@event load = function
@event error = function
title = "Card title"
subtitle = ""
description = ""
header = ""
footer = ""
imageSrc = ""
imageAlt = ""
imagePosition = "top"
actionLabel = ""
actionHref = ""
headerActions = []
navigation = []
activeNav = ""
mobileNavigation = false
alertTitle = ""
alertDescription = ""
empty = false
emptyTitle = "No data to show"
emptyIcon = "icon-[lucide--inbox]"
items = []
size = "md"
color = "primary"
variant = "default"
layout = "vertical"
align = "left"
hover = "none"
scrollable = false
maxHeight = "18rem"
dismissible = false
ariaLabel = ""
class = ""
}
state visible = true
state selectedNav = activeNav
functions {
function chooseNavigation(item, index) {
previousValue = selectedNav
selectedNav = item.value || item.label
$emit("navigate", {
value: selectedNav,
previousValue: previousValue,
item: item,
index: index
})
}
function chooseMobileNavigation(sourceEvent) {
previousValue = selectedNav
selectedNav = sourceEvent.target.value
$emit("navigate", {
value: selectedNav,
previousValue: previousValue
})
}
function chooseAction(action, index) {
$emit("action", {
action: action,
index: index
})
}
function dismissCard(sourceEvent) {
panel = sourceEvent.currentTarget.closest(".wire-next__card-panel")
if (panel) {
panel.setAttribute("hidden", "")
}
visible = false
$emit("dismiss", {
title: title
})
}
}
view {
<section
{...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}"
>
{#if items.length > 0}
<div class="wire-next__card-group">
{#each items as item}
<article class="wire-next__card-panel">
{#if item.imageSrc}
<div class="wire-next__card-media">
<img src="{item.imageSrc}" alt="{item.imageAlt || ''}" loading="lazy" />
</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>
{#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">
<img
src="{imageSrc}"
alt="{imageAlt}"
loading="lazy"
@load="$emit('load', { src: imageSrc })"
@error="$emit('error', { 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>
{:else}
{#if header || headerActions.length > 0 || dismissible}
<header class="wire-next__card-header">
<span>{header}</span>
{#if headerActions.length > 0 || dismissible}
<span class="wire-next__card-header-actions">
{#each headerActions as action, index}
<button
type="button"
aria-label="{action.ariaLabel || action.label}"
title="{action.label}"
@click="chooseAction(action, index)"
>
{#if action.icon}<span class="{action.icon}" aria-hidden="true"></span>{/if}
{#if !action.icon}<span>{action.label}</span>{/if}
</button>
{/each}
{#if dismissible}
<button type="button" aria-label="Close card" @click="dismissCard(event)">
<span class="icon-[lucide--x]" aria-hidden="true"></span>
</button>
{/if}
</span>
{/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>
{#if mobileNavigation}
<select
class="wire-next__card-navigation-select"
aria-label="{ariaLabel || title}"
value="{selectedNav}"
@change="chooseMobileNavigation(event)"
>
{#each navigation as item}
<option
value="{item.value || item.label}"
selected="{selectedNav === (item.value || item.label)}"
>
{item.label}
</option>
{/each}
</select>
{/if}
</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>
{/if}
<div
class="wire-next__card-body"
data-scrollable="{scrollable}"
style="--wire-card-max-height: {maxHeight}"
>
{#if empty}
<div class="wire-next__card-empty">
<span class="{emptyIcon}" aria-hidden="true"></span>
<strong>{emptyTitle}</strong>
</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}
{/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}
{/if}
</article>
{/if}
</section>
}
}