184 lines
3.8 KiB
Svelte
184 lines
3.8 KiB
Svelte
<script lang="ts">
|
|
import Icon from "@iconify/svelte";
|
|
import { getButtonStyle, parseColorText, getSegmentStyle } from "./utils";
|
|
import { themeColors } from "$lib/stores/theme";
|
|
import type { AccordionLine } from "./types";
|
|
import TuiLine from "./TuiLine.svelte";
|
|
import "$lib/assets/css/tui-accordion.css";
|
|
|
|
interface Props {
|
|
line: AccordionLine;
|
|
inline?: boolean;
|
|
}
|
|
|
|
let { line, inline = false }: Props = $props();
|
|
|
|
let openItems = $state(new Set(line.accordionOpen ? [0] : []));
|
|
|
|
function toggleItem(index: number) {
|
|
const newSet = new Set(openItems);
|
|
if (newSet.has(index)) {
|
|
newSet.delete(index);
|
|
} else {
|
|
newSet.add(index);
|
|
}
|
|
openItems = newSet;
|
|
}
|
|
|
|
const items = $derived(
|
|
line.accordionItems || [{ title: line.content, content: "" }],
|
|
);
|
|
const isSingleItemMode = $derived(
|
|
!line.accordionItems && line.children && line.children.length > 0,
|
|
);
|
|
</script>
|
|
|
|
<div
|
|
class="tui-accordion"
|
|
class:inline
|
|
style="--accordion-accent: {getButtonStyle(line.style)}"
|
|
>
|
|
{#each items as item, i}
|
|
{@const contentSegments = parseColorText(
|
|
item.content,
|
|
$themeColors.colorMap,
|
|
)}
|
|
<div class="accordion-item" class:open={openItems.has(i)}>
|
|
<button class="accordion-header" onclick={() => toggleItem(i)}>
|
|
<Icon
|
|
icon={openItems.has(i)
|
|
? "mdi:chevron-down"
|
|
: "mdi:chevron-right"}
|
|
width="16"
|
|
class="accordion-icon"
|
|
/>
|
|
<span class="accordion-title">{item.title}</span>
|
|
</button>
|
|
{#if openItems.has(i)}
|
|
<div class="accordion-content">
|
|
{#each contentSegments as segment}
|
|
{#if getSegmentStyle(segment)}
|
|
<span style={getSegmentStyle(segment)}
|
|
>{segment.text}</span
|
|
>
|
|
{:else}
|
|
{segment.text}
|
|
{/if}
|
|
{/each}
|
|
|
|
{#if isSingleItemMode && line.children}
|
|
<div
|
|
class="accordion-children"
|
|
class:flex={line.display === "flex"}
|
|
class:grid={line.display === "grid"}
|
|
>
|
|
{#each line.children as child, k}
|
|
<TuiLine
|
|
line={child}
|
|
index={k}
|
|
segments={parseColorText(child.content)}
|
|
complete={true}
|
|
showImage={true}
|
|
selectedIndex={-1}
|
|
inline={false}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<style>
|
|
.tui-accordion {
|
|
margin: 0.5rem 0;
|
|
border: 1px solid var(--terminal-border);
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.accordion-item {
|
|
border-bottom: 1px solid var(--terminal-border);
|
|
}
|
|
|
|
.accordion-item:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.accordion-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
padding: 0.6rem 0.75rem;
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--terminal-text);
|
|
font-family: inherit;
|
|
font-size: 0.85rem;
|
|
font-weight: 500;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease;
|
|
}
|
|
|
|
.accordion-header:hover {
|
|
background: rgba(255, 255, 255, 0.03);
|
|
}
|
|
|
|
.accordion-item.open .accordion-header {
|
|
background: rgba(0, 0, 0, 0.1);
|
|
border-bottom: 1px solid var(--terminal-border);
|
|
}
|
|
|
|
:global(.accordion-icon) {
|
|
color: var(--accordion-accent);
|
|
transition: transform 0.2s ease;
|
|
}
|
|
|
|
.accordion-title {
|
|
flex: 1;
|
|
}
|
|
|
|
.accordion-content {
|
|
padding: 0.75rem;
|
|
color: var(--terminal-muted);
|
|
font-size: 0.85rem;
|
|
line-height: 1.6;
|
|
white-space: pre-wrap;
|
|
animation: slideDown 0.2s ease-out;
|
|
}
|
|
|
|
.accordion-children {
|
|
margin-top: 0.5rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.accordion-children.flex {
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.accordion-children.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
@keyframes slideDown {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-5px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
</style>
|