this repo has no description
1import type { ReactNode, CSSProperties } from 'react';
2import { Stack, Cluster, Divider } from './Layout';
3
4interface TerminalProps {
5 children: ReactNode;
6 title?: string;
7 subtitle?: string;
8 showBack?: boolean;
9 className?: string;
10}
11
12/**
13 * Terminal - main wrapper with brutalist monospace aesthetic
14 */
15export function Terminal({ children, title = '@toshi', subtitle, showBack = false, className = '' }: TerminalProps) {
16 const displayTitle = subtitle ? `${title} ${subtitle}` : title;
17
18 return (
19 <article className={`terminal ${className}`}>
20 <header className="terminal-header">
21 {showBack && <a href="/" className="back-link"><</a>}
22 <h1 className="terminal-title">{displayTitle}</h1>
23 </header>
24 <Divider />
25 <Stack gap="lg">{children}</Stack>
26 </article>
27 );
28}
29
30interface SectionProps {
31 children: ReactNode;
32 title?: string;
33}
34
35/**
36 * Section - grouped content with optional title
37 */
38export function Section({ children, title }: SectionProps) {
39 return (
40 <section className="terminal-section">
41 {title && <h2 className="section-title">{title}</h2>}
42 <Stack gap="sm">{children}</Stack>
43 </section>
44 );
45}
46
47interface LabelValueProps {
48 label: string;
49 value: ReactNode;
50 status?: 'default' | 'success' | 'error' | 'warning';
51}
52
53/**
54 * LabelValue - key-value display (e.g., "BALANCE: 1000")
55 */
56export function LabelValue({ label, value, status = 'default' }: LabelValueProps) {
57 return (
58 <div className="label-value">
59 <span className="label">{label}:</span>{' '}
60 <span className={`value status-${status}`}>{value}</span>
61 </div>
62 );
63}
64
65interface DataRowProps {
66 cells: ReactNode[];
67 widths?: string[];
68 href?: string;
69}
70
71/**
72 * DataRow - single row in a data table
73 */
74export function DataRow({ cells, widths, href }: DataRowProps) {
75 const content = (
76 <div className="data-row">
77 {cells.map((cell, i) => (
78 <span
79 key={i}
80 className="data-cell"
81 style={widths?.[i] ? { width: widths[i], flexShrink: 0 } : { flex: 1 }}
82 >
83 {cell}
84 </span>
85 ))}
86 </div>
87 );
88
89 if (href) {
90 return <a href={href} className="data-row-link">{content}</a>;
91 }
92
93 return content;
94}
95
96interface DataTableProps {
97 headers: string[];
98 widths?: string[];
99 children: ReactNode;
100 emptyMessage?: string;
101}
102
103/**
104 * DataTable - tabular data display
105 */
106export function DataTable({ headers, widths, children, emptyMessage = '(no data)' }: DataTableProps) {
107 const hasChildren = Array.isArray(children) ? children.length > 0 : !!children;
108
109 return (
110 <div className="data-table">
111 <div className="data-row data-header">
112 {headers.map((header, i) => (
113 <span
114 key={i}
115 className="data-cell"
116 style={widths?.[i] ? { width: widths[i], flexShrink: 0 } : { flex: 1 }}
117 >
118 {header}
119 </span>
120 ))}
121 </div>
122 <div className="data-body">
123 {hasChildren ? children : <div className="data-empty">{emptyMessage}</div>}
124 </div>
125 </div>
126 );
127}
128
129interface ActionBarProps {
130 children: ReactNode;
131}
132
133/**
134 * ActionBar - row of action buttons
135 */
136export function ActionBar({ children }: ActionBarProps) {
137 return (
138 <Cluster gap="sm" className="action-bar">
139 {children}
140 </Cluster>
141 );
142}
143
144interface StatusBadgeProps {
145 status: 'online' | 'offline' | 'loading';
146 label?: string;
147}
148
149/**
150 * StatusBadge - inline status indicator
151 */
152export function StatusBadge({ status, label }: StatusBadgeProps) {
153 const labels = {
154 online: 'ONLINE',
155 offline: 'OFFLINE',
156 loading: 'LOADING...',
157 };
158
159 return (
160 <span className={`status-badge status-${status}`}>
161 {label || labels[status]}
162 </span>
163 );
164}
165
166interface InputFieldProps {
167 label: string;
168 value: string;
169 onChange: (value: string) => void;
170 placeholder?: string;
171 type?: 'text' | 'number';
172 disabled?: boolean;
173 width?: string;
174 min?: number;
175 max?: number;
176}
177
178/**
179 * InputField - labeled input in terminal style
180 */
181export function InputField({
182 label,
183 value,
184 onChange,
185 placeholder,
186 type = 'text',
187 disabled = false,
188 width = '200px',
189 min,
190 max,
191}: InputFieldProps) {
192 return (
193 <div className="input-field">
194 <label className="input-label">{label}:</label>{' '}
195 <input
196 type={type}
197 value={value}
198 onChange={(e) => onChange(e.target.value)}
199 placeholder={placeholder}
200 disabled={disabled}
201 style={{ width }}
202 min={min}
203 max={max}
204 />
205 </div>
206 );
207}
208
209interface MessageProps {
210 type: 'success' | 'error' | 'info';
211 children: ReactNode;
212}
213
214/**
215 * Message - styled message display
216 */
217export function Message({ type, children }: MessageProps) {
218 return (
219 <div className={`message message-${type}`}>
220 {children}
221 </div>
222 );
223}