@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.
- package/components/accordion/accordion.tsx +146 -0
- package/components/accordion/index.ts +2 -0
- package/components/alert/alert.tsx +69 -0
- package/components/alert/index.ts +2 -0
- package/components/alert-dialog/alert-dialog.tsx +185 -0
- package/components/alert-dialog/index.ts +2 -0
- package/components/avatar/avatar.tsx +57 -0
- package/components/avatar/index.ts +2 -0
- package/components/avatar-group/avatar-group.tsx +144 -0
- package/components/avatar-group/index.ts +2 -0
- package/components/badge/badge.tsx +52 -0
- package/components/badge/index.ts +2 -0
- package/components/banner/banner.tsx +407 -0
- package/components/banner/index.ts +2 -0
- package/components/breadcrumb/breadcrumb.tsx +58 -0
- package/components/breadcrumb/index.ts +2 -0
- package/components/button/button.tsx +114 -0
- package/components/button/index.ts +2 -0
- package/components/calendar/calendar.tsx +250 -0
- package/components/calendar/index.ts +2 -0
- package/components/card/card.tsx +88 -0
- package/components/card/index.ts +2 -0
- package/components/carousel/carousel.tsx +185 -0
- package/components/carousel/index.ts +2 -0
- package/components/chart/chart.tsx +189 -0
- package/components/chart/index.ts +2 -0
- package/components/checkbox/checkbox.tsx +98 -0
- package/components/checkbox/index.ts +2 -0
- package/components/code-block/code-block.tsx +214 -0
- package/components/code-block/index.ts +2 -0
- package/components/collapsible/collapsible.tsx +123 -0
- package/components/collapsible/index.ts +2 -0
- package/components/color-picker/color-picker.tsx +211 -0
- package/components/color-picker/index.ts +2 -0
- package/components/combobox/combobox.tsx +275 -0
- package/components/combobox/index.ts +2 -0
- package/components/command/command.tsx +304 -0
- package/components/command/index.ts +2 -0
- package/components/confirm-dialog/confirm-dialog.tsx +140 -0
- package/components/confirm-dialog/index.ts +2 -0
- package/components/context-menu/context-menu.tsx +188 -0
- package/components/context-menu/index.ts +2 -0
- package/components/countdown/countdown.tsx +165 -0
- package/components/countdown/index.ts +2 -0
- package/components/data-table/data-table.tsx +256 -0
- package/components/data-table/index.ts +2 -0
- package/components/date-picker/date-picker.tsx +280 -0
- package/components/date-picker/index.ts +2 -0
- package/components/dialog/dialog.tsx +84 -0
- package/components/dialog/index.ts +2 -0
- package/components/drawer/drawer.tsx +141 -0
- package/components/drawer/index.ts +2 -0
- package/components/dropdown-menu/dropdown-menu.tsx +188 -0
- package/components/dropdown-menu/index.ts +2 -0
- package/components/empty/empty.tsx +107 -0
- package/components/empty/index.ts +2 -0
- package/components/field/field.tsx +83 -0
- package/components/field/index.ts +2 -0
- package/components/form/form.tsx +202 -0
- package/components/form/index.ts +8 -0
- package/components/hover-card/hover-card.tsx +72 -0
- package/components/hover-card/index.ts +2 -0
- package/components/input-otp/index.ts +2 -0
- package/components/input-otp/input-otp.tsx +176 -0
- package/components/kbd/index.ts +2 -0
- package/components/kbd/kbd.tsx +30 -0
- package/components/label/index.ts +2 -0
- package/components/label/label.tsx +56 -0
- package/components/list/index.ts +2 -0
- package/components/list/list.tsx +247 -0
- package/components/menubar/index.ts +2 -0
- package/components/menubar/menubar.tsx +220 -0
- package/components/navigation-menu/index.ts +6 -0
- package/components/navigation-menu/navigation-menu.tsx +216 -0
- package/components/pagination/index.ts +2 -0
- package/components/pagination/pagination.tsx +158 -0
- package/components/password-input/index.ts +2 -0
- package/components/password-input/password-input.tsx +198 -0
- package/components/popover/index.ts +2 -0
- package/components/popover/popover.tsx +102 -0
- package/components/progress/index.ts +2 -0
- package/components/progress/progress.tsx +73 -0
- package/components/radio-group/index.ts +2 -0
- package/components/radio-group/radio-group.tsx +167 -0
- package/components/resizable/index.ts +2 -0
- package/components/resizable/resizable.tsx +141 -0
- package/components/scroll-area/index.ts +2 -0
- package/components/scroll-area/scroll-area.tsx +133 -0
- package/components/select/index.ts +2 -0
- package/components/select/select.tsx +185 -0
- package/components/separator/index.ts +2 -0
- package/components/separator/separator.tsx +63 -0
- package/components/sheet/index.ts +2 -0
- package/components/sheet/sheet.tsx +137 -0
- package/components/sidebar/index.ts +2 -0
- package/components/sidebar/sidebar.tsx +225 -0
- package/components/skeleton/index.ts +2 -0
- package/components/skeleton/skeleton.tsx +64 -0
- package/components/slider/index.ts +2 -0
- package/components/slider/slider.tsx +128 -0
- package/components/spinner/index.ts +2 -0
- package/components/spinner/spinner.tsx +57 -0
- package/components/stat/index.ts +2 -0
- package/components/stat/stat.tsx +138 -0
- package/components/stepper/index.ts +2 -0
- package/components/stepper/stepper.tsx +219 -0
- package/components/switch/index.ts +2 -0
- package/components/switch/switch.tsx +102 -0
- package/components/table/index.ts +2 -0
- package/components/table/table.tsx +242 -0
- package/components/tabs/index.ts +2 -0
- package/components/tabs/tabs.tsx +240 -0
- package/components/tag-input/index.ts +2 -0
- package/components/tag-input/tag-input.tsx +180 -0
- package/components/terminal/index.ts +2 -0
- package/components/terminal/terminal.tsx +162 -0
- package/components/text-input/index.ts +2 -0
- package/components/text-input/text-input.tsx +179 -0
- package/components/textarea/index.ts +2 -0
- package/components/textarea/textarea.tsx +206 -0
- package/components/timeline/index.ts +2 -0
- package/components/timeline/timeline.tsx +167 -0
- package/components/toast/index.ts +2 -0
- package/components/toast/toast.tsx +93 -0
- package/components/toggle/index.ts +2 -0
- package/components/toggle/toggle.tsx +114 -0
- package/components/toggle-group/index.ts +2 -0
- package/components/toggle-group/toggle-group.tsx +176 -0
- package/components/tooltip/index.ts +2 -0
- package/components/tooltip/tooltip.tsx +65 -0
- package/components/tree-view/index.ts +2 -0
- package/components/tree-view/tree-view.tsx +245 -0
- package/components/typography/index.ts +12 -0
- package/components/typography/typography.tsx +154 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +938 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
- package/registry.json +923 -0
|
@@ -0,0 +1,146 @@
|
|
|
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 AccordionItem {
|
|
8
|
+
/** Unique key for the item */
|
|
9
|
+
key: string;
|
|
10
|
+
/** Item title */
|
|
11
|
+
title: string;
|
|
12
|
+
/** Item content */
|
|
13
|
+
content: string;
|
|
14
|
+
/** Whether this item is disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AccordionProps {
|
|
19
|
+
/** Accordion items */
|
|
20
|
+
items: AccordionItem[];
|
|
21
|
+
/** Allow multiple items to be expanded */
|
|
22
|
+
multiple?: boolean;
|
|
23
|
+
/** Controlled expanded keys */
|
|
24
|
+
expandedKeys?: string[];
|
|
25
|
+
/** Default expanded keys for uncontrolled mode */
|
|
26
|
+
defaultExpandedKeys?: string[];
|
|
27
|
+
/** Callback when expansion changes */
|
|
28
|
+
onChange?: (keys: string[]) => void;
|
|
29
|
+
/** Whether the accordion is disabled */
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
/** Custom tokens override */
|
|
32
|
+
tokens?: Tokens;
|
|
33
|
+
/** Focus ID for focus management */
|
|
34
|
+
focusId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function Accordion({
|
|
38
|
+
items,
|
|
39
|
+
multiple = false,
|
|
40
|
+
expandedKeys: controlledExpandedKeys,
|
|
41
|
+
defaultExpandedKeys = [],
|
|
42
|
+
onChange,
|
|
43
|
+
disabled = false,
|
|
44
|
+
tokens: propTokens,
|
|
45
|
+
focusId,
|
|
46
|
+
}: AccordionProps): React.ReactElement {
|
|
47
|
+
const contextTokens = useTokens();
|
|
48
|
+
const tokens = propTokens ?? contextTokens;
|
|
49
|
+
const id = focusId ?? stableId("accordion");
|
|
50
|
+
const { isFocused } = useFocusable(id);
|
|
51
|
+
|
|
52
|
+
const [internalExpandedKeys, setInternalExpandedKeys] =
|
|
53
|
+
useState<string[]>(defaultExpandedKeys);
|
|
54
|
+
const [highlightedIndex, setHighlightedIndex] = useState(0);
|
|
55
|
+
|
|
56
|
+
const isControlled = controlledExpandedKeys !== undefined;
|
|
57
|
+
const expandedKeys = isControlled
|
|
58
|
+
? controlledExpandedKeys
|
|
59
|
+
: internalExpandedKeys;
|
|
60
|
+
|
|
61
|
+
const toggleItem = useCallback(
|
|
62
|
+
(key: string) => {
|
|
63
|
+
const item = items.find((i) => i.key === key);
|
|
64
|
+
if (!item || item.disabled) return;
|
|
65
|
+
|
|
66
|
+
let newKeys: string[];
|
|
67
|
+
if (expandedKeys.includes(key)) {
|
|
68
|
+
newKeys = expandedKeys.filter((k) => k !== key);
|
|
69
|
+
} else {
|
|
70
|
+
newKeys = multiple ? [...expandedKeys, key] : [key];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!isControlled) {
|
|
74
|
+
setInternalExpandedKeys(newKeys);
|
|
75
|
+
}
|
|
76
|
+
onChange?.(newKeys);
|
|
77
|
+
},
|
|
78
|
+
[items, expandedKeys, multiple, isControlled, onChange]
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
useInput(
|
|
82
|
+
(input, key) => {
|
|
83
|
+
if (!isFocused || disabled) return;
|
|
84
|
+
|
|
85
|
+
if (key.upArrow || input === "k") {
|
|
86
|
+
setHighlightedIndex((prev) => Math.max(0, prev - 1));
|
|
87
|
+
} else if (key.downArrow || input === "j") {
|
|
88
|
+
setHighlightedIndex((prev) => Math.min(items.length - 1, prev + 1));
|
|
89
|
+
} else if (key.return || input === " ") {
|
|
90
|
+
const item = items[highlightedIndex];
|
|
91
|
+
if (item) {
|
|
92
|
+
toggleItem(item.key);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{ isActive: isFocused }
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return React.createElement(
|
|
100
|
+
Box,
|
|
101
|
+
{
|
|
102
|
+
flexDirection: "column",
|
|
103
|
+
borderStyle: isFocused ? "round" : "single",
|
|
104
|
+
borderColor: isFocused ? tokens.colors.focus : tokens.colors.border,
|
|
105
|
+
},
|
|
106
|
+
items.map((item, index) => {
|
|
107
|
+
const isExpanded = expandedKeys.includes(item.key);
|
|
108
|
+
const isHighlighted = index === highlightedIndex && isFocused;
|
|
109
|
+
const isDisabled = item.disabled || disabled;
|
|
110
|
+
|
|
111
|
+
const icon = isExpanded ? "▼" : "▶";
|
|
112
|
+
|
|
113
|
+
return React.createElement(
|
|
114
|
+
Box,
|
|
115
|
+
{ key: item.key, flexDirection: "column", paddingX: 1 },
|
|
116
|
+
React.createElement(
|
|
117
|
+
Text,
|
|
118
|
+
{
|
|
119
|
+
color: isDisabled
|
|
120
|
+
? tokens.colors.disabled
|
|
121
|
+
: isHighlighted
|
|
122
|
+
? tokens.colors.accent
|
|
123
|
+
: tokens.colors.fg,
|
|
124
|
+
bold: isHighlighted,
|
|
125
|
+
inverse: isHighlighted,
|
|
126
|
+
dimColor: isDisabled,
|
|
127
|
+
},
|
|
128
|
+
icon,
|
|
129
|
+
" ",
|
|
130
|
+
item.title
|
|
131
|
+
),
|
|
132
|
+
isExpanded
|
|
133
|
+
? React.createElement(
|
|
134
|
+
Box,
|
|
135
|
+
{ paddingLeft: 2, paddingY: 0 },
|
|
136
|
+
React.createElement(
|
|
137
|
+
Text,
|
|
138
|
+
{ color: tokens.colors.muted },
|
|
139
|
+
item.content
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
: null
|
|
143
|
+
);
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
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 type AlertVariant = "info" | "success" | "warning" | "error";
|
|
7
|
+
|
|
8
|
+
export interface AlertProps {
|
|
9
|
+
/** Alert message content */
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
/** Alert title */
|
|
12
|
+
title?: string;
|
|
13
|
+
/** Alert variant */
|
|
14
|
+
variant?: AlertVariant;
|
|
15
|
+
/** Custom tokens override */
|
|
16
|
+
tokens?: Tokens;
|
|
17
|
+
/** Whether to show an icon */
|
|
18
|
+
showIcon?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ICONS: Record<AlertVariant, string> = {
|
|
22
|
+
info: "ℹ",
|
|
23
|
+
success: "✓",
|
|
24
|
+
warning: "⚠",
|
|
25
|
+
error: "✗",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function Alert({
|
|
29
|
+
children,
|
|
30
|
+
title,
|
|
31
|
+
variant = "info",
|
|
32
|
+
tokens: propTokens,
|
|
33
|
+
showIcon = true,
|
|
34
|
+
}: AlertProps): React.ReactElement {
|
|
35
|
+
const contextTokens = useTokens();
|
|
36
|
+
const tokens = propTokens ?? contextTokens;
|
|
37
|
+
|
|
38
|
+
const variantColors: Record<AlertVariant, string> = {
|
|
39
|
+
info: tokens.colors.accent,
|
|
40
|
+
success: tokens.colors.success,
|
|
41
|
+
warning: tokens.colors.warning,
|
|
42
|
+
error: tokens.colors.danger,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const color = variantColors[variant];
|
|
46
|
+
const icon = ICONS[variant];
|
|
47
|
+
|
|
48
|
+
return React.createElement(
|
|
49
|
+
Box,
|
|
50
|
+
{
|
|
51
|
+
flexDirection: "column",
|
|
52
|
+
borderStyle: "round",
|
|
53
|
+
borderColor: color,
|
|
54
|
+
paddingX: 1,
|
|
55
|
+
paddingY: 0,
|
|
56
|
+
},
|
|
57
|
+
React.createElement(
|
|
58
|
+
Box,
|
|
59
|
+
{ gap: 1 },
|
|
60
|
+
showIcon ? React.createElement(Text, { color }, icon) : null,
|
|
61
|
+
title ? React.createElement(Text, { color, bold: true }, title) : null
|
|
62
|
+
),
|
|
63
|
+
React.createElement(
|
|
64
|
+
Box,
|
|
65
|
+
{ paddingLeft: showIcon ? 2 : 0 },
|
|
66
|
+
React.createElement(Text, { color: tokens.colors.fg }, children)
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import React 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 type AlertDialogVariant = "default" | "destructive";
|
|
8
|
+
|
|
9
|
+
export interface AlertDialogProps {
|
|
10
|
+
/** Whether the dialog is open */
|
|
11
|
+
open: boolean;
|
|
12
|
+
/** Callback when dialog should close */
|
|
13
|
+
onClose: () => void;
|
|
14
|
+
/** Dialog title */
|
|
15
|
+
title: string;
|
|
16
|
+
/** Dialog description/message */
|
|
17
|
+
description: string;
|
|
18
|
+
/** Confirm button label */
|
|
19
|
+
confirmLabel?: string;
|
|
20
|
+
/** Cancel button label */
|
|
21
|
+
cancelLabel?: string;
|
|
22
|
+
/** Callback when confirmed */
|
|
23
|
+
onConfirm: () => void;
|
|
24
|
+
/** Callback when cancelled */
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
/** Dialog variant */
|
|
27
|
+
variant?: AlertDialogVariant;
|
|
28
|
+
/** Custom tokens override */
|
|
29
|
+
tokens?: Tokens;
|
|
30
|
+
/** Focus ID for focus management */
|
|
31
|
+
focusId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function AlertDialog({
|
|
35
|
+
open,
|
|
36
|
+
onClose,
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
confirmLabel = "Continue",
|
|
40
|
+
cancelLabel = "Cancel",
|
|
41
|
+
onConfirm,
|
|
42
|
+
onCancel,
|
|
43
|
+
variant = "default",
|
|
44
|
+
tokens: propTokens,
|
|
45
|
+
focusId,
|
|
46
|
+
}: AlertDialogProps): React.ReactElement | null {
|
|
47
|
+
const contextTokens = useTokens();
|
|
48
|
+
const tokens = propTokens ?? contextTokens;
|
|
49
|
+
const id = focusId ?? stableId("alert-dialog");
|
|
50
|
+
const { isFocused } = useFocusable(id);
|
|
51
|
+
|
|
52
|
+
const [focused, setFocused] = React.useState<"cancel" | "confirm">("cancel");
|
|
53
|
+
|
|
54
|
+
useInput(
|
|
55
|
+
(input, key) => {
|
|
56
|
+
if (!open) return;
|
|
57
|
+
|
|
58
|
+
if (key.escape) {
|
|
59
|
+
onCancel?.();
|
|
60
|
+
onClose();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (key.leftArrow || input === "h") {
|
|
65
|
+
setFocused("cancel");
|
|
66
|
+
} else if (key.rightArrow || input === "l") {
|
|
67
|
+
setFocused("confirm");
|
|
68
|
+
} else if (key.tab) {
|
|
69
|
+
setFocused((prev) => (prev === "cancel" ? "confirm" : "cancel"));
|
|
70
|
+
} else if (key.return) {
|
|
71
|
+
if (focused === "confirm") {
|
|
72
|
+
onConfirm();
|
|
73
|
+
onClose();
|
|
74
|
+
} else {
|
|
75
|
+
onCancel?.();
|
|
76
|
+
onClose();
|
|
77
|
+
}
|
|
78
|
+
} else if (input === "y" || input === "Y") {
|
|
79
|
+
onConfirm();
|
|
80
|
+
onClose();
|
|
81
|
+
} else if (input === "n" || input === "N") {
|
|
82
|
+
onCancel?.();
|
|
83
|
+
onClose();
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{ isActive: open && isFocused }
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (!open) return null;
|
|
90
|
+
|
|
91
|
+
const confirmColor =
|
|
92
|
+
variant === "destructive" ? tokens.colors.danger : tokens.colors.accent;
|
|
93
|
+
|
|
94
|
+
return React.createElement(
|
|
95
|
+
Box,
|
|
96
|
+
{
|
|
97
|
+
flexDirection: "column",
|
|
98
|
+
borderStyle: "round",
|
|
99
|
+
borderColor:
|
|
100
|
+
variant === "destructive" ? tokens.colors.danger : tokens.colors.border,
|
|
101
|
+
paddingX: 2,
|
|
102
|
+
paddingY: 1,
|
|
103
|
+
width: 50,
|
|
104
|
+
},
|
|
105
|
+
// Icon + Title
|
|
106
|
+
React.createElement(
|
|
107
|
+
Box,
|
|
108
|
+
{ gap: 1, marginBottom: 1 },
|
|
109
|
+
React.createElement(
|
|
110
|
+
Text,
|
|
111
|
+
{
|
|
112
|
+
color:
|
|
113
|
+
variant === "destructive"
|
|
114
|
+
? tokens.colors.danger
|
|
115
|
+
: tokens.colors.warning,
|
|
116
|
+
},
|
|
117
|
+
variant === "destructive" ? "⚠" : "ℹ"
|
|
118
|
+
),
|
|
119
|
+
React.createElement(Text, { bold: true, color: tokens.colors.fg }, title)
|
|
120
|
+
),
|
|
121
|
+
// Description
|
|
122
|
+
React.createElement(
|
|
123
|
+
Box,
|
|
124
|
+
{ marginBottom: 1 },
|
|
125
|
+
React.createElement(Text, { color: tokens.colors.muted }, description)
|
|
126
|
+
),
|
|
127
|
+
// Separator
|
|
128
|
+
React.createElement(
|
|
129
|
+
Box,
|
|
130
|
+
{ marginBottom: 1 },
|
|
131
|
+
React.createElement(Text, { color: tokens.colors.border }, "─".repeat(46))
|
|
132
|
+
),
|
|
133
|
+
// Buttons
|
|
134
|
+
React.createElement(
|
|
135
|
+
Box,
|
|
136
|
+
{ gap: 2, justifyContent: "flex-end" },
|
|
137
|
+
// Cancel button
|
|
138
|
+
React.createElement(
|
|
139
|
+
Box,
|
|
140
|
+
{
|
|
141
|
+
borderStyle: focused === "cancel" ? "round" : "single",
|
|
142
|
+
borderColor:
|
|
143
|
+
focused === "cancel" ? tokens.colors.focus : tokens.colors.border,
|
|
144
|
+
paddingX: 2,
|
|
145
|
+
},
|
|
146
|
+
React.createElement(
|
|
147
|
+
Text,
|
|
148
|
+
{
|
|
149
|
+
color:
|
|
150
|
+
focused === "cancel" ? tokens.colors.fg : tokens.colors.muted,
|
|
151
|
+
},
|
|
152
|
+
cancelLabel
|
|
153
|
+
)
|
|
154
|
+
),
|
|
155
|
+
// Confirm button
|
|
156
|
+
React.createElement(
|
|
157
|
+
Box,
|
|
158
|
+
{
|
|
159
|
+
borderStyle: focused === "confirm" ? "round" : "single",
|
|
160
|
+
borderColor:
|
|
161
|
+
focused === "confirm" ? confirmColor : tokens.colors.border,
|
|
162
|
+
paddingX: 2,
|
|
163
|
+
},
|
|
164
|
+
React.createElement(
|
|
165
|
+
Text,
|
|
166
|
+
{
|
|
167
|
+
color: focused === "confirm" ? confirmColor : tokens.colors.muted,
|
|
168
|
+
bold: focused === "confirm",
|
|
169
|
+
},
|
|
170
|
+
confirmLabel
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
),
|
|
174
|
+
// Hint
|
|
175
|
+
React.createElement(
|
|
176
|
+
Box,
|
|
177
|
+
{ marginTop: 1 },
|
|
178
|
+
React.createElement(
|
|
179
|
+
Text,
|
|
180
|
+
{ color: tokens.colors.muted, dimColor: true },
|
|
181
|
+
"Y/N to respond • Tab to switch • Enter to confirm"
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import type { Tokens } from "@hauktui/tokens";
|
|
4
|
+
import { useTokens } from "@hauktui/primitives-ink";
|
|
5
|
+
|
|
6
|
+
export interface AvatarProps {
|
|
7
|
+
/** Name to generate initials from */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** Fallback character when no name is provided */
|
|
10
|
+
fallback?: string;
|
|
11
|
+
/** Size of the avatar */
|
|
12
|
+
size?: "sm" | "md" | "lg";
|
|
13
|
+
/** Custom tokens override */
|
|
14
|
+
tokens?: Tokens;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getInitials(name: string): string {
|
|
18
|
+
const parts = name.trim().split(/\s+/);
|
|
19
|
+
if (parts.length === 1) {
|
|
20
|
+
return parts[0]!.charAt(0).toUpperCase();
|
|
21
|
+
}
|
|
22
|
+
return (
|
|
23
|
+
parts[0]!.charAt(0) + parts[parts.length - 1]!.charAt(0)
|
|
24
|
+
).toUpperCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function Avatar({
|
|
28
|
+
name,
|
|
29
|
+
fallback = "?",
|
|
30
|
+
size = "md",
|
|
31
|
+
tokens: propTokens,
|
|
32
|
+
}: AvatarProps): React.ReactElement {
|
|
33
|
+
const contextTokens = useTokens();
|
|
34
|
+
const tokens = propTokens ?? contextTokens;
|
|
35
|
+
|
|
36
|
+
const initials = name ? getInitials(name) : fallback;
|
|
37
|
+
|
|
38
|
+
const sizeConfig = {
|
|
39
|
+
sm: { padding: "" },
|
|
40
|
+
md: { padding: " " },
|
|
41
|
+
lg: { padding: " " },
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const config = sizeConfig[size];
|
|
45
|
+
|
|
46
|
+
return React.createElement(
|
|
47
|
+
Text,
|
|
48
|
+
{
|
|
49
|
+
backgroundColor: tokens.colors.accent,
|
|
50
|
+
color: tokens.colors.bg,
|
|
51
|
+
bold: true,
|
|
52
|
+
},
|
|
53
|
+
config.padding,
|
|
54
|
+
initials,
|
|
55
|
+
config.padding
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
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 AvatarGroupProps {
|
|
7
|
+
/** Avatar names or initials */
|
|
8
|
+
avatars: string[];
|
|
9
|
+
/** Maximum avatars to show */
|
|
10
|
+
max?: number;
|
|
11
|
+
/** Size variant */
|
|
12
|
+
size?: "sm" | "md" | "lg";
|
|
13
|
+
/** Custom tokens override */
|
|
14
|
+
tokens?: Tokens;
|
|
15
|
+
/** Stacking direction */
|
|
16
|
+
direction?: "left" | "right";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Color palette for avatars
|
|
20
|
+
const AVATAR_COLORS = [
|
|
21
|
+
"#e91e63",
|
|
22
|
+
"#9c27b0",
|
|
23
|
+
"#673ab7",
|
|
24
|
+
"#3f51b5",
|
|
25
|
+
"#2196f3",
|
|
26
|
+
"#03a9f4",
|
|
27
|
+
"#00bcd4",
|
|
28
|
+
"#009688",
|
|
29
|
+
"#4caf50",
|
|
30
|
+
"#8bc34a",
|
|
31
|
+
"#cddc39",
|
|
32
|
+
"#ffeb3b",
|
|
33
|
+
"#ffc107",
|
|
34
|
+
"#ff9800",
|
|
35
|
+
"#ff5722",
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export function AvatarGroup({
|
|
39
|
+
avatars,
|
|
40
|
+
max = 5,
|
|
41
|
+
size = "md",
|
|
42
|
+
tokens: propTokens,
|
|
43
|
+
direction = "right",
|
|
44
|
+
}: AvatarGroupProps): React.ReactElement {
|
|
45
|
+
const contextTokens = useTokens();
|
|
46
|
+
const tokens = propTokens ?? contextTokens;
|
|
47
|
+
|
|
48
|
+
// Get avatar dimensions based on size
|
|
49
|
+
const getDimensions = () => {
|
|
50
|
+
switch (size) {
|
|
51
|
+
case "sm":
|
|
52
|
+
return { width: 3, chars: 1 };
|
|
53
|
+
case "lg":
|
|
54
|
+
return { width: 5, chars: 2 };
|
|
55
|
+
default:
|
|
56
|
+
return { width: 4, chars: 2 };
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const dims = getDimensions();
|
|
61
|
+
|
|
62
|
+
// Get initials from name
|
|
63
|
+
const getInitials = (name: string): string => {
|
|
64
|
+
const parts = name.trim().split(/\s+/);
|
|
65
|
+
if (parts.length === 1) {
|
|
66
|
+
return parts[0].substring(0, dims.chars).toUpperCase();
|
|
67
|
+
}
|
|
68
|
+
return parts
|
|
69
|
+
.slice(0, dims.chars)
|
|
70
|
+
.map((p) => p[0])
|
|
71
|
+
.join("")
|
|
72
|
+
.toUpperCase();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Get color for name (deterministic based on name)
|
|
76
|
+
const getColor = (name: string): string => {
|
|
77
|
+
let hash = 0;
|
|
78
|
+
for (let i = 0; i < name.length; i++) {
|
|
79
|
+
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
|
80
|
+
}
|
|
81
|
+
return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length];
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Avatars to display
|
|
85
|
+
const displayAvatars = avatars.slice(0, max);
|
|
86
|
+
const remaining = avatars.length - max;
|
|
87
|
+
|
|
88
|
+
// Render single avatar
|
|
89
|
+
const renderAvatar = (name: string, index: number) => {
|
|
90
|
+
const initials = getInitials(name);
|
|
91
|
+
const bgColor = getColor(name);
|
|
92
|
+
const padding = " ".repeat(Math.floor((dims.width - initials.length) / 2));
|
|
93
|
+
|
|
94
|
+
return React.createElement(
|
|
95
|
+
Box,
|
|
96
|
+
{
|
|
97
|
+
key: index,
|
|
98
|
+
marginLeft: index > 0 ? -1 : 0,
|
|
99
|
+
},
|
|
100
|
+
React.createElement(
|
|
101
|
+
Text,
|
|
102
|
+
{
|
|
103
|
+
backgroundColor: bgColor,
|
|
104
|
+
color: "#ffffff",
|
|
105
|
+
bold: true,
|
|
106
|
+
},
|
|
107
|
+
`${padding}${initials}${padding}`
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Render overflow indicator
|
|
113
|
+
const renderOverflow = () => {
|
|
114
|
+
if (remaining <= 0) return null;
|
|
115
|
+
|
|
116
|
+
const text = `+${remaining}`;
|
|
117
|
+
const padding = " ".repeat(
|
|
118
|
+
Math.max(0, Math.floor((dims.width - text.length) / 2))
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return React.createElement(
|
|
122
|
+
Box,
|
|
123
|
+
{ marginLeft: -1 },
|
|
124
|
+
React.createElement(
|
|
125
|
+
Text,
|
|
126
|
+
{
|
|
127
|
+
backgroundColor: tokens.colors.border,
|
|
128
|
+
color: tokens.colors.fg,
|
|
129
|
+
},
|
|
130
|
+
`${padding}${text}${padding}`
|
|
131
|
+
)
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const avatarElements =
|
|
136
|
+
direction === "right" ? displayAvatars : displayAvatars.reverse();
|
|
137
|
+
|
|
138
|
+
return React.createElement(
|
|
139
|
+
Box,
|
|
140
|
+
{ flexDirection: "row" },
|
|
141
|
+
avatarElements.map(renderAvatar),
|
|
142
|
+
renderOverflow()
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import type { Tokens } from "@hauktui/tokens";
|
|
4
|
+
import { useTokens } from "@hauktui/primitives-ink";
|
|
5
|
+
|
|
6
|
+
export type BadgeVariant =
|
|
7
|
+
| "default"
|
|
8
|
+
| "primary"
|
|
9
|
+
| "success"
|
|
10
|
+
| "warning"
|
|
11
|
+
| "danger"
|
|
12
|
+
| "muted";
|
|
13
|
+
|
|
14
|
+
export interface BadgeProps {
|
|
15
|
+
/** Badge content */
|
|
16
|
+
children: React.ReactNode;
|
|
17
|
+
/** Badge variant */
|
|
18
|
+
variant?: BadgeVariant;
|
|
19
|
+
/** Custom tokens override */
|
|
20
|
+
tokens?: Tokens;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Badge({
|
|
24
|
+
children,
|
|
25
|
+
variant = "default",
|
|
26
|
+
tokens: propTokens,
|
|
27
|
+
}: BadgeProps): React.ReactElement {
|
|
28
|
+
const contextTokens = useTokens();
|
|
29
|
+
const tokens = propTokens ?? contextTokens;
|
|
30
|
+
|
|
31
|
+
const variantColors: Record<BadgeVariant, { bg: string; fg: string }> = {
|
|
32
|
+
default: { bg: tokens.colors.border, fg: tokens.colors.fg },
|
|
33
|
+
primary: { bg: tokens.colors.accent, fg: tokens.colors.bg },
|
|
34
|
+
success: { bg: tokens.colors.success, fg: tokens.colors.bg },
|
|
35
|
+
warning: { bg: tokens.colors.warning, fg: tokens.colors.bg },
|
|
36
|
+
danger: { bg: tokens.colors.danger, fg: tokens.colors.bg },
|
|
37
|
+
muted: { bg: tokens.colors.muted, fg: tokens.colors.bg },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const colors = variantColors[variant];
|
|
41
|
+
|
|
42
|
+
return React.createElement(
|
|
43
|
+
Text,
|
|
44
|
+
{
|
|
45
|
+
backgroundColor: colors.bg,
|
|
46
|
+
color: colors.fg,
|
|
47
|
+
},
|
|
48
|
+
" ",
|
|
49
|
+
children,
|
|
50
|
+
" "
|
|
51
|
+
);
|
|
52
|
+
}
|