182 lines
3.5 KiB
Svelte
182 lines
3.5 KiB
Svelte
<script lang="ts">
|
|
import Icon from "@iconify/svelte";
|
|
import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
|
|
import type { CardLine } from "./types";
|
|
import TuiLine from "./TuiLine.svelte";
|
|
import "$lib/assets/css/tui-card.css";
|
|
|
|
interface Props {
|
|
line: CardLine;
|
|
inline?: boolean;
|
|
}
|
|
|
|
let { line, inline = false }: Props = $props();
|
|
|
|
const segments = $derived(parseColorText(line.content));
|
|
const titleSegments = $derived(
|
|
line.cardTitle ? parseColorText(line.cardTitle) : [],
|
|
);
|
|
const footerSegments = $derived(
|
|
line.cardFooter ? parseColorText(line.cardFooter) : [],
|
|
);
|
|
</script>
|
|
|
|
<div
|
|
class="tui-card"
|
|
class:inline
|
|
style="--card-color: {getButtonStyle(line.style)}"
|
|
>
|
|
{#if line.cardTitle}
|
|
<div class="card-header">
|
|
{#if line.icon}
|
|
<Icon icon={line.icon} width="16" class="card-icon" />
|
|
{/if}
|
|
<span class="card-title">
|
|
{#each titleSegments as segment}
|
|
{#if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}
|
|
>{segment.text}</span
|
|
>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="card-body">
|
|
{#if line.image}
|
|
<img
|
|
src={line.image}
|
|
alt={line.imageAlt || ""}
|
|
class="card-image"
|
|
/>
|
|
{/if}
|
|
<div class="card-content">
|
|
{#each segments as segment}
|
|
{#if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
{#if line.children && line.children.length > 0}
|
|
<div
|
|
class="card-children"
|
|
class:flex={line.display === "flex"}
|
|
class:grid={line.display === "grid"}
|
|
>
|
|
{#each line.children as child, i}
|
|
<TuiLine
|
|
line={child}
|
|
index={i}
|
|
segments={parseColorText(child.content)}
|
|
complete={true}
|
|
showImage={true}
|
|
selectedIndex={-1}
|
|
inline={false}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
{#if line.cardFooter}
|
|
<div class="card-footer">
|
|
{#each footerSegments as segment}
|
|
{#if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.tui-card {
|
|
border: 1px solid var(--terminal-border);
|
|
border-radius: 6px;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--terminal-bg) 80%,
|
|
var(--card-accent) 5%
|
|
);
|
|
margin: 0.5rem 0;
|
|
overflow: hidden;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
.tui-card:hover {
|
|
border-color: var(--card-accent);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.6rem 0.75rem;
|
|
border-bottom: 1px solid var(--terminal-border);
|
|
background: rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
:global(.card-icon) {
|
|
color: var(--card-accent);
|
|
}
|
|
|
|
.card-title {
|
|
font-weight: 600;
|
|
color: var(--terminal-text);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 0.75rem;
|
|
}
|
|
|
|
.card-image {
|
|
width: 100%;
|
|
max-height: 200px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.card-content {
|
|
color: var(--terminal-muted);
|
|
font-size: 0.85rem;
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.card-children {
|
|
margin-top: 0.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.card-children.flex {
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.card-children.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.card-footer {
|
|
padding: 0.5rem 0.75rem;
|
|
border-top: 1px solid var(--terminal-border);
|
|
font-size: 0.75rem;
|
|
color: var(--terminal-muted);
|
|
background: rgba(0, 0, 0, 0.05);
|
|
}
|
|
</style>
|