@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,211 @@
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 ColorPickerProps {
8
+ /** Current color value (hex) */
9
+ value: string;
10
+ /** Callback when color changes */
11
+ onChange?: (color: string) => void;
12
+ /** Preset colors */
13
+ presets?: string[];
14
+ /** Show color preview */
15
+ showPreview?: boolean;
16
+ /** Custom tokens override */
17
+ tokens?: Tokens;
18
+ /** Focus ID for focus management */
19
+ focusId?: string;
20
+ }
21
+
22
+ // Default preset colors
23
+ const DEFAULT_PRESETS = [
24
+ "#ff0000",
25
+ "#ff7700",
26
+ "#ffff00",
27
+ "#00ff00",
28
+ "#00ffff",
29
+ "#0000ff",
30
+ "#7700ff",
31
+ "#ff00ff",
32
+ "#ffffff",
33
+ "#888888",
34
+ "#000000",
35
+ "#ff5555",
36
+ "#ffaa55",
37
+ "#ffff55",
38
+ "#55ff55",
39
+ "#55ffff",
40
+ "#5555ff",
41
+ "#aa55ff",
42
+ "#ff55ff",
43
+ "#aaaaaa",
44
+ ];
45
+
46
+ // Terminal color names
47
+ const TERMINAL_COLORS: Record<string, string> = {
48
+ "#ff0000": "red",
49
+ "#00ff00": "green",
50
+ "#0000ff": "blue",
51
+ "#ffff00": "yellow",
52
+ "#ff00ff": "magenta",
53
+ "#00ffff": "cyan",
54
+ "#ffffff": "white",
55
+ "#000000": "black",
56
+ };
57
+
58
+ export function ColorPicker({
59
+ value,
60
+ onChange,
61
+ presets = DEFAULT_PRESETS,
62
+ showPreview = true,
63
+ tokens: propTokens,
64
+ focusId,
65
+ }: ColorPickerProps): React.ReactElement {
66
+ const contextTokens = useTokens();
67
+ const tokens = propTokens ?? contextTokens;
68
+ const id = focusId ?? stableId("color-picker");
69
+ const { isFocused } = useFocusable(id);
70
+
71
+ const [selectedIndex, setSelectedIndex] = useState(() =>
72
+ Math.max(0, presets.indexOf(value))
73
+ );
74
+
75
+ // Grid dimensions
76
+ const cols = 5;
77
+ const rows = Math.ceil(presets.length / cols);
78
+
79
+ // Handle keyboard input
80
+ useInput(
81
+ (input, key) => {
82
+ if (!isFocused) return;
83
+
84
+ const currentRow = Math.floor(selectedIndex / cols);
85
+ const currentCol = selectedIndex % cols;
86
+
87
+ if (key.leftArrow || input === "h") {
88
+ if (currentCol > 0) {
89
+ setSelectedIndex((prev) => prev - 1);
90
+ }
91
+ } else if (key.rightArrow || input === "l") {
92
+ if (currentCol < cols - 1 && selectedIndex < presets.length - 1) {
93
+ setSelectedIndex((prev) => prev + 1);
94
+ }
95
+ } else if (key.upArrow || input === "k") {
96
+ if (currentRow > 0) {
97
+ setSelectedIndex((prev) => prev - cols);
98
+ }
99
+ } else if (key.downArrow || input === "j") {
100
+ const newIndex = selectedIndex + cols;
101
+ if (newIndex < presets.length) {
102
+ setSelectedIndex(newIndex);
103
+ }
104
+ } else if (key.return || input === " ") {
105
+ onChange?.(presets[selectedIndex]);
106
+ }
107
+ },
108
+ { isActive: isFocused }
109
+ );
110
+
111
+ // Render color swatch
112
+ const renderSwatch = (color: string, index: number) => {
113
+ const isSelected = color === value;
114
+ const isFocusedItem = isFocused && index === selectedIndex;
115
+
116
+ return React.createElement(
117
+ Text,
118
+ {
119
+ key: index,
120
+ backgroundColor: color,
121
+ color: getLuminance(color) > 0.5 ? "#000000" : "#ffffff",
122
+ },
123
+ isFocusedItem ? "[■]" : isSelected ? " ● " : " "
124
+ );
125
+ };
126
+
127
+ // Calculate relative luminance
128
+ function getLuminance(hex: string): number {
129
+ const rgb = hexToRgb(hex);
130
+ if (!rgb) return 0;
131
+ const [r, g, b] = rgb.map((c) => {
132
+ c = c / 255;
133
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
134
+ });
135
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
136
+ }
137
+
138
+ // Convert hex to RGB
139
+ function hexToRgb(hex: string): number[] | null {
140
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
141
+ return result
142
+ ? [
143
+ parseInt(result[1], 16),
144
+ parseInt(result[2], 16),
145
+ parseInt(result[3], 16),
146
+ ]
147
+ : null;
148
+ }
149
+
150
+ // Build grid rows
151
+ const gridRows: React.ReactElement[] = [];
152
+ for (let row = 0; row < rows; row++) {
153
+ const rowColors = presets.slice(row * cols, (row + 1) * cols);
154
+ gridRows.push(
155
+ React.createElement(
156
+ Box,
157
+ { key: row },
158
+ rowColors.map((color, i) => renderSwatch(color, row * cols + i))
159
+ )
160
+ );
161
+ }
162
+
163
+ return React.createElement(
164
+ Box,
165
+ {
166
+ flexDirection: "column",
167
+ borderStyle: isFocused ? "round" : "single",
168
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
169
+ padding: 1,
170
+ },
171
+ // Title
172
+ React.createElement(
173
+ Text,
174
+ { color: tokens.colors.muted, marginBottom: 1 },
175
+ "Color Picker"
176
+ ),
177
+ // Preview
178
+ showPreview &&
179
+ React.createElement(
180
+ Box,
181
+ { marginBottom: 1 },
182
+ React.createElement(
183
+ Text,
184
+ {
185
+ backgroundColor: value,
186
+ color: getLuminance(value) > 0.5 ? "#000000" : "#ffffff",
187
+ },
188
+ ` ${value} `
189
+ ),
190
+ TERMINAL_COLORS[value.toLowerCase()] &&
191
+ React.createElement(
192
+ Text,
193
+ { color: tokens.colors.muted },
194
+ ` (${TERMINAL_COLORS[value.toLowerCase()]})`
195
+ )
196
+ ),
197
+ // Color grid
198
+ React.createElement(Box, { flexDirection: "column" }, ...gridRows),
199
+ // Instructions
200
+ isFocused &&
201
+ React.createElement(
202
+ Box,
203
+ { marginTop: 1 },
204
+ React.createElement(
205
+ Text,
206
+ { color: tokens.colors.muted, dimColor: true },
207
+ "Arrow keys to move, Enter to select"
208
+ )
209
+ )
210
+ );
211
+ }
@@ -0,0 +1,2 @@
1
+ export { ColorPicker } from "./color-picker.js";
2
+ export type { ColorPickerProps } from "./color-picker.js";
@@ -0,0 +1,275 @@
1
+ import React, { useState, useCallback, useMemo } 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 ComboboxOption {
8
+ /** Unique value */
9
+ value: string;
10
+ /** Display label */
11
+ label: string;
12
+ /** Optional description */
13
+ description?: string;
14
+ /** Whether this option is disabled */
15
+ disabled?: boolean;
16
+ }
17
+
18
+ export interface ComboboxProps {
19
+ /** Available options */
20
+ options: ComboboxOption[];
21
+ /** Controlled value */
22
+ value?: string;
23
+ /** Default value for uncontrolled mode */
24
+ defaultValue?: string;
25
+ /** Callback when value changes */
26
+ onChange?: (value: string) => void;
27
+ /** Placeholder text */
28
+ placeholder?: string;
29
+ /** Label text */
30
+ label?: string;
31
+ /** Custom tokens override */
32
+ tokens?: Tokens;
33
+ /** Focus ID for focus management */
34
+ focusId?: string;
35
+ /** Maximum visible items */
36
+ maxItems?: number;
37
+ /** Empty state message */
38
+ emptyMessage?: string;
39
+ /** Allow creating new values */
40
+ allowCreate?: boolean;
41
+ }
42
+
43
+ export function Combobox({
44
+ options,
45
+ value: controlledValue,
46
+ defaultValue = "",
47
+ onChange,
48
+ placeholder = "Search...",
49
+ label,
50
+ tokens: propTokens,
51
+ focusId,
52
+ maxItems = 5,
53
+ emptyMessage = "No results found.",
54
+ allowCreate = false,
55
+ }: ComboboxProps): React.ReactElement {
56
+ const contextTokens = useTokens();
57
+ const tokens = propTokens ?? contextTokens;
58
+ const id = focusId ?? stableId("combobox");
59
+ const { isFocused } = useFocusable(id);
60
+
61
+ const [internalValue, setInternalValue] = useState(defaultValue);
62
+ const [search, setSearch] = useState("");
63
+ const [isOpen, setIsOpen] = useState(false);
64
+ const [selectedIndex, setSelectedIndex] = useState(0);
65
+
66
+ const isControlled = controlledValue !== undefined;
67
+ const currentValue = isControlled ? controlledValue : internalValue;
68
+
69
+ // Filter options based on search
70
+ const filteredOptions = useMemo(() => {
71
+ if (!search.trim()) return options;
72
+ const lowerSearch = search.toLowerCase();
73
+ return options.filter(
74
+ (opt) =>
75
+ opt.label.toLowerCase().includes(lowerSearch) ||
76
+ opt.value.toLowerCase().includes(lowerSearch) ||
77
+ opt.description?.toLowerCase().includes(lowerSearch)
78
+ );
79
+ }, [options, search]);
80
+
81
+ const visibleOptions = filteredOptions.slice(0, maxItems);
82
+
83
+ const handleSelect = useCallback(
84
+ (option: ComboboxOption) => {
85
+ if (option.disabled) return;
86
+ if (!isControlled) {
87
+ setInternalValue(option.value);
88
+ }
89
+ onChange?.(option.value);
90
+ setSearch("");
91
+ setIsOpen(false);
92
+ },
93
+ [isControlled, onChange]
94
+ );
95
+
96
+ useInput(
97
+ (input, key) => {
98
+ if (!isFocused) return;
99
+
100
+ if (key.escape) {
101
+ if (isOpen) {
102
+ setIsOpen(false);
103
+ setSearch("");
104
+ }
105
+ return;
106
+ }
107
+
108
+ if (key.return) {
109
+ if (isOpen && visibleOptions[selectedIndex]) {
110
+ handleSelect(visibleOptions[selectedIndex]!);
111
+ } else if (!isOpen) {
112
+ setIsOpen(true);
113
+ } else if (
114
+ allowCreate &&
115
+ search.trim() &&
116
+ filteredOptions.length === 0
117
+ ) {
118
+ // Create new value
119
+ if (!isControlled) {
120
+ setInternalValue(search);
121
+ }
122
+ onChange?.(search);
123
+ setSearch("");
124
+ setIsOpen(false);
125
+ }
126
+ return;
127
+ }
128
+
129
+ if (key.upArrow || input === "k") {
130
+ if (isOpen) {
131
+ setSelectedIndex((prev) =>
132
+ clamp(prev - 1, 0, visibleOptions.length - 1)
133
+ );
134
+ }
135
+ return;
136
+ }
137
+
138
+ if (key.downArrow || input === "j") {
139
+ if (isOpen) {
140
+ setSelectedIndex((prev) =>
141
+ clamp(prev + 1, 0, visibleOptions.length - 1)
142
+ );
143
+ } else {
144
+ setIsOpen(true);
145
+ }
146
+ return;
147
+ }
148
+
149
+ if (key.backspace || key.delete) {
150
+ setSearch((prev) => prev.slice(0, -1));
151
+ setIsOpen(true);
152
+ setSelectedIndex(0);
153
+ return;
154
+ }
155
+
156
+ // Regular character input
157
+ if (input && input.length === 1 && !key.ctrl && !key.meta) {
158
+ setSearch((prev) => prev + input);
159
+ setIsOpen(true);
160
+ setSelectedIndex(0);
161
+ }
162
+ },
163
+ { isActive: isFocused }
164
+ );
165
+
166
+ const selectedOption = options.find((opt) => opt.value === currentValue);
167
+ const displayValue = search || selectedOption?.label || "";
168
+
169
+ return React.createElement(
170
+ Box,
171
+ { flexDirection: "column" },
172
+ // Label
173
+ label
174
+ ? React.createElement(
175
+ Box,
176
+ { marginBottom: 0 },
177
+ React.createElement(
178
+ Text,
179
+ { color: tokens.colors.fg, bold: true },
180
+ label
181
+ )
182
+ )
183
+ : null,
184
+ // Input box
185
+ React.createElement(
186
+ Box,
187
+ {
188
+ borderStyle: "round",
189
+ borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
190
+ paddingX: 1,
191
+ width: 30,
192
+ },
193
+ React.createElement(
194
+ Text,
195
+ { color: displayValue ? tokens.colors.fg : tokens.colors.muted },
196
+ displayValue || placeholder
197
+ ),
198
+ React.createElement(
199
+ Box,
200
+ { flexGrow: 1, justifyContent: "flex-end" },
201
+ React.createElement(
202
+ Text,
203
+ { color: tokens.colors.muted },
204
+ isOpen ? "▲" : "▼"
205
+ )
206
+ )
207
+ ),
208
+ // Dropdown
209
+ isOpen
210
+ ? React.createElement(
211
+ Box,
212
+ {
213
+ flexDirection: "column",
214
+ borderStyle: "single",
215
+ borderColor: tokens.colors.border,
216
+ borderTop: false,
217
+ width: 30,
218
+ paddingY: 0,
219
+ },
220
+ visibleOptions.length > 0
221
+ ? visibleOptions.map((option, index) => {
222
+ const isSelected = index === selectedIndex;
223
+ return React.createElement(
224
+ Box,
225
+ { key: option.value, paddingX: 1, gap: 1 },
226
+ React.createElement(
227
+ Text,
228
+ {
229
+ color: isSelected
230
+ ? tokens.colors.accent
231
+ : tokens.colors.muted,
232
+ },
233
+ isSelected ? "▸" : " "
234
+ ),
235
+ React.createElement(
236
+ Box,
237
+ { flexDirection: "column" },
238
+ React.createElement(
239
+ Text,
240
+ {
241
+ color: option.disabled
242
+ ? tokens.colors.muted
243
+ : isSelected
244
+ ? tokens.colors.fg
245
+ : tokens.colors.fg,
246
+ dimColor: option.disabled,
247
+ bold: isSelected,
248
+ },
249
+ option.label
250
+ ),
251
+ option.description
252
+ ? React.createElement(
253
+ Text,
254
+ { color: tokens.colors.muted, dimColor: true },
255
+ option.description
256
+ )
257
+ : null
258
+ )
259
+ );
260
+ })
261
+ : React.createElement(
262
+ Box,
263
+ { paddingX: 1 },
264
+ React.createElement(
265
+ Text,
266
+ { color: tokens.colors.muted, dimColor: true },
267
+ allowCreate && search.trim()
268
+ ? `Press Enter to create "${search}"`
269
+ : emptyMessage
270
+ )
271
+ )
272
+ )
273
+ : null
274
+ );
275
+ }
@@ -0,0 +1,2 @@
1
+ export { Combobox } from "./combobox.js";
2
+ export type { ComboboxProps, ComboboxOption } from "./combobox.js";