Website Redesign 7
This commit is contained in:
185
src/lib/components/tui/utils.ts
Normal file
185
src/lib/components/tui/utils.ts
Normal file
@@ -0,0 +1,185 @@
|
||||
// Shared utilities used by TUI components
|
||||
|
||||
export interface TextSegment {
|
||||
text: string;
|
||||
color?: string;
|
||||
background?: string;
|
||||
bold?: boolean;
|
||||
dim?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
strikethrough?: boolean;
|
||||
overline?: boolean;
|
||||
// For inline icons
|
||||
icon?: string;
|
||||
iconSize?: number;
|
||||
// For inline clickable text
|
||||
href?: string;
|
||||
action?: () => void;
|
||||
}
|
||||
|
||||
// Color map for text and background colors
|
||||
export const colorMap: Record<string, string> = {
|
||||
// Basic colors
|
||||
'red': '#f38ba8',
|
||||
'green': '#a6e3a1',
|
||||
'yellow': '#f9e2af',
|
||||
'blue': '#89b4fa',
|
||||
'magenta': '#cba6f7',
|
||||
'cyan': '#94e2d5',
|
||||
'white': '#cdd6f4',
|
||||
'gray': '#6c7086',
|
||||
'orange': '#fab387',
|
||||
'pink': '#f5c2e7',
|
||||
'black': '#1e1e2e',
|
||||
'surface': '#313244',
|
||||
// Semantic colors
|
||||
'primary': 'var(--terminal-primary)',
|
||||
'accent': 'var(--terminal-accent)',
|
||||
'muted': 'var(--terminal-muted)',
|
||||
'error': '#f38ba8',
|
||||
'success': '#a6e3a1',
|
||||
'warning': '#f9e2af',
|
||||
'info': '#89b4fa',
|
||||
};
|
||||
|
||||
// Text style keywords
|
||||
const textStyles = ['bold', 'dim', 'italic', 'underline', 'strikethrough', 'overline'];
|
||||
|
||||
export function parseColorText(text: string): TextSegment[] {
|
||||
const segments: TextSegment[] = [];
|
||||
// Match both (&specs)content(&) and (&icon, iconName) patterns
|
||||
const regex = /\(&([^)]+)\)(.*?)\(&\)|\(&icon,\s*([^)]+)\)/g;
|
||||
let lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
if (match.index > lastIndex) {
|
||||
segments.push({ text: text.slice(lastIndex, match.index) });
|
||||
}
|
||||
|
||||
// Check if this is an icon match (match[3] is the icon name)
|
||||
if (match[3]) {
|
||||
const iconName = match[3].trim();
|
||||
segments.push({ text: '', icon: iconName });
|
||||
lastIndex = match.index + match[0].length;
|
||||
continue;
|
||||
}
|
||||
|
||||
const specs = match[1].split(',').map(s => s.trim().toLowerCase());
|
||||
const content = match[2];
|
||||
const segment: TextSegment = { text: content };
|
||||
|
||||
for (const spec of specs) {
|
||||
// Text styles
|
||||
if (spec === 'bold') segment.bold = true;
|
||||
else if (spec === 'dim') segment.dim = true;
|
||||
else if (spec === 'italic') segment.italic = true;
|
||||
else if (spec === 'underline') segment.underline = true;
|
||||
else if (spec === 'strikethrough' || spec === 'strike') segment.strikethrough = true;
|
||||
else if (spec === 'overline') segment.overline = true;
|
||||
// Background color (bg-colorname or bg-#hex)
|
||||
else if (spec.startsWith('bg-')) {
|
||||
const bgColor = spec.slice(3);
|
||||
if (colorMap[bgColor]) {
|
||||
segment.background = colorMap[bgColor];
|
||||
} else if (bgColor.startsWith('#')) {
|
||||
segment.background = bgColor;
|
||||
}
|
||||
}
|
||||
// Foreground color
|
||||
else if (colorMap[spec] && !textStyles.includes(spec)) {
|
||||
segment.color = colorMap[spec];
|
||||
} else if (spec.startsWith('#')) {
|
||||
segment.color = spec;
|
||||
}
|
||||
}
|
||||
|
||||
segments.push(segment);
|
||||
lastIndex = match.index + match[0].length;
|
||||
}
|
||||
|
||||
if (lastIndex < text.length) {
|
||||
segments.push({ text: text.slice(lastIndex) });
|
||||
}
|
||||
|
||||
if (segments.length === 0) {
|
||||
segments.push({ text });
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
// Get plain text from segments (for length calculation)
|
||||
export function getPlainText(segments: TextSegment[]): string {
|
||||
return segments.map(s => s.text).join('');
|
||||
}
|
||||
|
||||
// Get segments up to a certain character count (for typing animation)
|
||||
export function getSegmentsUpToChar(segments: TextSegment[], charCount: number): TextSegment[] {
|
||||
const result: TextSegment[] = [];
|
||||
let remaining = charCount;
|
||||
|
||||
for (const segment of segments) {
|
||||
if (remaining <= 0) break;
|
||||
|
||||
if (segment.text.length <= remaining) {
|
||||
result.push(segment);
|
||||
remaining -= segment.text.length;
|
||||
} else {
|
||||
// Partial segment
|
||||
result.push({ ...segment, text: segment.text.slice(0, remaining) });
|
||||
remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function getSegmentStyle(segment: TextSegment): string {
|
||||
const styles: string[] = [];
|
||||
if (segment.color) styles.push(`color: ${segment.color}`);
|
||||
if (segment.background) styles.push(`background-color: ${segment.background}; padding: 0.1em 0.25em; border-radius: 3px`);
|
||||
if (segment.bold) styles.push('font-weight: bold');
|
||||
if (segment.dim) styles.push('opacity: 0.6');
|
||||
if (segment.italic) styles.push('font-style: italic');
|
||||
|
||||
// Combine text decorations
|
||||
const decorations: string[] = [];
|
||||
if (segment.underline) decorations.push('underline');
|
||||
if (segment.strikethrough) decorations.push('line-through');
|
||||
if (segment.overline) decorations.push('overline');
|
||||
if (decorations.length > 0) {
|
||||
styles.push(`text-decoration: ${decorations.join(' ')}`);
|
||||
}
|
||||
|
||||
return styles.join('; ');
|
||||
}
|
||||
|
||||
export function getLinePrefix(type: string): string {
|
||||
switch (type) {
|
||||
case 'error':
|
||||
return '✗ ';
|
||||
case 'success':
|
||||
return '✓ ';
|
||||
case 'info':
|
||||
return '› ';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
export function getButtonStyle(style?: string): string {
|
||||
switch (style) {
|
||||
case 'primary':
|
||||
return 'var(--terminal-primary)';
|
||||
case 'accent':
|
||||
return 'var(--terminal-accent)';
|
||||
case 'warning':
|
||||
return '#f9e2af';
|
||||
case 'error':
|
||||
return '#f38ba8';
|
||||
default:
|
||||
return 'var(--terminal-text)';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user