Website Status
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
import TuiRadio from './TuiRadio.svelte';
|
||||
import TuiSelect from './TuiSelect.svelte';
|
||||
import TuiToggle from './TuiToggle.svelte';
|
||||
import TuiGroup from './TuiGroup.svelte';
|
||||
import type { DisplayedLine } from './types';
|
||||
import '$lib/assets/css/tui-body.css';
|
||||
|
||||
@@ -85,6 +86,8 @@
|
||||
<TuiCheckbox {line} inline={true} />
|
||||
{:else if line.type === 'toggle'}
|
||||
<TuiToggle {line} inline={true} />
|
||||
{:else if line.type === 'group'}
|
||||
<TuiGroup {line} inline={true} {onButtonClick} {onHoverButton} {onLinkClick} />
|
||||
{:else if line.type === 'image' && showImage}
|
||||
<div class="tui-image inline-image">
|
||||
<img src={line.image} alt={line.imageAlt || 'Image'} style="max-width: {line.imageWidth || 300}px" />
|
||||
@@ -146,6 +149,8 @@
|
||||
<TuiSelect {line} />
|
||||
{:else if line.type === 'toggle'}
|
||||
<TuiToggle {line} />
|
||||
{:else if line.type === 'group'}
|
||||
<TuiGroup {line} {onButtonClick} {onHoverButton} {onLinkClick} />
|
||||
{:else}
|
||||
<div class="tui-line {line.type}" class:complete id={line.id}>
|
||||
{#if line.type === 'command' || line.type === 'prompt'}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { themeColors } from '$lib/stores/theme';
|
||||
import type { TerminalLine } from './types';
|
||||
import '$lib/assets/css/tui-card-grid.css';
|
||||
|
||||
@@ -8,7 +9,7 @@
|
||||
$: cards = line.cards || [];
|
||||
</script>
|
||||
|
||||
<div class="tui-card-grid">
|
||||
<div class="tui-card-grid" style="--card-warning: {$themeColors.colorMap.warning};">
|
||||
{#each cards as card}
|
||||
<article class="tui-card" class:featured={card.featured}>
|
||||
{#if card.image}
|
||||
@@ -67,7 +68,7 @@
|
||||
{/if}
|
||||
|
||||
{#if card.liveWarning}
|
||||
<div class="warning">⚠ Demo may be unavailable</div>
|
||||
<div class="warning">Demo may be unavailable</div>
|
||||
{/if}
|
||||
|
||||
<div class="card-actions">
|
||||
@@ -268,12 +269,25 @@
|
||||
} */
|
||||
|
||||
.warning {
|
||||
font-size: 0.6rem;
|
||||
color: #f9e2af;
|
||||
padding: 0.2rem 0.35rem;
|
||||
background: rgba(249, 226, 175, 0.1);
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--card-warning, #b8860b);
|
||||
padding: 0.3rem 0.6rem;
|
||||
background: color-mix(in srgb, var(--card-warning, #b8860b) 8%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--card-warning, #b8860b) 20%, transparent);
|
||||
border-radius: 6px;
|
||||
width: fit-content;
|
||||
font-weight: 600;
|
||||
box-shadow: 0 1px 0 rgba(0,0,0,0.02) inset;
|
||||
}
|
||||
|
||||
.warning:before {
|
||||
content: '⚠';
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.card-actions {
|
||||
|
||||
@@ -129,4 +129,13 @@
|
||||
.tui-checkbox.disabled .checkbox-label {
|
||||
color: var(--terminal-muted);
|
||||
}
|
||||
|
||||
/* Mobile: inline checkboxes become full-width */
|
||||
@media (max-width: 768px) {
|
||||
.tui-checkbox.inline {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0.35rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { getSegmentStyle, getLinePrefix, getSegmentsUpToChar, parseColorText, getPlainText } from './utils';
|
||||
import { user } from '$lib/config';
|
||||
import { themeColors } from '$lib/stores/theme';
|
||||
import TuiButton from './TuiButton.svelte';
|
||||
import TuiLink from './TuiLink.svelte';
|
||||
import TuiProgress from './TuiProgress.svelte';
|
||||
import TuiTooltip from './TuiTooltip.svelte';
|
||||
import TuiInput from './TuiInput.svelte';
|
||||
import TuiCheckbox from './TuiCheckbox.svelte';
|
||||
import TuiToggle from './TuiToggle.svelte';
|
||||
import type { TerminalLine } from './types';
|
||||
|
||||
interface Props {
|
||||
line: TerminalLine;
|
||||
inline?: boolean;
|
||||
onButtonClick?: (idx: number) => void;
|
||||
onHoverButton?: (idx: number) => void;
|
||||
onLinkClick?: (idx: number) => void;
|
||||
}
|
||||
|
||||
let {
|
||||
line,
|
||||
inline = false,
|
||||
onButtonClick = () => {},
|
||||
onHoverButton = () => {},
|
||||
onLinkClick = () => {}
|
||||
}: Props = $props();
|
||||
|
||||
// Get colorMap from current theme
|
||||
const colorMap = $derived($themeColors.colorMap);
|
||||
|
||||
// Parse children with current colorMap
|
||||
const parsedChildren = $derived(
|
||||
(line.children || []).map(child => {
|
||||
const segments = parseColorText(child.content, colorMap);
|
||||
return {
|
||||
line: child,
|
||||
segments,
|
||||
plainText: getPlainText(segments)
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Style for the group container
|
||||
const groupStyle = $derived(() => {
|
||||
const styles: string[] = [];
|
||||
if (line.groupDirection === 'column') {
|
||||
styles.push('flex-direction: column');
|
||||
}
|
||||
if (line.groupAlign) {
|
||||
const alignMap = { start: 'flex-start', center: 'center', end: 'flex-end' };
|
||||
styles.push(`align-items: ${alignMap[line.groupAlign] || 'flex-start'}`);
|
||||
}
|
||||
if (line.groupGap) {
|
||||
styles.push(`gap: ${line.groupGap}`);
|
||||
}
|
||||
return styles.join('; ');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="tui-group"
|
||||
class:inline
|
||||
style={groupStyle()}
|
||||
id={line.id}
|
||||
>
|
||||
{#each parsedChildren as parsed, idx}
|
||||
{@const child = parsed.line}
|
||||
{@const visibleSegments = parsed.segments}
|
||||
{@const childInline = child.inline !== false}
|
||||
|
||||
{#if child.type === 'image'}
|
||||
<div class="tui-image" class:inline-image={childInline}>
|
||||
<img
|
||||
src={child.image}
|
||||
alt={child.imageAlt || 'Image'}
|
||||
style="max-width: {child.imageWidth || 300}px"
|
||||
/>
|
||||
{#if child.content}
|
||||
<span class="image-caption">{child.content}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if child.type === 'button'}
|
||||
<TuiButton line={child} index={idx} selected={false} onClick={onButtonClick} onHover={onHoverButton} inline={childInline} />
|
||||
{:else if child.type === 'link'}
|
||||
<TuiLink line={child} onClick={() => onLinkClick(idx)} />
|
||||
{:else if child.type === 'tooltip'}
|
||||
<TuiTooltip line={child} />
|
||||
{:else if child.type === 'progress'}
|
||||
<TuiProgress line={child} inline={childInline} />
|
||||
{:else if child.type === 'input'}
|
||||
<TuiInput line={child} inline={childInline} />
|
||||
{:else if child.type === 'checkbox'}
|
||||
<TuiCheckbox line={child} inline={childInline} />
|
||||
{:else if child.type === 'toggle'}
|
||||
<TuiToggle line={child} inline={childInline} />
|
||||
{:else if child.type === 'header'}
|
||||
<span class="content header-text">
|
||||
<Icon icon="mdi:pound" width="14" class="header-icon" />
|
||||
{#each visibleSegments as segment}
|
||||
{#if segment.icon}
|
||||
<Icon icon={segment.icon} width="16" class="inline-icon" />
|
||||
{:else if getSegmentStyle(segment)}
|
||||
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
||||
{:else}
|
||||
{segment.text}
|
||||
{/if}
|
||||
{/each}
|
||||
</span>
|
||||
{:else if child.type === 'blank'}
|
||||
<!-- Empty for blank -->
|
||||
{:else if child.type === 'command' || child.type === 'prompt'}
|
||||
<span class="group-line {child.type}">
|
||||
<span class="prompt">
|
||||
<span class="user">{user.username}</span><span class="at">@</span><span class="host">{user.hostname}</span>
|
||||
<span class="separator">:</span><span class="path">~</span><span class="symbol">$</span>
|
||||
</span>
|
||||
<span class="content">
|
||||
{#each visibleSegments as segment}
|
||||
{#if segment.icon}
|
||||
<Icon icon={segment.icon} width="14" class="inline-icon" />
|
||||
{:else if getSegmentStyle(segment)}
|
||||
<span style={getSegmentStyle(segment)}>{segment.text}</span>
|
||||
{:else}
|
||||
{segment.text}
|
||||
{/if}
|
||||
{/each}
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="group-line {child.type}">
|
||||
{getLinePrefix(child.type)}{#each visibleSegments as segment}{#if segment.icon}<Icon icon={segment.icon} width="14" class="inline-icon" />{:else if getSegmentStyle(segment)}<span style={getSegmentStyle(segment)}>{segment.text}</span>{:else}{segment.text}{/if}{/each}
|
||||
</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tui-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
animation: lineSlideIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.tui-group.inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.group-line {
|
||||
display: block;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.group-line.output {
|
||||
color: var(--terminal-muted);
|
||||
}
|
||||
|
||||
.group-line.info {
|
||||
color: var(--terminal-primary);
|
||||
}
|
||||
|
||||
.group-line.success {
|
||||
color: #a6e3a1;
|
||||
}
|
||||
|
||||
.group-line.error {
|
||||
color: #f38ba8;
|
||||
}
|
||||
|
||||
.group-line.warning {
|
||||
color: #f9e2af;
|
||||
}
|
||||
|
||||
.group-line.command,
|
||||
.group-line.prompt {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prompt .user {
|
||||
color: var(--terminal-user);
|
||||
}
|
||||
|
||||
.prompt .at {
|
||||
color: var(--terminal-muted);
|
||||
}
|
||||
|
||||
.prompt .host {
|
||||
color: var(--terminal-accent);
|
||||
}
|
||||
|
||||
.prompt .separator {
|
||||
color: var(--terminal-muted);
|
||||
}
|
||||
|
||||
.prompt .path {
|
||||
color: var(--terminal-path);
|
||||
}
|
||||
|
||||
.prompt .symbol {
|
||||
color: var(--terminal-muted);
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
color: var(--terminal-accent);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tui-image {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tui-image img {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--terminal-border);
|
||||
background: var(--terminal-bg-light);
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.image-caption {
|
||||
color: var(--terminal-muted);
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@keyframes lineSlideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile: inline groups become vertical stacked */
|
||||
@media (max-width: 768px) {
|
||||
.tui-group.inline {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tui-group.inline .tui-image img {
|
||||
max-width: 100% !important;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -195,4 +195,13 @@
|
||||
color: #f38ba8;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Mobile: inline inputs become full-width */
|
||||
@media (max-width: 768px) {
|
||||
.tui-input.inline {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -156,4 +156,19 @@
|
||||
vertical-align: middle;
|
||||
margin: 0 0.15em;
|
||||
}
|
||||
|
||||
/* Mobile: inline progress becomes full-width */
|
||||
@media (max-width: 768px) {
|
||||
.tui-progress.inline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.tui-progress.inline .progress-bar {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -173,4 +173,13 @@
|
||||
color: var(--toggle-color);
|
||||
transform: translateX(0.5rem);
|
||||
}
|
||||
|
||||
/* Mobile: inline toggles become full-width */
|
||||
@media (max-width: 768px) {
|
||||
.tui-toggle.inline {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: 0.35rem 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,8 @@ export type LineType =
|
||||
| 'command' | 'output' | 'prompt' | 'error' | 'success' | 'info' | 'warning'
|
||||
| 'image' | 'blank' | 'header' | 'button' | 'divider' | 'link'
|
||||
| 'card' | 'progress' | 'accordion' | 'table' | 'tooltip' | 'cardgrid'
|
||||
| 'input' | 'textarea' | 'checkbox' | 'radio' | 'select' | 'toggle';
|
||||
| 'input' | 'textarea' | 'checkbox' | 'radio' | 'select' | 'toggle'
|
||||
| 'group';
|
||||
|
||||
// Option type for radio and select components
|
||||
export interface FormOption {
|
||||
@@ -117,6 +118,14 @@ export interface TerminalLine {
|
||||
toggleOnLabel?: string;
|
||||
toggleOffLabel?: string;
|
||||
toggleShowLabels?: boolean;
|
||||
// For group type - contains child lines rendered together
|
||||
children?: TerminalLine[];
|
||||
// Group layout direction
|
||||
groupDirection?: 'row' | 'column';
|
||||
// Group alignment
|
||||
groupAlign?: 'start' | 'center' | 'end';
|
||||
// Group gap
|
||||
groupGap?: string;
|
||||
}
|
||||
|
||||
// Pre-parsed line with segments ready for rendering
|
||||
|
||||
@@ -188,6 +188,8 @@ export function getLinePrefix(type: string): string {
|
||||
return '✗ ';
|
||||
case 'success':
|
||||
return '✓ ';
|
||||
case 'warning':
|
||||
return '⚠ ';
|
||||
case 'info':
|
||||
return '› ';
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user