264 lines
5.7 KiB
Svelte
264 lines
5.7 KiB
Svelte
<script lang="ts">
|
|
import Icon from "@iconify/svelte";
|
|
import {
|
|
getButtonStyle,
|
|
parseColorText,
|
|
getSegmentStyle,
|
|
parseDimension,
|
|
} from "./utils";
|
|
import type { CardLine, TerminalLine } from "./types";
|
|
import TuiLine from "./TuiLine.svelte";
|
|
import "$lib/assets/css/tui-card.css";
|
|
|
|
interface Props {
|
|
line: CardLine;
|
|
inline?: boolean;
|
|
onButtonClick?: (idx: number, line?: any) => void;
|
|
onHoverButton?: (idx: number) => void;
|
|
onLinkClick?: (idx: number) => void;
|
|
}
|
|
|
|
let {
|
|
line,
|
|
inline = false,
|
|
onButtonClick = () => {},
|
|
onHoverButton = () => {},
|
|
onLinkClick = () => {},
|
|
}: Props = $props();
|
|
|
|
const segments = $derived(parseColorText(line.content));
|
|
const titleSegments = $derived(
|
|
line.cardTitle ? parseColorText(line.cardTitle) : [],
|
|
);
|
|
const footerSegments = $derived(
|
|
line.cardFooter ? parseColorText(line.cardFooter) : [],
|
|
);
|
|
|
|
const cardStyle = $derived(
|
|
[
|
|
`--card-color: ${getButtonStyle(line.style)}`,
|
|
line.cardWidth ? `width: ${parseDimension(line.cardWidth)}` : "",
|
|
line.cardHeight ? `height: ${parseDimension(line.cardHeight)}` : "",
|
|
line.cardFloat === "start" ? "margin-right: auto" : "",
|
|
line.cardFloat === "center" ? "margin-inline: auto" : "",
|
|
line.cardFloat === "end" ? "margin-left: auto" : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join("; "),
|
|
);
|
|
const processedChildren = $derived.by(() => {
|
|
if (!line.children) return [];
|
|
const groups: Array<
|
|
| { kind: "single"; index: number; line: TerminalLine }
|
|
| {
|
|
kind: "inline";
|
|
items: Array<{ index: number; line: TerminalLine }>;
|
|
}
|
|
> = [];
|
|
let i = 0;
|
|
|
|
while (i < line.children.length) {
|
|
const child = line.children[i];
|
|
const isInline = child.inline || child.display === "inline";
|
|
|
|
if (isInline) {
|
|
const inlineItems: Array<{
|
|
index: number;
|
|
line: TerminalLine;
|
|
}> = [];
|
|
while (i < line.children.length) {
|
|
const nextChild = line.children[i];
|
|
const nextIsInline =
|
|
nextChild.inline || nextChild.display === "inline";
|
|
if (!nextIsInline) break;
|
|
|
|
inlineItems.push({ index: i, line: nextChild });
|
|
i++;
|
|
}
|
|
groups.push({ kind: "inline", items: inlineItems });
|
|
} else {
|
|
groups.push({ kind: "single", index: i, line: child });
|
|
i++;
|
|
}
|
|
}
|
|
return groups;
|
|
});
|
|
</script>
|
|
|
|
<div class="tui-card" class:inline style={cardStyle}>
|
|
{#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 processedChildren as group}
|
|
{#if group.kind === "inline"}
|
|
<div class="tui-inline-group">
|
|
{#each group.items as item}
|
|
<TuiLine
|
|
line={item.line}
|
|
index={item.index}
|
|
segments={parseColorText(item.line.content)}
|
|
complete={true}
|
|
showImage={true}
|
|
selectedIndex={-1}
|
|
inline={true}
|
|
{onButtonClick}
|
|
{onHoverButton}
|
|
{onLinkClick}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{:else}
|
|
<TuiLine
|
|
line={group.line}
|
|
index={group.index}
|
|
segments={parseColorText(group.line.content)}
|
|
complete={true}
|
|
showImage={true}
|
|
selectedIndex={-1}
|
|
inline={false}
|
|
{onButtonClick}
|
|
{onHoverButton}
|
|
{onLinkClick}
|
|
/>
|
|
{/if}
|
|
{/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 {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
padding: 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>
|