TUI Renderer, Element Types, and Page Updates

This commit is contained in:
2025-11-30 18:26:56 -05:00
parent f09c198f17
commit 1167c686e2
31 changed files with 1642 additions and 809 deletions
+86 -24
View File
@@ -1,34 +1,55 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { getButtonStyle, parseColorText, getSegmentStyle } from './utils';
import { themeColors } from '$lib/stores/theme';
import type { TerminalLine } from './types';
import '$lib/assets/css/tui-accordion.css';
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";
export let line: TerminalLine;
let openItems: Set<number> = new Set(line.accordionOpen ? [0] : []);
function toggleItem(index: number) {
if (openItems.has(index)) {
openItems.delete(index);
} else {
openItems.add(index);
}
openItems = openItems; // trigger reactivity
interface Props {
line: AccordionLine;
inline?: boolean;
}
$: items = line.accordionItems || [{ title: line.content, content: '' }];
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" style="--accordion-accent: {getButtonStyle(line.style)}">
<div
class="tui-accordion"
class:inline
style="--accordion-accent: {getButtonStyle(line.style)}"
>
{#each items as item, i}
{@const contentSegments = parseColorText(item.content, $themeColors.colorMap)}
{@const contentSegments = parseColorText(
item.content,
$themeColors.colorMap,
)}
<div class="accordion-item" class:open={openItems.has(i)}>
<button class="accordion-header" on:click={() => toggleItem(i)}>
<Icon
icon={openItems.has(i) ? 'mdi:chevron-down' : 'mdi:chevron-right'}
width="16"
<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>
@@ -37,11 +58,33 @@
<div class="accordion-content">
{#each contentSegments as segment}
{#if getSegmentStyle(segment)}
<span style={getSegmentStyle(segment)}>{segment.text}</span>
<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>
@@ -108,6 +151,25 @@
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;