285 lines
11 KiB
Plaintext
285 lines
11 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 dismissed = false
|
|
|
|
view {
|
|
<div
|
|
{...attrs}
|
|
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, itemIndex}
|
|
<article
|
|
class="wire-next__card-panel"
|
|
aria-label='{ariaLabel || title || "Card"}'
|
|
hidden='{dismissed}'
|
|
>
|
|
{#if item.imageSrc || item.image}
|
|
<div class="wire-next__card-media">
|
|
<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-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>
|
|
</article>
|
|
{/each}
|
|
</div>
|
|
{: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}'
|
|
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 class="wire-next__card-overlay-shade" aria-hidden="true"></div>
|
|
</div>
|
|
{/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">
|
|
<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}
|
|
<div class="wire-next__card-header-actions">
|
|
{#each headerActions as headerAction, actionIndex}
|
|
<button
|
|
type="button"
|
|
aria-label='{headerAction.ariaLabel || headerAction.label || "Card action"}'
|
|
@click='event.currentTarget.dispatchEvent(new CustomEvent("action", { bubbles: true, detail: { action: headerAction, index: actionIndex } }))'
|
|
>
|
|
{#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="Dismiss card"
|
|
@click='event.currentTarget.dispatchEvent(new CustomEvent("dismiss", { bubbles: true, detail: { title: title } })); dismissed = true'
|
|
>
|
|
<span class="icon-[lucide--x]" aria-hidden="true"></span>
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</header>
|
|
{/if}
|
|
|
|
{#if navigation.length > 0}
|
|
<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 || "Card navigation"}'
|
|
@change='event.currentTarget.dispatchEvent(new CustomEvent("navigate", { bubbles: true, detail: { value: event.target.value } }))'
|
|
>
|
|
{#each navigation as navItem}
|
|
<option
|
|
value='{navItem.value || navItem.href || navItem.label}'
|
|
selected='{activeNav === navItem.value || activeNav === navItem.href}'
|
|
>
|
|
{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='event.currentTarget.dispatchEvent(new CustomEvent("navigate", { bubbles: true, detail: { item: navItem, index: 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 alertTitle || alertDescription}
|
|
<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='max-height: {scrollable ? maxHeight : "none"};'
|
|
>
|
|
{#if empty}
|
|
<div class="wire-next__card-empty">
|
|
<span class='{emptyIcon}' aria-hidden="true"></span>
|
|
<p>{emptyTitle}</p>
|
|
</div>
|
|
{:else}
|
|
{#if description}
|
|
<p class="wire-next__card-description">{description}</p>
|
|
{/if}
|
|
<slot></slot>
|
|
{/if}
|
|
</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}
|
|
</div>
|
|
}
|
|
}
|