@k3-universe/react-kit 0.0.37 → 0.0.39
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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3163 -889
- package/dist/kit/components/keyboard/Keyboard.d.ts +28 -0
- package/dist/kit/components/keyboard/Keyboard.d.ts.map +1 -0
- package/dist/kit/components/keyboard/index.d.ts +5 -0
- package/dist/kit/components/keyboard/index.d.ts.map +1 -0
- package/dist/kit/components/numpad/Numpad.d.ts +26 -0
- package/dist/kit/components/numpad/Numpad.d.ts.map +1 -0
- package/dist/kit/components/numpad/index.d.ts +5 -0
- package/dist/kit/components/numpad/index.d.ts.map +1 -0
- package/dist/kit/themes/base.css +1 -1
- package/dist/kit/themes/clean-slate.css +161 -1
- package/dist/kit/themes/default.css +161 -1
- package/dist/kit/themes/minimal-modern.css +161 -1
- package/dist/kit/themes/spotify.css +161 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/kit/components/keyboard/Keyboard.tsx +916 -0
- package/src/kit/components/keyboard/index.ts +4 -0
- package/src/kit/components/numpad/Numpad.tsx +377 -0
- package/src/kit/components/numpad/index.ts +4 -0
- package/src/shadcn/ui/calendar.tsx +1 -1
- package/src/stories/kit/components/Keyboard.stories.tsx +263 -0
- package/src/stories/kit/components/Numpad.stories.tsx +195 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Delete, ChevronLeft } from "lucide-react";
|
|
5
|
+
import { cn } from "../../../shadcn/lib/utils";
|
|
6
|
+
import { Button } from "../../../shadcn/ui/button";
|
|
7
|
+
|
|
8
|
+
// Constants
|
|
9
|
+
const NUMBER_BUTTON_BASE_CLASSES =
|
|
10
|
+
"rounded-xl border-2 border-teal-200 bg-white text-3xl font-semibold text-teal-600 hover:text-teal-900 transition-colors hover:bg-teal-50 h-auto";
|
|
11
|
+
const CLEAR_BUTTON_CLASSES =
|
|
12
|
+
"rounded-xl border-2 border-teal-200 bg-white text-2xl font-semibold text-teal-600 hover:text-teal-900 transition-colors hover:bg-teal-50 h-auto";
|
|
13
|
+
const BACKSPACE_BUTTON_CLASSES =
|
|
14
|
+
"rounded-xl bg-red-600 text-white transition-colors hover:bg-red-700 h-auto";
|
|
15
|
+
const SUBMIT_BUTTON_CLASSES =
|
|
16
|
+
"row-span-3 rounded-xl bg-green-400 transition-colors hover:bg-green-500 h-auto";
|
|
17
|
+
|
|
18
|
+
export interface NumpadProps
|
|
19
|
+
extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
20
|
+
value?: string;
|
|
21
|
+
onChange?: (value: string) => void;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
allowDecimal?: boolean;
|
|
24
|
+
allowDoubleZero?: boolean;
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
className?: string;
|
|
27
|
+
buttonClassName?: string;
|
|
28
|
+
customKeyStyle?: React.CSSProperties;
|
|
29
|
+
customKeyClassName?: string;
|
|
30
|
+
customSubmitStyle?: React.CSSProperties;
|
|
31
|
+
customSubmitClassName?: string;
|
|
32
|
+
showBackspaceButton?: boolean;
|
|
33
|
+
showClearButton?: boolean;
|
|
34
|
+
showSubmitButton?: boolean;
|
|
35
|
+
onSubmit?: () => void;
|
|
36
|
+
inputRef?: React.RefObject<HTMLInputElement | HTMLTextAreaElement | null>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Numpad({
|
|
40
|
+
value: controlledValue,
|
|
41
|
+
onChange,
|
|
42
|
+
disabled = false,
|
|
43
|
+
allowDecimal = false,
|
|
44
|
+
allowDoubleZero = true,
|
|
45
|
+
maxLength,
|
|
46
|
+
className,
|
|
47
|
+
buttonClassName,
|
|
48
|
+
customKeyStyle,
|
|
49
|
+
customKeyClassName,
|
|
50
|
+
customSubmitStyle,
|
|
51
|
+
customSubmitClassName,
|
|
52
|
+
showBackspaceButton = true,
|
|
53
|
+
showClearButton = true,
|
|
54
|
+
showSubmitButton = false,
|
|
55
|
+
onSubmit,
|
|
56
|
+
inputRef,
|
|
57
|
+
...props
|
|
58
|
+
}: NumpadProps) {
|
|
59
|
+
const [internalValue, setInternalValue] = React.useState("");
|
|
60
|
+
const isControlled = controlledValue !== undefined;
|
|
61
|
+
const value = isControlled ? controlledValue : internalValue;
|
|
62
|
+
|
|
63
|
+
// Helper function to update input value with cursor position handling
|
|
64
|
+
const updateInputValue = React.useCallback(
|
|
65
|
+
(
|
|
66
|
+
input: HTMLInputElement | HTMLTextAreaElement,
|
|
67
|
+
newValue: string,
|
|
68
|
+
cursorPos: number
|
|
69
|
+
) => {
|
|
70
|
+
input.value = newValue;
|
|
71
|
+
input.setSelectionRange(cursorPos, cursorPos);
|
|
72
|
+
const event = new Event("input", { bubbles: true });
|
|
73
|
+
input.dispatchEvent(event);
|
|
74
|
+
|
|
75
|
+
if (!isControlled) {
|
|
76
|
+
setInternalValue(newValue);
|
|
77
|
+
}
|
|
78
|
+
onChange?.(newValue);
|
|
79
|
+
},
|
|
80
|
+
[isControlled, onChange]
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const handleValueChange = React.useCallback(
|
|
84
|
+
(newValue: string) => {
|
|
85
|
+
if (disabled) return;
|
|
86
|
+
if (maxLength && newValue.length > maxLength) return;
|
|
87
|
+
|
|
88
|
+
if (!isControlled) {
|
|
89
|
+
setInternalValue(newValue);
|
|
90
|
+
}
|
|
91
|
+
onChange?.(newValue);
|
|
92
|
+
},
|
|
93
|
+
[disabled, maxLength, isControlled, onChange]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const handleNumberClick = React.useCallback(
|
|
97
|
+
(num: string) => {
|
|
98
|
+
if (disabled) return;
|
|
99
|
+
|
|
100
|
+
if (inputRef?.current) {
|
|
101
|
+
const input = inputRef.current;
|
|
102
|
+
const start = input.selectionStart ?? 0;
|
|
103
|
+
const end = input.selectionEnd ?? 0;
|
|
104
|
+
const newValue =
|
|
105
|
+
input.value.slice(0, start) + num + input.value.slice(end);
|
|
106
|
+
const newCursorPos = start + num.length;
|
|
107
|
+
|
|
108
|
+
if (maxLength && newValue.length > maxLength) return;
|
|
109
|
+
|
|
110
|
+
updateInputValue(input, newValue, newCursorPos);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
handleValueChange(value + num);
|
|
115
|
+
},
|
|
116
|
+
[disabled, value, handleValueChange, inputRef, maxLength, updateInputValue]
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const handleDecimalClick = React.useCallback(() => {
|
|
120
|
+
if (disabled || !allowDecimal) return;
|
|
121
|
+
|
|
122
|
+
if (inputRef?.current) {
|
|
123
|
+
const input = inputRef.current;
|
|
124
|
+
if (input.value.includes(".")) return;
|
|
125
|
+
|
|
126
|
+
const start = input.selectionStart ?? 0;
|
|
127
|
+
const end = input.selectionEnd ?? 0;
|
|
128
|
+
const newValue = input.value.slice(0, start) + "." + input.value.slice(end);
|
|
129
|
+
const newCursorPos = start + 1;
|
|
130
|
+
|
|
131
|
+
if (maxLength && newValue.length > maxLength) return;
|
|
132
|
+
|
|
133
|
+
updateInputValue(input, newValue, newCursorPos);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (value.includes(".")) return;
|
|
138
|
+
handleValueChange(value + ".");
|
|
139
|
+
}, [
|
|
140
|
+
disabled,
|
|
141
|
+
allowDecimal,
|
|
142
|
+
value,
|
|
143
|
+
handleValueChange,
|
|
144
|
+
inputRef,
|
|
145
|
+
maxLength,
|
|
146
|
+
updateInputValue,
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
const handleBackspace = React.useCallback(() => {
|
|
150
|
+
if (disabled) return;
|
|
151
|
+
|
|
152
|
+
if (inputRef?.current) {
|
|
153
|
+
const input = inputRef.current;
|
|
154
|
+
const start = input.selectionStart ?? 0;
|
|
155
|
+
const end = input.selectionEnd ?? 0;
|
|
156
|
+
|
|
157
|
+
if (start === end && start === 0) return;
|
|
158
|
+
|
|
159
|
+
const newValue =
|
|
160
|
+
start !== end
|
|
161
|
+
? input.value.slice(0, start) + input.value.slice(end)
|
|
162
|
+
: input.value.slice(0, start - 1) + input.value.slice(start);
|
|
163
|
+
const newCursorPos = start !== end ? start : Math.max(0, start - 1);
|
|
164
|
+
|
|
165
|
+
updateInputValue(input, newValue, newCursorPos);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (value.length > 0) {
|
|
170
|
+
handleValueChange(value.slice(0, -1));
|
|
171
|
+
}
|
|
172
|
+
}, [disabled, value, handleValueChange, inputRef, updateInputValue]);
|
|
173
|
+
|
|
174
|
+
const handleClear = React.useCallback(() => {
|
|
175
|
+
handleValueChange("");
|
|
176
|
+
}, [handleValueChange]);
|
|
177
|
+
|
|
178
|
+
const handleSubmit = React.useCallback(() => {
|
|
179
|
+
if (disabled) return;
|
|
180
|
+
|
|
181
|
+
// If inputRef is provided, handle Enter like a real keyboard
|
|
182
|
+
if (inputRef?.current) {
|
|
183
|
+
const input = inputRef.current;
|
|
184
|
+
const isTextarea = input.tagName === 'TEXTAREA';
|
|
185
|
+
const isInput = input.tagName === 'INPUT';
|
|
186
|
+
|
|
187
|
+
if (isTextarea) {
|
|
188
|
+
const start = input.selectionStart ?? 0;
|
|
189
|
+
const end = input.selectionEnd ?? 0;
|
|
190
|
+
const newValue =
|
|
191
|
+
input.value.slice(0, start) + "\n" + input.value.slice(end);
|
|
192
|
+
const newCursorPos = start + 1;
|
|
193
|
+
updateInputValue(input, newValue, newCursorPos);
|
|
194
|
+
} else if (isInput) {
|
|
195
|
+
// For input: trigger Enter key events like real keyboard
|
|
196
|
+
// This will trigger form submission if inside a form
|
|
197
|
+
|
|
198
|
+
// Create and dispatch keydown event
|
|
199
|
+
const keyDownEvent = new KeyboardEvent('keydown', {
|
|
200
|
+
key: 'Enter',
|
|
201
|
+
code: 'Enter',
|
|
202
|
+
keyCode: 13,
|
|
203
|
+
which: 13,
|
|
204
|
+
bubbles: true,
|
|
205
|
+
cancelable: true,
|
|
206
|
+
});
|
|
207
|
+
input.dispatchEvent(keyDownEvent);
|
|
208
|
+
|
|
209
|
+
// Only trigger keypress and keyup if keydown wasn't prevented
|
|
210
|
+
if (!keyDownEvent.defaultPrevented) {
|
|
211
|
+
// Create and dispatch keypress event
|
|
212
|
+
const keyPressEvent = new KeyboardEvent('keypress', {
|
|
213
|
+
key: 'Enter',
|
|
214
|
+
code: 'Enter',
|
|
215
|
+
keyCode: 13,
|
|
216
|
+
which: 13,
|
|
217
|
+
bubbles: true,
|
|
218
|
+
cancelable: true,
|
|
219
|
+
});
|
|
220
|
+
input.dispatchEvent(keyPressEvent);
|
|
221
|
+
|
|
222
|
+
// Create and dispatch keyup event
|
|
223
|
+
const keyUpEvent = new KeyboardEvent('keyup', {
|
|
224
|
+
key: 'Enter',
|
|
225
|
+
code: 'Enter',
|
|
226
|
+
keyCode: 13,
|
|
227
|
+
which: 13,
|
|
228
|
+
bubbles: true,
|
|
229
|
+
cancelable: true,
|
|
230
|
+
});
|
|
231
|
+
input.dispatchEvent(keyUpEvent);
|
|
232
|
+
|
|
233
|
+
// If input is inside a form, trigger form submit
|
|
234
|
+
const form = input.closest('form');
|
|
235
|
+
if (form && !keyPressEvent.defaultPrevented) {
|
|
236
|
+
// Trigger form submit event
|
|
237
|
+
const submitEvent = new Event('submit', {
|
|
238
|
+
bubbles: true,
|
|
239
|
+
cancelable: true,
|
|
240
|
+
});
|
|
241
|
+
form.dispatchEvent(submitEvent);
|
|
242
|
+
|
|
243
|
+
// If submit wasn't prevented, actually submit the form
|
|
244
|
+
if (!submitEvent.defaultPrevented) {
|
|
245
|
+
form.requestSubmit();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
onSubmit?.();
|
|
253
|
+
}, [disabled, onSubmit, inputRef, updateInputValue]);
|
|
254
|
+
|
|
255
|
+
// Helper component for number buttons
|
|
256
|
+
const NumberButton = React.useCallback(
|
|
257
|
+
({ num, onClick }: { num: string; onClick: () => void }) => (
|
|
258
|
+
<Button
|
|
259
|
+
type="button"
|
|
260
|
+
disabled={disabled}
|
|
261
|
+
onClick={onClick}
|
|
262
|
+
style={customKeyStyle}
|
|
263
|
+
className={cn(
|
|
264
|
+
NUMBER_BUTTON_BASE_CLASSES,
|
|
265
|
+
buttonClassName,
|
|
266
|
+
customKeyClassName
|
|
267
|
+
)}
|
|
268
|
+
>
|
|
269
|
+
{num}
|
|
270
|
+
</Button>
|
|
271
|
+
),
|
|
272
|
+
[disabled, customKeyStyle, buttonClassName, customKeyClassName]
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<div className={cn("grid grid-cols-4 gap-3 w-fit", className)} {...props}>
|
|
277
|
+
{/* Layout: 1,2,3,Delete | 4,5,6,Submit | 7,8,9 | 0,00,000 */}
|
|
278
|
+
{/* Row 1: 1, 2, 3, Delete */}
|
|
279
|
+
<NumberButton num="1" onClick={() => handleNumberClick("1")} />
|
|
280
|
+
<NumberButton num="2" onClick={() => handleNumberClick("2")} />
|
|
281
|
+
<NumberButton num="3" onClick={() => handleNumberClick("3")} />
|
|
282
|
+
{showBackspaceButton && (
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
disabled={disabled || value.length === 0}
|
|
286
|
+
onClick={handleBackspace}
|
|
287
|
+
style={customKeyStyle}
|
|
288
|
+
className={cn(
|
|
289
|
+
BACKSPACE_BUTTON_CLASSES,
|
|
290
|
+
buttonClassName,
|
|
291
|
+
customKeyClassName
|
|
292
|
+
)}
|
|
293
|
+
>
|
|
294
|
+
<Delete className="mx-auto h-8 w-8" />
|
|
295
|
+
</button>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{/* Row 2: 4, 5, 6, Clear */}
|
|
299
|
+
<NumberButton num="4" onClick={() => handleNumberClick("4")} />
|
|
300
|
+
<NumberButton num="5" onClick={() => handleNumberClick("5")} />
|
|
301
|
+
<NumberButton num="6" onClick={() => handleNumberClick("6")} />
|
|
302
|
+
{showClearButton && (
|
|
303
|
+
<Button
|
|
304
|
+
type="button"
|
|
305
|
+
disabled={disabled || value.length === 0}
|
|
306
|
+
onClick={handleClear}
|
|
307
|
+
style={customKeyStyle}
|
|
308
|
+
className={cn(
|
|
309
|
+
CLEAR_BUTTON_CLASSES,
|
|
310
|
+
buttonClassName,
|
|
311
|
+
customKeyClassName
|
|
312
|
+
)}
|
|
313
|
+
>
|
|
314
|
+
Clear
|
|
315
|
+
</Button>
|
|
316
|
+
)}
|
|
317
|
+
{showSubmitButton && (
|
|
318
|
+
<>
|
|
319
|
+
<div />
|
|
320
|
+
<div />
|
|
321
|
+
<div />
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
disabled={disabled}
|
|
325
|
+
onClick={handleSubmit}
|
|
326
|
+
style={customSubmitStyle}
|
|
327
|
+
className={cn(
|
|
328
|
+
SUBMIT_BUTTON_CLASSES,
|
|
329
|
+
buttonClassName,
|
|
330
|
+
customSubmitClassName
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
333
|
+
<ChevronLeft className="mx-auto h-12 w-12 rotate-180 text-gray-800" />
|
|
334
|
+
</button>
|
|
335
|
+
</>
|
|
336
|
+
)}
|
|
337
|
+
|
|
338
|
+
{/* Row 3: 7, 8, 9 */}
|
|
339
|
+
<NumberButton num="7" onClick={() => handleNumberClick("7")} />
|
|
340
|
+
<NumberButton num="8" onClick={() => handleNumberClick("8")} />
|
|
341
|
+
<NumberButton num="9" onClick={() => handleNumberClick("9")} />
|
|
342
|
+
|
|
343
|
+
{/* Row 4: 0, 00, 000 */}
|
|
344
|
+
<NumberButton num="0" onClick={() => handleNumberClick("0")} />
|
|
345
|
+
{allowDoubleZero ? (
|
|
346
|
+
<>
|
|
347
|
+
<NumberButton num="00" onClick={() => handleNumberClick("00")} />
|
|
348
|
+
<NumberButton num="000" onClick={() => handleNumberClick("000")} />
|
|
349
|
+
</>
|
|
350
|
+
) : allowDecimal ? (
|
|
351
|
+
<Button
|
|
352
|
+
type="button"
|
|
353
|
+
disabled={disabled}
|
|
354
|
+
onClick={handleDecimalClick}
|
|
355
|
+
style={customKeyStyle}
|
|
356
|
+
className={cn(
|
|
357
|
+
NUMBER_BUTTON_BASE_CLASSES,
|
|
358
|
+
buttonClassName,
|
|
359
|
+
customKeyClassName
|
|
360
|
+
)}
|
|
361
|
+
>
|
|
362
|
+
.
|
|
363
|
+
</Button>
|
|
364
|
+
) : (
|
|
365
|
+
<>
|
|
366
|
+
<div />
|
|
367
|
+
<div />
|
|
368
|
+
</>
|
|
369
|
+
)}
|
|
370
|
+
{showSubmitButton && <div />}
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
Numpad.displayName = "Numpad";
|
|
376
|
+
|
|
377
|
+
export default Numpad;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Keyboard } from "../../../kit/components/keyboard/Keyboard";
|
|
4
|
+
import { Input } from "../../../shadcn/ui/input";
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Keyboard> = {
|
|
7
|
+
title: "Kit/Components/Keyboard",
|
|
8
|
+
component: Keyboard,
|
|
9
|
+
parameters: {
|
|
10
|
+
controls: { expanded: true },
|
|
11
|
+
backgrounds: { disable: true },
|
|
12
|
+
},
|
|
13
|
+
argTypes: {
|
|
14
|
+
layout: {
|
|
15
|
+
control: "select",
|
|
16
|
+
options: ["qwerty", "qwertz", "azerty"],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
|
|
23
|
+
type Story = StoryObj<typeof Keyboard>;
|
|
24
|
+
|
|
25
|
+
export const FullKeyboard: Story = {
|
|
26
|
+
name: "Full Keyboard",
|
|
27
|
+
render: (args) => {
|
|
28
|
+
const [value, setValue] = React.useState("");
|
|
29
|
+
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="p-6 space-y-4">
|
|
33
|
+
<div>
|
|
34
|
+
<div className="text-sm text-muted-foreground mb-2">Text Input</div>
|
|
35
|
+
<Input
|
|
36
|
+
ref={inputRef}
|
|
37
|
+
type="text"
|
|
38
|
+
value={value}
|
|
39
|
+
onChange={(e) => {
|
|
40
|
+
setValue((e.target as any).value);
|
|
41
|
+
}}
|
|
42
|
+
placeholder="Type here..."
|
|
43
|
+
className="min-h-[60px]"
|
|
44
|
+
/>
|
|
45
|
+
</div>
|
|
46
|
+
<Keyboard
|
|
47
|
+
{...args}
|
|
48
|
+
value={value}
|
|
49
|
+
onChange={setValue}
|
|
50
|
+
inputRef={inputRef}
|
|
51
|
+
showNumbers
|
|
52
|
+
showShift
|
|
53
|
+
showSpace
|
|
54
|
+
showBackspace
|
|
55
|
+
showEnter
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const Disabled: Story = {
|
|
63
|
+
name: "Disabled",
|
|
64
|
+
render: (args) => {
|
|
65
|
+
const [value, setValue] = React.useState("Hello World");
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="p-6 space-y-4">
|
|
69
|
+
<div>
|
|
70
|
+
<div className="text-sm text-muted-foreground mb-2">Text Input</div>
|
|
71
|
+
<Input
|
|
72
|
+
type="text"
|
|
73
|
+
value={value}
|
|
74
|
+
onChange={(e) => setValue((e.target as any).value)}
|
|
75
|
+
placeholder="Type here..."
|
|
76
|
+
disabled
|
|
77
|
+
className="min-h-[60px]"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<Keyboard {...args} value={value} onChange={setValue} disabled />
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const CustomStyling: Story = {
|
|
87
|
+
name: "Custom Styling",
|
|
88
|
+
render: (args) => {
|
|
89
|
+
const [value, setValue] = React.useState("");
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="flex-1 p-6 space-y-4">
|
|
93
|
+
<div>
|
|
94
|
+
<div className="text-sm text-muted-foreground mb-2">Text Input</div>
|
|
95
|
+
<Input
|
|
96
|
+
type="text"
|
|
97
|
+
value={value}
|
|
98
|
+
onChange={(e) => {
|
|
99
|
+
setValue((e.target as any).value);
|
|
100
|
+
}}
|
|
101
|
+
placeholder="Type here..."
|
|
102
|
+
className="min-h-[60px]"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
<Keyboard
|
|
106
|
+
{...args}
|
|
107
|
+
value={value}
|
|
108
|
+
onChange={setValue}
|
|
109
|
+
showNumbers
|
|
110
|
+
showShift
|
|
111
|
+
showSpace
|
|
112
|
+
showBackspace
|
|
113
|
+
showEnter
|
|
114
|
+
customKeyStyle={{ minWidth: "50px", height: "45px" }}
|
|
115
|
+
customKeyClassName="text-base font-bold"
|
|
116
|
+
/>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const MultipleInputs: Story = {
|
|
123
|
+
name: "Multiple Inputs (Focused)",
|
|
124
|
+
render: (args) => {
|
|
125
|
+
const [value1, setValue1] = React.useState("");
|
|
126
|
+
const [value2, setValue2] = React.useState("");
|
|
127
|
+
const [value3, setValue3] = React.useState("");
|
|
128
|
+
const [value4, setValue4] = React.useState("");
|
|
129
|
+
const [value5, setValue5] = React.useState("");
|
|
130
|
+
const [focusedInput, setFocusedInput] = React.useState<
|
|
131
|
+
"input1" | "input2" | "input3" | "input4" | "input5"
|
|
132
|
+
>("input1");
|
|
133
|
+
|
|
134
|
+
const input1Ref = React.useRef<HTMLInputElement>(null);
|
|
135
|
+
const input2Ref = React.useRef<HTMLInputElement>(null);
|
|
136
|
+
const input3Ref = React.useRef<HTMLInputElement>(null);
|
|
137
|
+
const input4Ref = React.useRef<HTMLInputElement>(null);
|
|
138
|
+
const input5Ref = React.useRef<HTMLInputElement>(null);
|
|
139
|
+
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
140
|
+
|
|
141
|
+
const values = {
|
|
142
|
+
input1: value1,
|
|
143
|
+
input2: value2,
|
|
144
|
+
input3: value3,
|
|
145
|
+
input4: value4,
|
|
146
|
+
input5: value5,
|
|
147
|
+
};
|
|
148
|
+
const setters = {
|
|
149
|
+
input1: setValue1,
|
|
150
|
+
input2: setValue2,
|
|
151
|
+
input3: setValue3,
|
|
152
|
+
input4: setValue4,
|
|
153
|
+
input5: setValue5,
|
|
154
|
+
};
|
|
155
|
+
const refs = {
|
|
156
|
+
input1: input1Ref,
|
|
157
|
+
input2: input2Ref,
|
|
158
|
+
input3: input3Ref,
|
|
159
|
+
input4: input4Ref,
|
|
160
|
+
input5: input5Ref,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const currentValue = values[focusedInput];
|
|
164
|
+
const setCurrentValue = setters[focusedInput];
|
|
165
|
+
|
|
166
|
+
const handleInputFocus = (
|
|
167
|
+
input: "input1" | "input2" | "input3" | "input4" | "input5"
|
|
168
|
+
) => {
|
|
169
|
+
setFocusedInput(input);
|
|
170
|
+
|
|
171
|
+
// Scroll the focused input into view
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
const inputElement = refs[input].current;
|
|
174
|
+
if (inputElement && containerRef.current) {
|
|
175
|
+
(inputElement as any).scrollIntoView?.({
|
|
176
|
+
behavior: "smooth",
|
|
177
|
+
block: "center",
|
|
178
|
+
inline: "nearest",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}, 100);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const inputs = [
|
|
185
|
+
{
|
|
186
|
+
id: "input1" as const,
|
|
187
|
+
label: "Text Input 1",
|
|
188
|
+
value: value1,
|
|
189
|
+
setValue: setValue1,
|
|
190
|
+
ref: input1Ref,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: "input2" as const,
|
|
194
|
+
label: "Text Input 2",
|
|
195
|
+
value: value2,
|
|
196
|
+
setValue: setValue2,
|
|
197
|
+
ref: input2Ref,
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "input3" as const,
|
|
201
|
+
label: "Text Input 3",
|
|
202
|
+
value: value3,
|
|
203
|
+
setValue: setValue3,
|
|
204
|
+
ref: input3Ref,
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: "input4" as const,
|
|
208
|
+
label: "Text Input 4",
|
|
209
|
+
value: value4,
|
|
210
|
+
setValue: setValue4,
|
|
211
|
+
ref: input4Ref,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: "input5" as const,
|
|
215
|
+
label: "Text Input 5",
|
|
216
|
+
value: value5,
|
|
217
|
+
setValue: setValue5,
|
|
218
|
+
ref: input5Ref,
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div
|
|
224
|
+
ref={containerRef}
|
|
225
|
+
className="h-screen p-6 pt-40 pb-96 space-y-4 overflow-y-auto relative flex-1"
|
|
226
|
+
>
|
|
227
|
+
<div className="flex-1 w-full space-y-4 flex flex-col">
|
|
228
|
+
{inputs.map((input) => (
|
|
229
|
+
<div key={input.id}>
|
|
230
|
+
<div className="text-sm text-muted-foreground mb-2">
|
|
231
|
+
{input.label}
|
|
232
|
+
</div>
|
|
233
|
+
<Input
|
|
234
|
+
ref={input.ref}
|
|
235
|
+
type="text"
|
|
236
|
+
value={input.value}
|
|
237
|
+
onChange={(e) => input.setValue((e.target as any).value)}
|
|
238
|
+
placeholder="Type here..."
|
|
239
|
+
className="min-h-[60px]"
|
|
240
|
+
onFocus={() => handleInputFocus(input.id)}
|
|
241
|
+
/>
|
|
242
|
+
{focusedInput === input.id && (
|
|
243
|
+
<div className="text-xs text-teal-600 mt-1">✓ Connected</div>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
))}
|
|
247
|
+
</div>
|
|
248
|
+
<Keyboard
|
|
249
|
+
{...args}
|
|
250
|
+
value={currentValue}
|
|
251
|
+
onChange={setCurrentValue}
|
|
252
|
+
inputRef={refs[focusedInput]}
|
|
253
|
+
className="fixed bottom-0 left-0 right-0 z-10"
|
|
254
|
+
showNumbers
|
|
255
|
+
showShift
|
|
256
|
+
showSpace
|
|
257
|
+
showBackspace
|
|
258
|
+
showEnter
|
|
259
|
+
/>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
},
|
|
263
|
+
};
|