@hauktui/registry 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/components/accordion/accordion.tsx +146 -0
  2. package/components/accordion/index.ts +2 -0
  3. package/components/alert/alert.tsx +69 -0
  4. package/components/alert/index.ts +2 -0
  5. package/components/alert-dialog/alert-dialog.tsx +185 -0
  6. package/components/alert-dialog/index.ts +2 -0
  7. package/components/avatar/avatar.tsx +57 -0
  8. package/components/avatar/index.ts +2 -0
  9. package/components/avatar-group/avatar-group.tsx +144 -0
  10. package/components/avatar-group/index.ts +2 -0
  11. package/components/badge/badge.tsx +52 -0
  12. package/components/badge/index.ts +2 -0
  13. package/components/banner/banner.tsx +407 -0
  14. package/components/banner/index.ts +2 -0
  15. package/components/breadcrumb/breadcrumb.tsx +58 -0
  16. package/components/breadcrumb/index.ts +2 -0
  17. package/components/button/button.tsx +114 -0
  18. package/components/button/index.ts +2 -0
  19. package/components/calendar/calendar.tsx +250 -0
  20. package/components/calendar/index.ts +2 -0
  21. package/components/card/card.tsx +88 -0
  22. package/components/card/index.ts +2 -0
  23. package/components/carousel/carousel.tsx +185 -0
  24. package/components/carousel/index.ts +2 -0
  25. package/components/chart/chart.tsx +189 -0
  26. package/components/chart/index.ts +2 -0
  27. package/components/checkbox/checkbox.tsx +98 -0
  28. package/components/checkbox/index.ts +2 -0
  29. package/components/code-block/code-block.tsx +214 -0
  30. package/components/code-block/index.ts +2 -0
  31. package/components/collapsible/collapsible.tsx +123 -0
  32. package/components/collapsible/index.ts +2 -0
  33. package/components/color-picker/color-picker.tsx +211 -0
  34. package/components/color-picker/index.ts +2 -0
  35. package/components/combobox/combobox.tsx +275 -0
  36. package/components/combobox/index.ts +2 -0
  37. package/components/command/command.tsx +304 -0
  38. package/components/command/index.ts +2 -0
  39. package/components/confirm-dialog/confirm-dialog.tsx +140 -0
  40. package/components/confirm-dialog/index.ts +2 -0
  41. package/components/context-menu/context-menu.tsx +188 -0
  42. package/components/context-menu/index.ts +2 -0
  43. package/components/countdown/countdown.tsx +165 -0
  44. package/components/countdown/index.ts +2 -0
  45. package/components/data-table/data-table.tsx +256 -0
  46. package/components/data-table/index.ts +2 -0
  47. package/components/date-picker/date-picker.tsx +280 -0
  48. package/components/date-picker/index.ts +2 -0
  49. package/components/dialog/dialog.tsx +84 -0
  50. package/components/dialog/index.ts +2 -0
  51. package/components/drawer/drawer.tsx +141 -0
  52. package/components/drawer/index.ts +2 -0
  53. package/components/dropdown-menu/dropdown-menu.tsx +188 -0
  54. package/components/dropdown-menu/index.ts +2 -0
  55. package/components/empty/empty.tsx +107 -0
  56. package/components/empty/index.ts +2 -0
  57. package/components/field/field.tsx +83 -0
  58. package/components/field/index.ts +2 -0
  59. package/components/form/form.tsx +202 -0
  60. package/components/form/index.ts +8 -0
  61. package/components/hover-card/hover-card.tsx +72 -0
  62. package/components/hover-card/index.ts +2 -0
  63. package/components/input-otp/index.ts +2 -0
  64. package/components/input-otp/input-otp.tsx +176 -0
  65. package/components/kbd/index.ts +2 -0
  66. package/components/kbd/kbd.tsx +30 -0
  67. package/components/label/index.ts +2 -0
  68. package/components/label/label.tsx +56 -0
  69. package/components/list/index.ts +2 -0
  70. package/components/list/list.tsx +247 -0
  71. package/components/menubar/index.ts +2 -0
  72. package/components/menubar/menubar.tsx +220 -0
  73. package/components/navigation-menu/index.ts +6 -0
  74. package/components/navigation-menu/navigation-menu.tsx +216 -0
  75. package/components/pagination/index.ts +2 -0
  76. package/components/pagination/pagination.tsx +158 -0
  77. package/components/password-input/index.ts +2 -0
  78. package/components/password-input/password-input.tsx +198 -0
  79. package/components/popover/index.ts +2 -0
  80. package/components/popover/popover.tsx +102 -0
  81. package/components/progress/index.ts +2 -0
  82. package/components/progress/progress.tsx +73 -0
  83. package/components/radio-group/index.ts +2 -0
  84. package/components/radio-group/radio-group.tsx +167 -0
  85. package/components/resizable/index.ts +2 -0
  86. package/components/resizable/resizable.tsx +141 -0
  87. package/components/scroll-area/index.ts +2 -0
  88. package/components/scroll-area/scroll-area.tsx +133 -0
  89. package/components/select/index.ts +2 -0
  90. package/components/select/select.tsx +185 -0
  91. package/components/separator/index.ts +2 -0
  92. package/components/separator/separator.tsx +63 -0
  93. package/components/sheet/index.ts +2 -0
  94. package/components/sheet/sheet.tsx +137 -0
  95. package/components/sidebar/index.ts +2 -0
  96. package/components/sidebar/sidebar.tsx +225 -0
  97. package/components/skeleton/index.ts +2 -0
  98. package/components/skeleton/skeleton.tsx +64 -0
  99. package/components/slider/index.ts +2 -0
  100. package/components/slider/slider.tsx +128 -0
  101. package/components/spinner/index.ts +2 -0
  102. package/components/spinner/spinner.tsx +57 -0
  103. package/components/stat/index.ts +2 -0
  104. package/components/stat/stat.tsx +138 -0
  105. package/components/stepper/index.ts +2 -0
  106. package/components/stepper/stepper.tsx +219 -0
  107. package/components/switch/index.ts +2 -0
  108. package/components/switch/switch.tsx +102 -0
  109. package/components/table/index.ts +2 -0
  110. package/components/table/table.tsx +242 -0
  111. package/components/tabs/index.ts +2 -0
  112. package/components/tabs/tabs.tsx +240 -0
  113. package/components/tag-input/index.ts +2 -0
  114. package/components/tag-input/tag-input.tsx +180 -0
  115. package/components/terminal/index.ts +2 -0
  116. package/components/terminal/terminal.tsx +162 -0
  117. package/components/text-input/index.ts +2 -0
  118. package/components/text-input/text-input.tsx +179 -0
  119. package/components/textarea/index.ts +2 -0
  120. package/components/textarea/textarea.tsx +206 -0
  121. package/components/timeline/index.ts +2 -0
  122. package/components/timeline/timeline.tsx +167 -0
  123. package/components/toast/index.ts +2 -0
  124. package/components/toast/toast.tsx +93 -0
  125. package/components/toggle/index.ts +2 -0
  126. package/components/toggle/toggle.tsx +114 -0
  127. package/components/toggle-group/index.ts +2 -0
  128. package/components/toggle-group/toggle-group.tsx +176 -0
  129. package/components/tooltip/index.ts +2 -0
  130. package/components/tooltip/tooltip.tsx +65 -0
  131. package/components/tree-view/index.ts +2 -0
  132. package/components/tree-view/tree-view.tsx +245 -0
  133. package/components/typography/index.ts +12 -0
  134. package/components/typography/typography.tsx +154 -0
  135. package/dist/index.d.ts +102 -0
  136. package/dist/index.js +938 -0
  137. package/dist/index.js.map +1 -0
  138. package/package.json +41 -0
  139. package/registry.json +923 -0
@@ -0,0 +1,240 @@
1
+ import React, { useState, useCallback } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import type { Tokens } from "@hauktui/tokens";
4
+ import { useTokens, useFocusable } from "@hauktui/primitives-ink";
5
+ import { stableId, clamp } from "@hauktui/core";
6
+
7
+ export interface Tab {
8
+ /** Tab key/identifier */
9
+ key: string;
10
+ /** Tab label */
11
+ label: string;
12
+ /** Whether this tab is disabled */
13
+ disabled?: boolean;
14
+ }
15
+
16
+ export interface TabsProps {
17
+ /** Available tabs */
18
+ tabs: Tab[];
19
+ /** Controlled active tab key */
20
+ activeKey?: string;
21
+ /** Default active tab for uncontrolled mode */
22
+ defaultActiveKey?: string;
23
+ /** Callback when tab changes */
24
+ onChange?: (key: string) => void;
25
+ /** Whether the tabs component is disabled */
26
+ disabled?: boolean;
27
+ /** Custom tokens override */
28
+ tokens?: Tokens;
29
+ /** Focus ID for focus management */
30
+ focusId?: string;
31
+ /** Tab orientation */
32
+ orientation?: "horizontal" | "vertical";
33
+ /** Tab style variant */
34
+ variant?: "default" | "boxed" | "underline";
35
+ }
36
+
37
+ export function Tabs({
38
+ tabs,
39
+ activeKey: controlledActiveKey,
40
+ defaultActiveKey,
41
+ onChange,
42
+ disabled = false,
43
+ tokens: propTokens,
44
+ focusId,
45
+ orientation = "horizontal",
46
+ variant = "default",
47
+ }: TabsProps): React.ReactElement {
48
+ const contextTokens = useTokens();
49
+ const tokens = propTokens ?? contextTokens;
50
+ const id = focusId ?? stableId("tabs");
51
+ const { isFocused } = useFocusable(id);
52
+
53
+ // Internal state for uncontrolled mode
54
+ const defaultKey = defaultActiveKey ?? tabs[0]?.key;
55
+ const [internalActiveKey, setInternalActiveKey] = useState<string | undefined>(defaultKey);
56
+ const isControlled = controlledActiveKey !== undefined;
57
+ const currentActiveKey = isControlled ? controlledActiveKey : internalActiveKey;
58
+
59
+ const activeIndex = tabs.findIndex((t) => t.key === currentActiveKey);
60
+
61
+ const selectTab = useCallback(
62
+ (key: string) => {
63
+ const tab = tabs.find((t) => t.key === key);
64
+ if (!tab || tab.disabled) return;
65
+
66
+ if (!isControlled) {
67
+ setInternalActiveKey(key);
68
+ }
69
+ onChange?.(key);
70
+ },
71
+ [tabs, isControlled, onChange]
72
+ );
73
+
74
+ const navigateTo = useCallback(
75
+ (direction: 1 | -1) => {
76
+ let nextIndex = activeIndex + direction;
77
+
78
+ // Skip disabled tabs
79
+ while (
80
+ nextIndex >= 0 &&
81
+ nextIndex < tabs.length &&
82
+ tabs[nextIndex]?.disabled
83
+ ) {
84
+ nextIndex += direction;
85
+ }
86
+
87
+ nextIndex = clamp(nextIndex, 0, tabs.length - 1);
88
+ const nextTab = tabs[nextIndex];
89
+ if (nextTab && !nextTab.disabled) {
90
+ selectTab(nextTab.key);
91
+ }
92
+ },
93
+ [activeIndex, tabs, selectTab]
94
+ );
95
+
96
+ // Handle keyboard navigation
97
+ useInput(
98
+ (input, key) => {
99
+ if (!isFocused || disabled) return;
100
+
101
+ const isHorizontal = orientation === "horizontal";
102
+
103
+ if (isHorizontal) {
104
+ if (key.leftArrow || input === "h") {
105
+ navigateTo(-1);
106
+ } else if (key.rightArrow || input === "l") {
107
+ navigateTo(1);
108
+ }
109
+ } else {
110
+ if (key.upArrow || input === "k") {
111
+ navigateTo(-1);
112
+ } else if (key.downArrow || input === "j") {
113
+ navigateTo(1);
114
+ }
115
+ }
116
+
117
+ // Number keys for quick access (1-9)
118
+ const num = parseInt(input, 10);
119
+ if (num >= 1 && num <= 9 && num <= tabs.length) {
120
+ const tab = tabs[num - 1];
121
+ if (tab && !tab.disabled) {
122
+ selectTab(tab.key);
123
+ }
124
+ }
125
+
126
+ // Home/End
127
+ if (input === "g" && key.shift === false) {
128
+ // Go to first tab
129
+ const firstEnabled = tabs.find((t) => !t.disabled);
130
+ if (firstEnabled) selectTab(firstEnabled.key);
131
+ } else if (input === "G") {
132
+ // Go to last tab
133
+ const lastEnabled = [...tabs].reverse().find((t) => !t.disabled);
134
+ if (lastEnabled) selectTab(lastEnabled.key);
135
+ }
136
+ },
137
+ { isActive: isFocused }
138
+ );
139
+
140
+ // Render a single tab
141
+ const renderTab = (tab: Tab, index: number) => {
142
+ const isActive = tab.key === currentActiveKey;
143
+ const isDisabled = tab.disabled;
144
+
145
+ let color = tokens.colors.fg;
146
+ if (isDisabled) {
147
+ color = tokens.colors.disabled;
148
+ } else if (isActive) {
149
+ color = tokens.colors.accent;
150
+ }
151
+
152
+ // Different styles based on variant
153
+ let prefix = "";
154
+ let suffix = "";
155
+
156
+ if (variant === "boxed") {
157
+ prefix = isActive ? "[" : " ";
158
+ suffix = isActive ? "]" : " ";
159
+ } else if (variant === "underline") {
160
+ suffix = isActive ? "─" : " ";
161
+ } else {
162
+ prefix = isActive ? "● " : "○ ";
163
+ }
164
+
165
+ const tabNumber = index + 1 <= 9 ? `${index + 1}.` : " ";
166
+
167
+ return React.createElement(
168
+ Box,
169
+ {
170
+ key: tab.key,
171
+ paddingX: orientation === "horizontal" ? 1 : 0,
172
+ paddingY: orientation === "vertical" ? 0 : 0,
173
+ },
174
+ React.createElement(
175
+ Text,
176
+ {
177
+ color,
178
+ bold: isActive && isFocused,
179
+ dimColor: isDisabled,
180
+ inverse: isActive && isFocused && variant !== "underline",
181
+ },
182
+ prefix,
183
+ React.createElement(Text, { color: tokens.colors.muted }, tabNumber, " "),
184
+ tab.label,
185
+ suffix
186
+ )
187
+ );
188
+ };
189
+
190
+ // Render underline for underline variant
191
+ const renderUnderline = () => {
192
+ if (variant !== "underline" || orientation !== "horizontal") return null;
193
+
194
+ return React.createElement(
195
+ Box,
196
+ null,
197
+ tabs.map((tab) => {
198
+ const isActive = tab.key === currentActiveKey;
199
+ const width = tab.label.length + 5; // account for number prefix and padding
200
+
201
+ return React.createElement(
202
+ Box,
203
+ { key: tab.key, paddingX: 1 },
204
+ React.createElement(
205
+ Text,
206
+ { color: isActive ? tokens.colors.accent : tokens.colors.border },
207
+ isActive ? "─".repeat(width) : " ".repeat(width)
208
+ )
209
+ );
210
+ })
211
+ );
212
+ };
213
+
214
+ return React.createElement(
215
+ Box,
216
+ {
217
+ flexDirection: "column",
218
+ borderStyle: isFocused ? "round" : "single",
219
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
220
+ paddingX: 1,
221
+ },
222
+ React.createElement(
223
+ Box,
224
+ {
225
+ flexDirection: orientation === "horizontal" ? "row" : "column",
226
+ gap: orientation === "horizontal" ? 0 : 0,
227
+ },
228
+ tabs.map(renderTab)
229
+ ),
230
+ renderUnderline(),
231
+ // Hint
232
+ isFocused
233
+ ? React.createElement(
234
+ Text,
235
+ { color: tokens.colors.muted, dimColor: true },
236
+ orientation === "horizontal" ? "←/→ or 1-9" : "↑/↓ or 1-9"
237
+ )
238
+ : null
239
+ );
240
+ }
@@ -0,0 +1,2 @@
1
+ export { TagInput } from "./tag-input.js";
2
+ export type { TagInputProps } from "./tag-input.js";
@@ -0,0 +1,180 @@
1
+ import React, { useState } from "react";
2
+ import { Box, Text, useInput } from "ink";
3
+ import type { Tokens } from "@hauktui/tokens";
4
+ import { useTokens, useFocusable } from "@hauktui/primitives-ink";
5
+ import { stableId } from "@hauktui/core";
6
+
7
+ export interface TagInputProps {
8
+ /** Current tags */
9
+ value: string[];
10
+ /** Callback when tags change */
11
+ onChange?: (tags: string[]) => void;
12
+ /** Placeholder text */
13
+ placeholder?: string;
14
+ /** Maximum number of tags */
15
+ maxTags?: number;
16
+ /** Allowed tag separator characters */
17
+ separators?: string[];
18
+ /** Custom tokens override */
19
+ tokens?: Tokens;
20
+ /** Focus ID for focus management */
21
+ focusId?: string;
22
+ /** Tag variant */
23
+ variant?: "default" | "outline";
24
+ }
25
+
26
+ export function TagInput({
27
+ value,
28
+ onChange,
29
+ placeholder = "Add tag...",
30
+ maxTags,
31
+ separators = [",", " ", "Enter"],
32
+ tokens: propTokens,
33
+ focusId,
34
+ variant = "default",
35
+ }: TagInputProps): React.ReactElement {
36
+ const contextTokens = useTokens();
37
+ const tokens = propTokens ?? contextTokens;
38
+ const id = focusId ?? stableId("tag-input");
39
+ const { isFocused } = useFocusable(id);
40
+
41
+ const [inputValue, setInputValue] = useState("");
42
+ const [focusedTagIndex, setFocusedTagIndex] = useState(-1); // -1 means input is focused
43
+
44
+ // Add a tag
45
+ const addTag = (tag: string) => {
46
+ const trimmed = tag.trim();
47
+ if (!trimmed) return;
48
+ if (value.includes(trimmed)) return;
49
+ if (maxTags && value.length >= maxTags) return;
50
+
51
+ onChange?.([...value, trimmed]);
52
+ setInputValue("");
53
+ };
54
+
55
+ // Remove a tag
56
+ const removeTag = (index: number) => {
57
+ const newTags = [...value];
58
+ newTags.splice(index, 1);
59
+ onChange?.(newTags);
60
+ setFocusedTagIndex(-1);
61
+ };
62
+
63
+ // Handle keyboard input
64
+ useInput(
65
+ (input, key) => {
66
+ if (!isFocused) return;
67
+
68
+ // If we're focused on a tag
69
+ if (focusedTagIndex >= 0) {
70
+ if (key.leftArrow || input === "h") {
71
+ setFocusedTagIndex((prev) => Math.max(0, prev - 1));
72
+ } else if (key.rightArrow || input === "l") {
73
+ if (focusedTagIndex < value.length - 1) {
74
+ setFocusedTagIndex((prev) => prev + 1);
75
+ } else {
76
+ setFocusedTagIndex(-1); // Go to input
77
+ }
78
+ } else if (key.backspace || key.delete || input === "x") {
79
+ removeTag(focusedTagIndex);
80
+ } else if (key.escape) {
81
+ setFocusedTagIndex(-1);
82
+ }
83
+ return;
84
+ }
85
+
86
+ // Input mode
87
+ if (key.backspace) {
88
+ if (inputValue === "" && value.length > 0) {
89
+ setFocusedTagIndex(value.length - 1);
90
+ } else {
91
+ setInputValue((prev) => prev.slice(0, -1));
92
+ }
93
+ } else if (key.return || (separators.includes(input) && inputValue)) {
94
+ addTag(inputValue);
95
+ } else if (key.leftArrow && inputValue === "" && value.length > 0) {
96
+ setFocusedTagIndex(value.length - 1);
97
+ } else if (input && !key.ctrl && !key.meta) {
98
+ if (!separators.includes(input)) {
99
+ setInputValue((prev) => prev + input);
100
+ }
101
+ }
102
+ },
103
+ { isActive: isFocused }
104
+ );
105
+
106
+ // Render a tag
107
+ const renderTag = (tag: string, index: number) => {
108
+ const isFocusedTag = focusedTagIndex === index;
109
+
110
+ return React.createElement(
111
+ Box,
112
+ {
113
+ key: index,
114
+ borderStyle: variant === "outline" ? "round" : undefined,
115
+ borderColor: isFocusedTag ? tokens.colors.danger : tokens.colors.accent,
116
+ paddingX: variant === "outline" ? 0 : undefined,
117
+ },
118
+ React.createElement(
119
+ Text,
120
+ {
121
+ backgroundColor:
122
+ variant === "default"
123
+ ? isFocusedTag
124
+ ? tokens.colors.danger
125
+ : tokens.colors.accent
126
+ : undefined,
127
+ color:
128
+ variant === "default"
129
+ ? tokens.colors.bg
130
+ : isFocusedTag
131
+ ? tokens.colors.danger
132
+ : tokens.colors.accent,
133
+ },
134
+ ` ${tag} `
135
+ ),
136
+ isFocusedTag &&
137
+ React.createElement(Text, { color: tokens.colors.danger }, "×")
138
+ );
139
+ };
140
+
141
+ return React.createElement(
142
+ Box,
143
+ {
144
+ flexDirection: "row",
145
+ flexWrap: "wrap",
146
+ gap: 1,
147
+ borderStyle: isFocused ? "round" : "single",
148
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
149
+ paddingX: 1,
150
+ paddingY: 0,
151
+ },
152
+ // Existing tags
153
+ ...value.map(renderTag),
154
+ // Input
155
+ React.createElement(
156
+ Box,
157
+ {},
158
+ React.createElement(
159
+ Text,
160
+ {
161
+ color:
162
+ inputValue || focusedTagIndex === -1
163
+ ? tokens.colors.fg
164
+ : tokens.colors.muted,
165
+ },
166
+ inputValue || (focusedTagIndex === -1 ? placeholder : "")
167
+ ),
168
+ isFocused &&
169
+ focusedTagIndex === -1 &&
170
+ React.createElement(Text, { color: tokens.colors.accent }, "█")
171
+ ),
172
+ // Max tags indicator
173
+ maxTags &&
174
+ React.createElement(
175
+ Text,
176
+ { color: tokens.colors.muted, dimColor: true },
177
+ ` (${value.length}/${maxTags})`
178
+ )
179
+ );
180
+ }
@@ -0,0 +1,2 @@
1
+ export { Terminal } from "./terminal.js";
2
+ export type { TerminalProps, TerminalLine } from "./terminal.js";
@@ -0,0 +1,162 @@
1
+ import React from "react";
2
+ import { Box, Text } from "ink";
3
+ import type { Tokens } from "@hauktui/tokens";
4
+ import { useTokens } from "@hauktui/primitives-ink";
5
+
6
+ export interface TerminalLine {
7
+ /** Line content */
8
+ content: string;
9
+ /** Line type */
10
+ type?: "command" | "output" | "error" | "success" | "info" | "warning";
11
+ /** Optional prefix/prompt */
12
+ prefix?: string;
13
+ /** Timestamp */
14
+ timestamp?: Date;
15
+ }
16
+
17
+ export interface TerminalProps {
18
+ /** Terminal lines */
19
+ lines: TerminalLine[];
20
+ /** Terminal title */
21
+ title?: string;
22
+ /** Default prompt prefix */
23
+ prompt?: string;
24
+ /** Show timestamps */
25
+ showTimestamps?: boolean;
26
+ /** Maximum lines to display (scrollable) */
27
+ maxLines?: number;
28
+ /** Custom tokens override */
29
+ tokens?: Tokens;
30
+ /** Terminal width */
31
+ width?: number;
32
+ }
33
+
34
+ export function Terminal({
35
+ lines,
36
+ title = "Terminal",
37
+ prompt = "$ ",
38
+ showTimestamps = false,
39
+ maxLines,
40
+ tokens: propTokens,
41
+ width,
42
+ }: TerminalProps): React.ReactElement {
43
+ const contextTokens = useTokens();
44
+ const tokens = propTokens ?? contextTokens;
45
+
46
+ // Limit lines if maxLines is set
47
+ const displayLines = maxLines ? lines.slice(-maxLines) : lines;
48
+
49
+ // Get color for line type
50
+ const getLineColor = (type: TerminalLine["type"]): string => {
51
+ switch (type) {
52
+ case "command":
53
+ return tokens.colors.accent;
54
+ case "error":
55
+ return tokens.colors.danger;
56
+ case "success":
57
+ return tokens.colors.success;
58
+ case "info":
59
+ return tokens.colors.info;
60
+ case "warning":
61
+ return tokens.colors.warning;
62
+ case "output":
63
+ default:
64
+ return tokens.colors.fg;
65
+ }
66
+ };
67
+
68
+ // Format timestamp
69
+ const formatTimestamp = (date: Date): string => {
70
+ return date.toLocaleTimeString("en-US", {
71
+ hour12: false,
72
+ hour: "2-digit",
73
+ minute: "2-digit",
74
+ second: "2-digit",
75
+ });
76
+ };
77
+
78
+ return React.createElement(
79
+ Box,
80
+ {
81
+ flexDirection: "column",
82
+ borderStyle: "round",
83
+ borderColor: tokens.colors.border,
84
+ ...(width ? { width } : {}),
85
+ },
86
+ // Title bar
87
+ React.createElement(
88
+ Box,
89
+ {
90
+ paddingX: 1,
91
+ justifyContent: "space-between",
92
+ borderStyle: "single",
93
+ borderTop: false,
94
+ borderLeft: false,
95
+ borderRight: false,
96
+ borderColor: tokens.colors.border,
97
+ },
98
+ React.createElement(
99
+ Box,
100
+ { gap: 1 },
101
+ React.createElement(Text, { color: tokens.colors.danger }, "●"),
102
+ React.createElement(Text, { color: tokens.colors.warning }, "●"),
103
+ React.createElement(Text, { color: tokens.colors.success }, "●")
104
+ ),
105
+ React.createElement(
106
+ Text,
107
+ { color: tokens.colors.muted, bold: true },
108
+ title
109
+ ),
110
+ React.createElement(Text, {}, " ") // Spacer for symmetry
111
+ ),
112
+ // Terminal content
113
+ React.createElement(
114
+ Box,
115
+ { flexDirection: "column", paddingX: 1, paddingY: 1 },
116
+ displayLines.length === 0
117
+ ? React.createElement(
118
+ Text,
119
+ { color: tokens.colors.muted },
120
+ prompt + "█"
121
+ )
122
+ : displayLines.map((line, index) => {
123
+ const prefix =
124
+ line.type === "command"
125
+ ? (line.prefix ?? prompt)
126
+ : (line.prefix ?? "");
127
+
128
+ return React.createElement(
129
+ Box,
130
+ { key: index, gap: 1 },
131
+ // Timestamp
132
+ showTimestamps &&
133
+ line.timestamp &&
134
+ React.createElement(
135
+ Text,
136
+ { color: tokens.colors.muted },
137
+ "[" + formatTimestamp(line.timestamp) + "]"
138
+ ),
139
+ // Prefix/prompt
140
+ prefix &&
141
+ React.createElement(
142
+ Text,
143
+ {
144
+ color:
145
+ line.type === "command"
146
+ ? tokens.colors.success
147
+ : tokens.colors.muted,
148
+ bold: line.type === "command",
149
+ },
150
+ prefix
151
+ ),
152
+ // Content
153
+ React.createElement(
154
+ Text,
155
+ { color: getLineColor(line.type) },
156
+ line.content
157
+ )
158
+ );
159
+ })
160
+ )
161
+ );
162
+ }
@@ -0,0 +1,2 @@
1
+ export { TextInput } from "./text-input.js";
2
+ export type { TextInputProps } from "./text-input.js";