@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,114 @@
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 } from "@hauktui/core";
6
+
7
+ export interface ToggleProps {
8
+ /** Whether the toggle is on (controlled) */
9
+ value?: boolean;
10
+ /** Default value (uncontrolled) */
11
+ defaultValue?: boolean;
12
+ /** Callback when value changes */
13
+ onChange?: (value: boolean) => void;
14
+ /** Toggle label */
15
+ label?: string;
16
+ /** Whether the toggle is disabled */
17
+ disabled?: boolean;
18
+ /** Custom tokens override */
19
+ tokens?: Tokens;
20
+ /** Focus ID for focus management */
21
+ focusId?: string;
22
+ /** On label */
23
+ onLabel?: string;
24
+ /** Off label */
25
+ offLabel?: string;
26
+ }
27
+
28
+ export function Toggle({
29
+ value: controlledValue,
30
+ defaultValue = false,
31
+ onChange,
32
+ label,
33
+ disabled = false,
34
+ tokens: propTokens,
35
+ focusId,
36
+ onLabel = "ON",
37
+ offLabel = "OFF",
38
+ }: ToggleProps): React.ReactElement {
39
+ const contextTokens = useTokens();
40
+ const tokens = propTokens ?? contextTokens;
41
+ const id = focusId ?? stableId("toggle");
42
+ const { isFocused } = useFocusable(id);
43
+
44
+ const [internalValue, setInternalValue] = useState(defaultValue);
45
+ const isControlled = controlledValue !== undefined;
46
+ const isOn = isControlled ? controlledValue : internalValue;
47
+
48
+ const toggle = useCallback(() => {
49
+ if (disabled) return;
50
+ const newValue = !isOn;
51
+ if (!isControlled) {
52
+ setInternalValue(newValue);
53
+ }
54
+ onChange?.(newValue);
55
+ }, [disabled, isOn, isControlled, onChange]);
56
+
57
+ useInput(
58
+ (input, key) => {
59
+ if (!isFocused || disabled) return;
60
+ if (key.return || input === " ") {
61
+ toggle();
62
+ }
63
+ },
64
+ { isActive: isFocused }
65
+ );
66
+
67
+ const trackColor = disabled
68
+ ? tokens.colors.disabled
69
+ : isOn
70
+ ? tokens.colors.success
71
+ : tokens.colors.border;
72
+
73
+ const thumbPosition = isOn ? " ●" : "● ";
74
+
75
+ return React.createElement(
76
+ Box,
77
+ { gap: 1 },
78
+ label
79
+ ? React.createElement(
80
+ Text,
81
+ {
82
+ color: disabled ? tokens.colors.disabled : tokens.colors.fg,
83
+ bold: isFocused,
84
+ },
85
+ label
86
+ )
87
+ : null,
88
+ React.createElement(
89
+ Box,
90
+ {
91
+ borderStyle: "round",
92
+ borderColor: isFocused ? tokens.colors.focus : trackColor,
93
+ paddingX: 0,
94
+ },
95
+ React.createElement(
96
+ Text,
97
+ { color: trackColor, bold: true },
98
+ thumbPosition
99
+ )
100
+ ),
101
+ React.createElement(
102
+ Text,
103
+ {
104
+ color: disabled
105
+ ? tokens.colors.disabled
106
+ : isOn
107
+ ? tokens.colors.success
108
+ : tokens.colors.muted,
109
+ dimColor: disabled,
110
+ },
111
+ isOn ? onLabel : offLabel
112
+ )
113
+ );
114
+ }
@@ -0,0 +1,2 @@
1
+ export { ToggleGroup } from "./toggle-group.js";
2
+ export type { ToggleGroupProps, ToggleGroupItem } from "./toggle-group.js";
@@ -0,0 +1,176 @@
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 ToggleGroupItem {
8
+ /** Unique value */
9
+ value: string;
10
+ /** Display label */
11
+ label: string;
12
+ /** Whether this item is disabled */
13
+ disabled?: boolean;
14
+ }
15
+
16
+ export interface ToggleGroupProps {
17
+ /** Available toggle items */
18
+ items: ToggleGroupItem[];
19
+ /** Controlled selected value(s) */
20
+ value?: string | string[];
21
+ /** Default value for uncontrolled mode */
22
+ defaultValue?: string | string[];
23
+ /** Callback when selection changes */
24
+ onChange?: (value: string | string[]) => void;
25
+ /** Allow multiple selection */
26
+ multiple?: boolean;
27
+ /** Whether the toggle group is disabled */
28
+ disabled?: boolean;
29
+ /** Custom tokens override */
30
+ tokens?: Tokens;
31
+ /** Focus ID for focus management */
32
+ focusId?: string;
33
+ /** Orientation */
34
+ orientation?: "horizontal" | "vertical";
35
+ /** Variant style */
36
+ variant?: "default" | "outline";
37
+ }
38
+
39
+ export function ToggleGroup({
40
+ items,
41
+ value: controlledValue,
42
+ defaultValue = [],
43
+ onChange,
44
+ multiple = false,
45
+ disabled = false,
46
+ tokens: propTokens,
47
+ focusId,
48
+ orientation = "horizontal",
49
+ variant = "default",
50
+ }: ToggleGroupProps): React.ReactElement {
51
+ const contextTokens = useTokens();
52
+ const tokens = propTokens ?? contextTokens;
53
+ const id = focusId ?? stableId("toggle-group");
54
+ const { isFocused } = useFocusable(id);
55
+
56
+ const normalizeValue = (v: string | string[] | undefined): string[] => {
57
+ if (v === undefined) return [];
58
+ return Array.isArray(v) ? v : [v];
59
+ };
60
+
61
+ const [internalValue, setInternalValue] = useState<string[]>(
62
+ normalizeValue(defaultValue)
63
+ );
64
+ const [highlightedIndex, setHighlightedIndex] = useState(0);
65
+
66
+ const isControlled = controlledValue !== undefined;
67
+ const currentValue = normalizeValue(
68
+ isControlled ? controlledValue : internalValue
69
+ );
70
+
71
+ const toggleItem = useCallback(
72
+ (itemValue: string) => {
73
+ const item = items.find((i) => i.value === itemValue);
74
+ if (!item || item.disabled || disabled) return;
75
+
76
+ let newValue: string[];
77
+ if (multiple) {
78
+ if (currentValue.includes(itemValue)) {
79
+ newValue = currentValue.filter((v) => v !== itemValue);
80
+ } else {
81
+ newValue = [...currentValue, itemValue];
82
+ }
83
+ } else {
84
+ newValue = currentValue.includes(itemValue) ? [] : [itemValue];
85
+ }
86
+
87
+ if (!isControlled) {
88
+ setInternalValue(newValue);
89
+ }
90
+ onChange?.(multiple ? newValue : newValue[0] || "");
91
+ },
92
+ [items, currentValue, multiple, disabled, isControlled, onChange]
93
+ );
94
+
95
+ useInput(
96
+ (input, key) => {
97
+ if (!isFocused || disabled) return;
98
+
99
+ const isHorizontal = orientation === "horizontal";
100
+
101
+ if (
102
+ (isHorizontal && key.leftArrow) ||
103
+ (!isHorizontal && key.upArrow) ||
104
+ input === "h" ||
105
+ input === "k"
106
+ ) {
107
+ setHighlightedIndex((prev) => clamp(prev - 1, 0, items.length - 1));
108
+ } else if (
109
+ (isHorizontal && key.rightArrow) ||
110
+ (!isHorizontal && key.downArrow) ||
111
+ input === "l" ||
112
+ input === "j"
113
+ ) {
114
+ setHighlightedIndex((prev) => clamp(prev + 1, 0, items.length - 1));
115
+ } else if (key.return || input === " ") {
116
+ const item = items[highlightedIndex];
117
+ if (item) {
118
+ toggleItem(item.value);
119
+ }
120
+ }
121
+ },
122
+ { isActive: isFocused }
123
+ );
124
+
125
+ return React.createElement(
126
+ Box,
127
+ {
128
+ flexDirection: orientation === "horizontal" ? "row" : "column",
129
+ borderStyle: isFocused ? "round" : "single",
130
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
131
+ },
132
+ items.map((item, index) => {
133
+ const isSelected = currentValue.includes(item.value);
134
+ const isHighlighted = index === highlightedIndex && isFocused;
135
+ const isDisabled = item.disabled || disabled;
136
+
137
+ let bgColor: string | undefined;
138
+ let fgColor = tokens.colors.fg;
139
+
140
+ if (isDisabled) {
141
+ fgColor = tokens.colors.disabled;
142
+ } else if (isSelected) {
143
+ bgColor = tokens.colors.accent;
144
+ fgColor = tokens.colors.bg;
145
+ } else if (isHighlighted) {
146
+ fgColor = tokens.colors.accent;
147
+ }
148
+
149
+ return React.createElement(
150
+ Box,
151
+ {
152
+ key: item.value,
153
+ paddingX: 1,
154
+ borderStyle: variant === "outline" ? "single" : undefined,
155
+ borderColor:
156
+ variant === "outline"
157
+ ? isSelected
158
+ ? tokens.colors.accent
159
+ : tokens.colors.border
160
+ : undefined,
161
+ },
162
+ React.createElement(
163
+ Text,
164
+ {
165
+ backgroundColor: bgColor,
166
+ color: fgColor,
167
+ bold: isSelected || isHighlighted,
168
+ inverse: isHighlighted && !isSelected,
169
+ dimColor: isDisabled,
170
+ },
171
+ item.label
172
+ )
173
+ );
174
+ })
175
+ );
176
+ }
@@ -0,0 +1,2 @@
1
+ export { Tooltip } from "./tooltip.js";
2
+ export type { TooltipProps } from "./tooltip.js";
@@ -0,0 +1,65 @@
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 TooltipProps {
7
+ /** The content to show in the tooltip */
8
+ content: string;
9
+ /** The element that triggers the tooltip */
10
+ children: React.ReactNode;
11
+ /** Whether the tooltip is visible */
12
+ visible?: boolean;
13
+ /** Tooltip position */
14
+ position?: "top" | "bottom" | "left" | "right";
15
+ /** Custom tokens override */
16
+ tokens?: Tokens;
17
+ }
18
+
19
+ export function Tooltip({
20
+ content,
21
+ children,
22
+ visible = false,
23
+ position = "top",
24
+ tokens: propTokens,
25
+ }: TooltipProps): React.ReactElement {
26
+ const contextTokens = useTokens();
27
+ const tokens = propTokens ?? contextTokens;
28
+
29
+ const tooltipElement = React.createElement(
30
+ Box,
31
+ {
32
+ borderStyle: "round",
33
+ borderColor: tokens.colors.border,
34
+ paddingX: 1,
35
+ backgroundColor: tokens.colors.bg,
36
+ },
37
+ React.createElement(Text, { color: tokens.colors.fg }, content)
38
+ );
39
+
40
+ if (!visible) {
41
+ return React.createElement(Box, null, children);
42
+ }
43
+
44
+ const containerProps: Record<string, unknown> = {
45
+ flexDirection:
46
+ position === "top" || position === "bottom" ? "column" : "row",
47
+ alignItems: "center",
48
+ gap: 0,
49
+ };
50
+
51
+ if (position === "top") {
52
+ return React.createElement(Box, containerProps, tooltipElement, children);
53
+ }
54
+
55
+ if (position === "bottom") {
56
+ return React.createElement(Box, containerProps, children, tooltipElement);
57
+ }
58
+
59
+ if (position === "left") {
60
+ return React.createElement(Box, containerProps, tooltipElement, children);
61
+ }
62
+
63
+ // right
64
+ return React.createElement(Box, containerProps, children, tooltipElement);
65
+ }
@@ -0,0 +1,2 @@
1
+ export { TreeView } from "./tree-view.js";
2
+ export type { TreeViewProps, TreeNode } from "./tree-view.js";
@@ -0,0 +1,245 @@
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 } from "@hauktui/core";
6
+
7
+ export interface TreeNode {
8
+ /** Unique identifier */
9
+ id: string;
10
+ /** Display label */
11
+ label: string;
12
+ /** Icon to display (emoji or character) */
13
+ icon?: string;
14
+ /** Child nodes */
15
+ children?: TreeNode[];
16
+ /** Whether the node is disabled */
17
+ disabled?: boolean;
18
+ }
19
+
20
+ export interface TreeViewProps {
21
+ /** Tree data structure */
22
+ data: TreeNode[];
23
+ /** Currently selected node ID */
24
+ selectedId?: string;
25
+ /** Callback when a node is selected */
26
+ onSelect?: (node: TreeNode) => void;
27
+ /** Initially expanded node IDs */
28
+ defaultExpanded?: string[];
29
+ /** Custom tokens override */
30
+ tokens?: Tokens;
31
+ /** Focus ID for focus management */
32
+ focusId?: string;
33
+ /** Show connecting lines */
34
+ showLines?: boolean;
35
+ /** Indent size in characters */
36
+ indentSize?: number;
37
+ }
38
+
39
+ interface FlatNode {
40
+ node: TreeNode;
41
+ depth: number;
42
+ isExpanded: boolean;
43
+ hasChildren: boolean;
44
+ isLast: boolean;
45
+ parentIsLast: boolean[];
46
+ }
47
+
48
+ export function TreeView({
49
+ data,
50
+ selectedId,
51
+ onSelect,
52
+ defaultExpanded = [],
53
+ tokens: propTokens,
54
+ focusId,
55
+ showLines = true,
56
+ indentSize = 2,
57
+ }: TreeViewProps): React.ReactElement {
58
+ const contextTokens = useTokens();
59
+ const tokens = propTokens ?? contextTokens;
60
+ const id = focusId ?? stableId("tree-view");
61
+ const { isFocused } = useFocusable(id);
62
+
63
+ const [expanded, setExpanded] = useState<Set<string>>(
64
+ new Set(defaultExpanded)
65
+ );
66
+ const [focusedIndex, setFocusedIndex] = useState(0);
67
+
68
+ // Flatten tree for rendering
69
+ const flattenTree = useCallback(
70
+ (
71
+ nodes: TreeNode[],
72
+ depth = 0,
73
+ parentIsLast: boolean[] = []
74
+ ): FlatNode[] => {
75
+ const result: FlatNode[] = [];
76
+
77
+ nodes.forEach((node, index) => {
78
+ const isLast = index === nodes.length - 1;
79
+ const hasChildren = !!node.children && node.children.length > 0;
80
+ const isExpanded = expanded.has(node.id);
81
+
82
+ result.push({
83
+ node,
84
+ depth,
85
+ isExpanded,
86
+ hasChildren,
87
+ isLast,
88
+ parentIsLast,
89
+ });
90
+
91
+ if (hasChildren && isExpanded) {
92
+ result.push(
93
+ ...flattenTree(node.children!, depth + 1, [...parentIsLast, isLast])
94
+ );
95
+ }
96
+ });
97
+
98
+ return result;
99
+ },
100
+ [expanded]
101
+ );
102
+
103
+ const flatNodes = flattenTree(data);
104
+
105
+ // Handle keyboard input
106
+ useInput(
107
+ (input, key) => {
108
+ if (!isFocused) return;
109
+
110
+ const currentNode = flatNodes[focusedIndex];
111
+ if (!currentNode) return;
112
+
113
+ if (key.upArrow || input === "k") {
114
+ setFocusedIndex((prev) => Math.max(0, prev - 1));
115
+ } else if (key.downArrow || input === "j") {
116
+ setFocusedIndex((prev) => Math.min(flatNodes.length - 1, prev + 1));
117
+ } else if (key.rightArrow || input === "l") {
118
+ if (currentNode.hasChildren && !currentNode.isExpanded) {
119
+ setExpanded((prev) => new Set([...prev, currentNode.node.id]));
120
+ }
121
+ } else if (key.leftArrow || input === "h") {
122
+ if (currentNode.hasChildren && currentNode.isExpanded) {
123
+ setExpanded((prev) => {
124
+ const next = new Set(prev);
125
+ next.delete(currentNode.node.id);
126
+ return next;
127
+ });
128
+ }
129
+ } else if (key.return || input === " ") {
130
+ if (!currentNode.node.disabled) {
131
+ if (currentNode.hasChildren) {
132
+ setExpanded((prev) => {
133
+ const next = new Set(prev);
134
+ if (next.has(currentNode.node.id)) {
135
+ next.delete(currentNode.node.id);
136
+ } else {
137
+ next.add(currentNode.node.id);
138
+ }
139
+ return next;
140
+ });
141
+ }
142
+ onSelect?.(currentNode.node);
143
+ }
144
+ } else if (input === "g") {
145
+ setFocusedIndex(0);
146
+ } else if (input === "G") {
147
+ setFocusedIndex(flatNodes.length - 1);
148
+ }
149
+ },
150
+ { isActive: isFocused }
151
+ );
152
+
153
+ // Build line prefix
154
+ const buildPrefix = (flatNode: FlatNode): string => {
155
+ if (!showLines) {
156
+ return " ".repeat(flatNode.depth * indentSize);
157
+ }
158
+
159
+ let prefix = "";
160
+
161
+ for (let i = 0; i < flatNode.depth; i++) {
162
+ if (flatNode.parentIsLast[i]) {
163
+ prefix += " ".repeat(indentSize);
164
+ } else {
165
+ prefix += "│" + " ".repeat(indentSize - 1);
166
+ }
167
+ }
168
+
169
+ if (flatNode.depth > 0) {
170
+ prefix = prefix.slice(0, -indentSize);
171
+ prefix += flatNode.isLast
172
+ ? "└" + "─".repeat(indentSize - 1)
173
+ : "├" + "─".repeat(indentSize - 1);
174
+ }
175
+
176
+ return prefix;
177
+ };
178
+
179
+ // Get node icon
180
+ const getIcon = (flatNode: FlatNode): string => {
181
+ if (flatNode.node.icon) return flatNode.node.icon;
182
+ if (flatNode.hasChildren) {
183
+ return flatNode.isExpanded ? "▼" : "▶";
184
+ }
185
+ return "•";
186
+ };
187
+
188
+ return React.createElement(
189
+ Box,
190
+ {
191
+ flexDirection: "column",
192
+ borderStyle: isFocused ? "round" : "single",
193
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
194
+ paddingX: 1,
195
+ },
196
+ flatNodes.map((flatNode, index) => {
197
+ const isSelected = selectedId === flatNode.node.id;
198
+ const isFocusedItem = isFocused && index === focusedIndex;
199
+ const prefix = buildPrefix(flatNode);
200
+ const icon = getIcon(flatNode);
201
+
202
+ return React.createElement(
203
+ Box,
204
+ { key: flatNode.node.id },
205
+ React.createElement(
206
+ Text,
207
+ {
208
+ color: flatNode.node.disabled
209
+ ? tokens.colors.muted
210
+ : isFocusedItem
211
+ ? tokens.colors.accent
212
+ : tokens.colors.border,
213
+ },
214
+ prefix
215
+ ),
216
+ React.createElement(
217
+ Text,
218
+ {
219
+ color: flatNode.node.disabled
220
+ ? tokens.colors.muted
221
+ : isFocusedItem
222
+ ? tokens.colors.accent
223
+ : isSelected
224
+ ? tokens.colors.success
225
+ : tokens.colors.fg,
226
+ },
227
+ icon + " "
228
+ ),
229
+ React.createElement(
230
+ Text,
231
+ {
232
+ color: flatNode.node.disabled
233
+ ? tokens.colors.muted
234
+ : isFocusedItem || isSelected
235
+ ? tokens.colors.accent
236
+ : tokens.colors.fg,
237
+ bold: isFocusedItem || isSelected,
238
+ dimColor: flatNode.node.disabled,
239
+ },
240
+ flatNode.node.label
241
+ )
242
+ );
243
+ })
244
+ );
245
+ }
@@ -0,0 +1,12 @@
1
+ export {
2
+ Typography,
3
+ H1,
4
+ H2,
5
+ H3,
6
+ H4,
7
+ Code,
8
+ Quote,
9
+ Small,
10
+ Lead,
11
+ } from "./typography.js";
12
+ export type { TypographyProps, TypographyVariant } from "./typography.js";