@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,202 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState, useCallback } 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 FormState {
|
|
7
|
+
values: Record<string, unknown>;
|
|
8
|
+
errors: Record<string, string>;
|
|
9
|
+
touched: Record<string, boolean>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface FormContextValue {
|
|
13
|
+
values: Record<string, unknown>;
|
|
14
|
+
errors: Record<string, string>;
|
|
15
|
+
touched: Record<string, boolean>;
|
|
16
|
+
setValue: (name: string, value: unknown) => void;
|
|
17
|
+
setError: (name: string, error: string) => void;
|
|
18
|
+
setTouched: (name: string) => void;
|
|
19
|
+
handleSubmit: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const FormContext = createContext<FormContextValue | null>(null);
|
|
23
|
+
|
|
24
|
+
export function useFormContext(): FormContextValue {
|
|
25
|
+
const context = useContext(FormContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("useFormContext must be used within a Form");
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FormProps {
|
|
33
|
+
/** Initial form values */
|
|
34
|
+
defaultValues?: Record<string, unknown>;
|
|
35
|
+
/** Callback when form is submitted */
|
|
36
|
+
onSubmit?: (values: Record<string, unknown>) => void;
|
|
37
|
+
/** Validation function */
|
|
38
|
+
validate?: (values: Record<string, unknown>) => Record<string, string>;
|
|
39
|
+
/** Form children */
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
/** Custom tokens override */
|
|
42
|
+
tokens?: Tokens;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function Form({
|
|
46
|
+
defaultValues = {},
|
|
47
|
+
onSubmit,
|
|
48
|
+
validate,
|
|
49
|
+
children,
|
|
50
|
+
tokens: propTokens,
|
|
51
|
+
}: FormProps): React.ReactElement {
|
|
52
|
+
const contextTokens = useTokens();
|
|
53
|
+
const tokens = propTokens ?? contextTokens;
|
|
54
|
+
|
|
55
|
+
const [values, setValues] = useState<Record<string, unknown>>(defaultValues);
|
|
56
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
57
|
+
const [touched, setTouchedState] = useState<Record<string, boolean>>({});
|
|
58
|
+
|
|
59
|
+
const setValue = useCallback((name: string, value: unknown) => {
|
|
60
|
+
setValues((prev) => ({ ...prev, [name]: value }));
|
|
61
|
+
// Clear error when value changes
|
|
62
|
+
setErrors((prev) => {
|
|
63
|
+
const next = { ...prev };
|
|
64
|
+
delete next[name];
|
|
65
|
+
return next;
|
|
66
|
+
});
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const setError = useCallback((name: string, error: string) => {
|
|
70
|
+
setErrors((prev) => ({ ...prev, [name]: error }));
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
const setTouched = useCallback((name: string) => {
|
|
74
|
+
setTouchedState((prev) => ({ ...prev, [name]: true }));
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const handleSubmit = useCallback(() => {
|
|
78
|
+
// Validate if validator provided
|
|
79
|
+
if (validate) {
|
|
80
|
+
const validationErrors = validate(values);
|
|
81
|
+
if (Object.keys(validationErrors).length > 0) {
|
|
82
|
+
setErrors(validationErrors);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
onSubmit?.(values);
|
|
87
|
+
}, [values, validate, onSubmit]);
|
|
88
|
+
|
|
89
|
+
const contextValue: FormContextValue = {
|
|
90
|
+
values,
|
|
91
|
+
errors,
|
|
92
|
+
touched,
|
|
93
|
+
setValue,
|
|
94
|
+
setError,
|
|
95
|
+
setTouched,
|
|
96
|
+
handleSubmit,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return React.createElement(
|
|
100
|
+
FormContext.Provider,
|
|
101
|
+
{ value: contextValue },
|
|
102
|
+
React.createElement(Box, { flexDirection: "column", gap: 1 }, children)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface FormFieldProps {
|
|
107
|
+
/** Field name */
|
|
108
|
+
name: string;
|
|
109
|
+
/** Field label */
|
|
110
|
+
label?: string;
|
|
111
|
+
/** Whether field is required */
|
|
112
|
+
required?: boolean;
|
|
113
|
+
/** Help text */
|
|
114
|
+
description?: string;
|
|
115
|
+
/** Field children (input component) */
|
|
116
|
+
children: React.ReactElement;
|
|
117
|
+
/** Custom tokens override */
|
|
118
|
+
tokens?: Tokens;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function FormField({
|
|
122
|
+
name,
|
|
123
|
+
label,
|
|
124
|
+
required,
|
|
125
|
+
description,
|
|
126
|
+
children,
|
|
127
|
+
tokens: propTokens,
|
|
128
|
+
}: FormFieldProps): React.ReactElement {
|
|
129
|
+
const contextTokens = useTokens();
|
|
130
|
+
const tokens = propTokens ?? contextTokens;
|
|
131
|
+
const form = useFormContext();
|
|
132
|
+
|
|
133
|
+
const error = form.errors[name];
|
|
134
|
+
const isTouched = form.touched[name];
|
|
135
|
+
|
|
136
|
+
return React.createElement(
|
|
137
|
+
Box,
|
|
138
|
+
{ flexDirection: "column" },
|
|
139
|
+
label
|
|
140
|
+
? React.createElement(
|
|
141
|
+
Box,
|
|
142
|
+
{ gap: 1 },
|
|
143
|
+
React.createElement(
|
|
144
|
+
Text,
|
|
145
|
+
{ color: tokens.colors.fg, bold: true },
|
|
146
|
+
label
|
|
147
|
+
),
|
|
148
|
+
required
|
|
149
|
+
? React.createElement(Text, { color: tokens.colors.danger }, "*")
|
|
150
|
+
: null
|
|
151
|
+
)
|
|
152
|
+
: null,
|
|
153
|
+
description
|
|
154
|
+
? React.createElement(
|
|
155
|
+
Text,
|
|
156
|
+
{ color: tokens.colors.muted, dimColor: true },
|
|
157
|
+
description
|
|
158
|
+
)
|
|
159
|
+
: null,
|
|
160
|
+
children,
|
|
161
|
+
error && isTouched
|
|
162
|
+
? React.createElement(Text, { color: tokens.colors.danger }, `✗ ${error}`)
|
|
163
|
+
: null
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export interface FormMessageProps {
|
|
168
|
+
/** Message type */
|
|
169
|
+
type?: "error" | "success" | "info";
|
|
170
|
+
/** Message content */
|
|
171
|
+
children: React.ReactNode;
|
|
172
|
+
/** Custom tokens override */
|
|
173
|
+
tokens?: Tokens;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function FormMessage({
|
|
177
|
+
type = "error",
|
|
178
|
+
children,
|
|
179
|
+
tokens: propTokens,
|
|
180
|
+
}: FormMessageProps): React.ReactElement {
|
|
181
|
+
const contextTokens = useTokens();
|
|
182
|
+
const tokens = propTokens ?? contextTokens;
|
|
183
|
+
|
|
184
|
+
const colors = {
|
|
185
|
+
error: tokens.colors.danger,
|
|
186
|
+
success: tokens.colors.success,
|
|
187
|
+
info: tokens.colors.accent,
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
const icons = {
|
|
191
|
+
error: "✗",
|
|
192
|
+
success: "✓",
|
|
193
|
+
info: "ℹ",
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return React.createElement(
|
|
197
|
+
Box,
|
|
198
|
+
{ gap: 1 },
|
|
199
|
+
React.createElement(Text, { color: colors[type] }, icons[type]),
|
|
200
|
+
React.createElement(Text, { color: colors[type] }, children)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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 HoverCardProps {
|
|
7
|
+
/** The trigger element */
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/** Content to show in the hover card */
|
|
10
|
+
content: React.ReactNode;
|
|
11
|
+
/** Whether the hover card is visible */
|
|
12
|
+
open: boolean;
|
|
13
|
+
/** Position of the card */
|
|
14
|
+
position?: "top" | "bottom" | "left" | "right";
|
|
15
|
+
/** Width of the card */
|
|
16
|
+
width?: number;
|
|
17
|
+
/** Custom tokens override */
|
|
18
|
+
tokens?: Tokens;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function HoverCard({
|
|
22
|
+
children,
|
|
23
|
+
content,
|
|
24
|
+
open,
|
|
25
|
+
position = "bottom",
|
|
26
|
+
width = 30,
|
|
27
|
+
tokens: propTokens,
|
|
28
|
+
}: HoverCardProps): React.ReactElement {
|
|
29
|
+
const contextTokens = useTokens();
|
|
30
|
+
const tokens = propTokens ?? contextTokens;
|
|
31
|
+
|
|
32
|
+
const cardElement = React.createElement(
|
|
33
|
+
Box,
|
|
34
|
+
{
|
|
35
|
+
flexDirection: "column",
|
|
36
|
+
borderStyle: "round",
|
|
37
|
+
borderColor: tokens.colors.border,
|
|
38
|
+
width,
|
|
39
|
+
paddingX: 1,
|
|
40
|
+
paddingY: 0,
|
|
41
|
+
marginTop: position === "bottom" ? 0 : undefined,
|
|
42
|
+
marginBottom: position === "top" ? 0 : undefined,
|
|
43
|
+
},
|
|
44
|
+
content
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (!open) {
|
|
48
|
+
return React.createElement(Box, null, children);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isVertical = position === "top" || position === "bottom";
|
|
52
|
+
|
|
53
|
+
const containerProps: Record<string, unknown> = {
|
|
54
|
+
flexDirection: isVertical ? "column" : "row",
|
|
55
|
+
alignItems: isVertical ? "flex-start" : "center",
|
|
56
|
+
gap: 0,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (position === "top") {
|
|
60
|
+
return React.createElement(Box, containerProps, cardElement, children);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (position === "bottom") {
|
|
64
|
+
return React.createElement(Box, containerProps, children, cardElement);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (position === "left") {
|
|
68
|
+
return React.createElement(Box, containerProps, cardElement, children);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return React.createElement(Box, containerProps, children, cardElement);
|
|
72
|
+
}
|
|
@@ -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 } from "@hauktui/core";
|
|
6
|
+
|
|
7
|
+
export interface InputOTPProps {
|
|
8
|
+
/** Number of OTP digits */
|
|
9
|
+
length?: number;
|
|
10
|
+
/** Controlled value */
|
|
11
|
+
value?: string;
|
|
12
|
+
/** Callback when value changes */
|
|
13
|
+
onChange?: (value: string) => void;
|
|
14
|
+
/** Callback when all digits are filled */
|
|
15
|
+
onComplete?: (value: string) => void;
|
|
16
|
+
/** Whether to mask the input */
|
|
17
|
+
mask?: boolean;
|
|
18
|
+
/** Mask character */
|
|
19
|
+
maskChar?: string;
|
|
20
|
+
/** Whether the input is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Custom tokens override */
|
|
23
|
+
tokens?: Tokens;
|
|
24
|
+
/** Focus ID for focus management */
|
|
25
|
+
focusId?: string;
|
|
26
|
+
/** Label text */
|
|
27
|
+
label?: string;
|
|
28
|
+
/** Separator between groups */
|
|
29
|
+
separator?: string;
|
|
30
|
+
/** Group size (for visual grouping) */
|
|
31
|
+
groupSize?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function InputOTP({
|
|
35
|
+
length = 6,
|
|
36
|
+
value: controlledValue,
|
|
37
|
+
onChange,
|
|
38
|
+
onComplete,
|
|
39
|
+
mask = false,
|
|
40
|
+
maskChar = "●",
|
|
41
|
+
disabled = false,
|
|
42
|
+
tokens: propTokens,
|
|
43
|
+
focusId,
|
|
44
|
+
label,
|
|
45
|
+
separator = "-",
|
|
46
|
+
groupSize = 3,
|
|
47
|
+
}: InputOTPProps): React.ReactElement {
|
|
48
|
+
const contextTokens = useTokens();
|
|
49
|
+
const tokens = propTokens ?? contextTokens;
|
|
50
|
+
const id = focusId ?? stableId("input-otp");
|
|
51
|
+
const { isFocused } = useFocusable(id);
|
|
52
|
+
|
|
53
|
+
const [internalValue, setInternalValue] = useState("");
|
|
54
|
+
const isControlled = controlledValue !== undefined;
|
|
55
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
56
|
+
|
|
57
|
+
const updateValue = useCallback(
|
|
58
|
+
(newValue: string) => {
|
|
59
|
+
// Only allow digits
|
|
60
|
+
const digits = newValue.replace(/\D/g, "").slice(0, length);
|
|
61
|
+
|
|
62
|
+
if (!isControlled) {
|
|
63
|
+
setInternalValue(digits);
|
|
64
|
+
}
|
|
65
|
+
onChange?.(digits);
|
|
66
|
+
|
|
67
|
+
if (digits.length === length) {
|
|
68
|
+
onComplete?.(digits);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
[isControlled, length, onChange, onComplete]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
useInput(
|
|
75
|
+
(input, key) => {
|
|
76
|
+
if (!isFocused || disabled) return;
|
|
77
|
+
|
|
78
|
+
if (key.backspace || key.delete) {
|
|
79
|
+
updateValue(currentValue.slice(0, -1));
|
|
80
|
+
} else if (/^\d$/.test(input)) {
|
|
81
|
+
updateValue(currentValue + input);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
{ isActive: isFocused }
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Render OTP boxes
|
|
88
|
+
const renderBoxes = () => {
|
|
89
|
+
const boxes: React.ReactElement[] = [];
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < length; i++) {
|
|
92
|
+
const char = currentValue[i];
|
|
93
|
+
const isCursor = i === currentValue.length && isFocused;
|
|
94
|
+
const isFilled = char !== undefined;
|
|
95
|
+
|
|
96
|
+
// Add separator between groups
|
|
97
|
+
if (i > 0 && groupSize > 0 && i % groupSize === 0) {
|
|
98
|
+
boxes.push(
|
|
99
|
+
React.createElement(
|
|
100
|
+
Text,
|
|
101
|
+
{ key: `sep-${i}`, color: tokens.colors.muted },
|
|
102
|
+
` ${separator} `
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const displayChar = isFilled
|
|
108
|
+
? mask
|
|
109
|
+
? maskChar
|
|
110
|
+
: char
|
|
111
|
+
: isCursor
|
|
112
|
+
? "▎"
|
|
113
|
+
: " ";
|
|
114
|
+
|
|
115
|
+
boxes.push(
|
|
116
|
+
React.createElement(
|
|
117
|
+
Box,
|
|
118
|
+
{
|
|
119
|
+
key: i,
|
|
120
|
+
borderStyle: "single",
|
|
121
|
+
borderColor: isCursor
|
|
122
|
+
? tokens.colors.focus
|
|
123
|
+
: isFilled
|
|
124
|
+
? tokens.colors.accent
|
|
125
|
+
: tokens.colors.border,
|
|
126
|
+
paddingX: 1,
|
|
127
|
+
},
|
|
128
|
+
React.createElement(
|
|
129
|
+
Text,
|
|
130
|
+
{
|
|
131
|
+
color: isCursor
|
|
132
|
+
? tokens.colors.focus
|
|
133
|
+
: isFilled
|
|
134
|
+
? tokens.colors.fg
|
|
135
|
+
: tokens.colors.muted,
|
|
136
|
+
bold: isFilled,
|
|
137
|
+
},
|
|
138
|
+
displayChar
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return boxes;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return React.createElement(
|
|
148
|
+
Box,
|
|
149
|
+
{ flexDirection: "column" },
|
|
150
|
+
// Label
|
|
151
|
+
label
|
|
152
|
+
? React.createElement(
|
|
153
|
+
Text,
|
|
154
|
+
{ color: tokens.colors.fg, bold: true },
|
|
155
|
+
label
|
|
156
|
+
)
|
|
157
|
+
: null,
|
|
158
|
+
// OTP boxes
|
|
159
|
+
React.createElement(
|
|
160
|
+
Box,
|
|
161
|
+
{
|
|
162
|
+
gap: 0,
|
|
163
|
+
borderStyle: isFocused ? "round" : undefined,
|
|
164
|
+
borderColor: isFocused ? tokens.colors.focus : undefined,
|
|
165
|
+
paddingX: isFocused ? 1 : 0,
|
|
166
|
+
},
|
|
167
|
+
...renderBoxes()
|
|
168
|
+
),
|
|
169
|
+
// Status
|
|
170
|
+
React.createElement(
|
|
171
|
+
Text,
|
|
172
|
+
{ color: tokens.colors.muted, dimColor: true },
|
|
173
|
+
`${currentValue.length}/${length} digits`
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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 KbdProps {
|
|
7
|
+
/** Key or key combination to display */
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/** Custom tokens override */
|
|
10
|
+
tokens?: Tokens;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Kbd({
|
|
14
|
+
children,
|
|
15
|
+
tokens: propTokens,
|
|
16
|
+
}: KbdProps): React.ReactElement {
|
|
17
|
+
const contextTokens = useTokens();
|
|
18
|
+
const tokens = propTokens ?? contextTokens;
|
|
19
|
+
|
|
20
|
+
return React.createElement(
|
|
21
|
+
Text,
|
|
22
|
+
{
|
|
23
|
+
backgroundColor: tokens.colors.border,
|
|
24
|
+
color: tokens.colors.fg,
|
|
25
|
+
},
|
|
26
|
+
" ",
|
|
27
|
+
children,
|
|
28
|
+
" "
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 LabelProps {
|
|
7
|
+
/** Label text */
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
/** Whether the associated field is required */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Custom tokens override */
|
|
12
|
+
tokens?: Tokens;
|
|
13
|
+
/** Label position relative to its content */
|
|
14
|
+
position?: "top" | "left";
|
|
15
|
+
/** Description text below the label */
|
|
16
|
+
description?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Label({
|
|
20
|
+
children,
|
|
21
|
+
required = false,
|
|
22
|
+
tokens: propTokens,
|
|
23
|
+
position = "top",
|
|
24
|
+
description,
|
|
25
|
+
}: LabelProps): React.ReactElement {
|
|
26
|
+
const contextTokens = useTokens();
|
|
27
|
+
const tokens = propTokens ?? contextTokens;
|
|
28
|
+
|
|
29
|
+
const labelContent = React.createElement(
|
|
30
|
+
Box,
|
|
31
|
+
{ gap: 0 },
|
|
32
|
+
React.createElement(
|
|
33
|
+
Text,
|
|
34
|
+
{ color: tokens.colors.fg, bold: true },
|
|
35
|
+
children
|
|
36
|
+
),
|
|
37
|
+
required
|
|
38
|
+
? React.createElement(Text, { color: tokens.colors.danger }, " *")
|
|
39
|
+
: null
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (description) {
|
|
43
|
+
return React.createElement(
|
|
44
|
+
Box,
|
|
45
|
+
{ flexDirection: "column" },
|
|
46
|
+
labelContent,
|
|
47
|
+
React.createElement(
|
|
48
|
+
Text,
|
|
49
|
+
{ color: tokens.colors.muted, dimColor: true },
|
|
50
|
+
description
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return labelContent;
|
|
56
|
+
}
|