@telegraph/input 0.1.1 → 0.1.3
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/CHANGELOG.md +16 -0
- package/README.md +634 -6
- package/dist/types/Input/Input.d.ts.map +1 -1
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @telegraph/input
|
|
2
2
|
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [[`322bf1e`](https://github.com/knocklabs/telegraph/commit/322bf1e463b0a2a5b83899843d8ea54004b89b9b)]:
|
|
8
|
+
- @telegraph/layout@0.2.3
|
|
9
|
+
- @telegraph/typography@0.1.25
|
|
10
|
+
|
|
11
|
+
## 0.1.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies []:
|
|
16
|
+
- @telegraph/layout@0.2.2
|
|
17
|
+
- @telegraph/typography@0.1.24
|
|
18
|
+
|
|
3
19
|
## 0.1.1
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,18 +1,646 @@
|
|
|
1
|
+
# 📝 Input
|
|
2
|
+
|
|
3
|
+
> Flexible input component with slots for icons, buttons, and custom elements.
|
|
4
|
+
|
|
1
5
|

|
|
2
6
|
|
|
3
7
|
[](https://www.npmjs.com/package/@telegraph/input)
|
|
8
|
+
[](https://bundlephobia.com/result?p=@telegraph/input)
|
|
9
|
+
[](https://github.com/knocklabs/telegraph/blob/main/LICENSE)
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
> Input component with options
|
|
7
|
-
|
|
8
|
-
## Installation Instructions
|
|
11
|
+
## Installation
|
|
9
12
|
|
|
10
|
-
```
|
|
13
|
+
```bash
|
|
11
14
|
npm install @telegraph/input
|
|
12
15
|
```
|
|
13
16
|
|
|
14
17
|
### Add stylesheet
|
|
18
|
+
|
|
19
|
+
Pick one:
|
|
20
|
+
|
|
21
|
+
Via CSS (preferred):
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
@import "@telegraph/input";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Via Javascript:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import "@telegraph/input/default.css";
|
|
15
31
|
```
|
|
16
|
-
|
|
32
|
+
|
|
33
|
+
> Then, include `className="tgph"` on the farthest parent element wrapping the telegraph components
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Icon } from "@telegraph/icon";
|
|
39
|
+
import { Input } from "@telegraph/input";
|
|
40
|
+
import { Search } from "lucide-react";
|
|
41
|
+
|
|
42
|
+
export const SearchInput = () => (
|
|
43
|
+
<Input
|
|
44
|
+
placeholder="Search..."
|
|
45
|
+
LeadingComponent={<Icon icon={Search} alt="Search" />}
|
|
46
|
+
/>
|
|
47
|
+
);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## API Reference
|
|
51
|
+
|
|
52
|
+
### `<Input>` (Default Component)
|
|
53
|
+
|
|
54
|
+
The default Input component provides a simple API for common use cases.
|
|
55
|
+
|
|
56
|
+
| Prop | Type | Default | Description |
|
|
57
|
+
| ------------------- | ---------------------- | ----------- | ------------------------------------------ |
|
|
58
|
+
| `size` | `"1" \| "2" \| "3"` | `"2"` | Input size |
|
|
59
|
+
| `variant` | `"outline" \| "ghost"` | `"outline"` | Visual style variant |
|
|
60
|
+
| `errored` | `boolean` | `false` | Shows error state styling |
|
|
61
|
+
| `disabled` | `boolean` | `false` | Disables the input |
|
|
62
|
+
| `LeadingComponent` | `React.ReactNode` | `undefined` | Component to display before the input text |
|
|
63
|
+
| `TrailingComponent` | `React.ReactNode` | `undefined` | Component to display after the input text |
|
|
64
|
+
| `as` | `TgphElement` | `"input"` | HTML element or component to render |
|
|
65
|
+
|
|
66
|
+
### Composition API
|
|
67
|
+
|
|
68
|
+
For advanced use cases, use the composition API:
|
|
69
|
+
|
|
70
|
+
#### `<Input.Root>`
|
|
71
|
+
|
|
72
|
+
Container component that wraps the input and slots.
|
|
73
|
+
|
|
74
|
+
| Prop | Type | Default | Description |
|
|
75
|
+
| ---------- | ---------------------- | ----------- | ----------------------------------- |
|
|
76
|
+
| `size` | `"1" \| "2" \| "3"` | `"2"` | Input size |
|
|
77
|
+
| `variant` | `"outline" \| "ghost"` | `"outline"` | Visual style variant |
|
|
78
|
+
| `errored` | `boolean` | `false` | Shows error state styling |
|
|
79
|
+
| `disabled` | `boolean` | `false` | Disables the input |
|
|
80
|
+
| `as` | `TgphElement` | `"input"` | HTML element or component to render |
|
|
81
|
+
|
|
82
|
+
#### `<Input.Slot>`
|
|
83
|
+
|
|
84
|
+
Wrapper for leading and trailing components.
|
|
85
|
+
|
|
86
|
+
| Prop | Type | Default | Description |
|
|
87
|
+
| ---------- | ------------------------- | ----------- | -------------------- |
|
|
88
|
+
| `position` | `"leading" \| "trailing"` | `"leading"` | Position of the slot |
|
|
89
|
+
|
|
90
|
+
## Usage Patterns
|
|
91
|
+
|
|
92
|
+
### Basic Input
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { Input } from "@telegraph/input";
|
|
96
|
+
|
|
97
|
+
// Simple text input
|
|
98
|
+
<Input placeholder="Enter your name" />
|
|
99
|
+
|
|
100
|
+
// Email input
|
|
101
|
+
<Input type="email" placeholder="Enter your email" />
|
|
102
|
+
|
|
103
|
+
// Password input
|
|
104
|
+
<Input type="password" placeholder="Enter your password" />
|
|
17
105
|
```
|
|
18
106
|
|
|
107
|
+
### Sizes
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import { Input } from "@telegraph/input";
|
|
111
|
+
|
|
112
|
+
// Small input
|
|
113
|
+
<Input size="1" placeholder="Small" />
|
|
114
|
+
|
|
115
|
+
// Medium input (default)
|
|
116
|
+
<Input size="2" placeholder="Medium" />
|
|
117
|
+
|
|
118
|
+
// Large input
|
|
119
|
+
<Input size="3" placeholder="Large" />
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Variants
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { Input } from "@telegraph/input";
|
|
126
|
+
|
|
127
|
+
// Outline variant (default)
|
|
128
|
+
<Input variant="outline" placeholder="Outlined input" />
|
|
129
|
+
|
|
130
|
+
// Ghost variant
|
|
131
|
+
<Input variant="ghost" placeholder="Ghost input" />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### States
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { Input } from "@telegraph/input";
|
|
138
|
+
|
|
139
|
+
// Error state
|
|
140
|
+
<Input errored placeholder="This has an error" />
|
|
141
|
+
|
|
142
|
+
// Disabled state
|
|
143
|
+
<Input disabled placeholder="This is disabled" />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### With Icons
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
import { Input } from "@telegraph/input";
|
|
150
|
+
import { Icon } from "@telegraph/icon";
|
|
151
|
+
import { Search, Mail, Lock } from "lucide-react";
|
|
152
|
+
|
|
153
|
+
// Search input
|
|
154
|
+
<Input
|
|
155
|
+
placeholder="Search..."
|
|
156
|
+
LeadingComponent={<Icon icon={Search} alt="Search" />}
|
|
157
|
+
/>
|
|
158
|
+
|
|
159
|
+
// Email input
|
|
160
|
+
<Input
|
|
161
|
+
type="email"
|
|
162
|
+
placeholder="Enter email"
|
|
163
|
+
LeadingComponent={<Icon icon={Mail} alt="Email" />}
|
|
164
|
+
/>
|
|
165
|
+
|
|
166
|
+
// Password input with toggle
|
|
167
|
+
<Input
|
|
168
|
+
type="password"
|
|
169
|
+
placeholder="Password"
|
|
170
|
+
LeadingComponent={<Icon icon={Lock} alt="Password" />}
|
|
171
|
+
TrailingComponent={
|
|
172
|
+
<Icon
|
|
173
|
+
icon={Eye}
|
|
174
|
+
alt="Toggle password visibility"
|
|
175
|
+
onClick={togglePasswordVisibility}
|
|
176
|
+
/>
|
|
177
|
+
}
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Advanced Usage
|
|
182
|
+
|
|
183
|
+
### Composition Pattern
|
|
184
|
+
|
|
185
|
+
Use `Input.Root` and `Input.Slot` for maximum control:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
import { Button } from "@telegraph/button";
|
|
189
|
+
import { Icon } from "@telegraph/icon";
|
|
190
|
+
import { Input } from "@telegraph/input";
|
|
191
|
+
import { Filter, Search } from "lucide-react";
|
|
192
|
+
|
|
193
|
+
export const AdvancedSearchInput = () => (
|
|
194
|
+
<Input.Root placeholder="Search products...">
|
|
195
|
+
<Input.Slot position="leading">
|
|
196
|
+
<Icon icon={Search} alt="Search" />
|
|
197
|
+
</Input.Slot>
|
|
198
|
+
<Input.Slot position="trailing">
|
|
199
|
+
<Button
|
|
200
|
+
variant="ghost"
|
|
201
|
+
size="1"
|
|
202
|
+
icon={{ icon: Filter, alt: "Filter" }}
|
|
203
|
+
onClick={openFilters}
|
|
204
|
+
/>
|
|
205
|
+
</Input.Slot>
|
|
206
|
+
</Input.Root>
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Form Integration
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
import { Icon } from "@telegraph/icon";
|
|
214
|
+
import { Input } from "@telegraph/input";
|
|
215
|
+
import { Stack } from "@telegraph/layout";
|
|
216
|
+
import { Text } from "@telegraph/typography";
|
|
217
|
+
import { AlertCircle } from "lucide-react";
|
|
218
|
+
import { useState } from "react";
|
|
219
|
+
|
|
220
|
+
export const FormField = () => {
|
|
221
|
+
const [value, setValue] = useState("");
|
|
222
|
+
const [error, setError] = useState("");
|
|
223
|
+
|
|
224
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
225
|
+
setValue(e.target.value);
|
|
226
|
+
if (error) setError(""); // Clear error on change
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<Stack gap="1">
|
|
231
|
+
<Text as="label" htmlFor="email">
|
|
232
|
+
Email Address
|
|
233
|
+
</Text>
|
|
234
|
+
<Input
|
|
235
|
+
id="email"
|
|
236
|
+
type="email"
|
|
237
|
+
value={value}
|
|
238
|
+
onChange={handleChange}
|
|
239
|
+
errored={!!error}
|
|
240
|
+
placeholder="Enter your email"
|
|
241
|
+
LeadingComponent={<Icon icon={Mail} alt="Email" />}
|
|
242
|
+
/>
|
|
243
|
+
{error && (
|
|
244
|
+
<Stack direction="row" align="center" gap="1">
|
|
245
|
+
<Icon icon={AlertCircle} alt="Error" color="red" size="1" />
|
|
246
|
+
<Text size="1" color="red">
|
|
247
|
+
{error}
|
|
248
|
+
</Text>
|
|
249
|
+
</Stack>
|
|
250
|
+
)}
|
|
251
|
+
</Stack>
|
|
252
|
+
);
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Search with Clear Button
|
|
257
|
+
|
|
258
|
+
```tsx
|
|
259
|
+
import { Button } from "@telegraph/button";
|
|
260
|
+
import { Icon } from "@telegraph/icon";
|
|
261
|
+
import { Input } from "@telegraph/input";
|
|
262
|
+
import { Search, X } from "lucide-react";
|
|
263
|
+
import { useState } from "react";
|
|
264
|
+
|
|
265
|
+
export const SearchInput = () => {
|
|
266
|
+
const [query, setQuery] = useState("");
|
|
267
|
+
|
|
268
|
+
const clearSearch = () => setQuery("");
|
|
269
|
+
|
|
270
|
+
return (
|
|
271
|
+
<Input
|
|
272
|
+
value={query}
|
|
273
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
274
|
+
placeholder="Search..."
|
|
275
|
+
LeadingComponent={<Icon icon={Search} alt="Search" />}
|
|
276
|
+
TrailingComponent={
|
|
277
|
+
query && (
|
|
278
|
+
<Button
|
|
279
|
+
variant="ghost"
|
|
280
|
+
size="1"
|
|
281
|
+
icon={{ icon: X, alt: "Clear search" }}
|
|
282
|
+
onClick={clearSearch}
|
|
283
|
+
/>
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
};
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Number Input with Controls
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { Button } from "@telegraph/button";
|
|
295
|
+
import { Icon } from "@telegraph/icon";
|
|
296
|
+
import { Input } from "@telegraph/input";
|
|
297
|
+
import { Minus, Plus } from "lucide-react";
|
|
298
|
+
import { useState } from "react";
|
|
299
|
+
|
|
300
|
+
export const NumberInput = ({ min = 0, max = 100 }) => {
|
|
301
|
+
const [value, setValue] = useState(0);
|
|
302
|
+
|
|
303
|
+
const increment = () => setValue((prev) => Math.min(prev + 1, max));
|
|
304
|
+
const decrement = () => setValue((prev) => Math.max(prev - 1, min));
|
|
305
|
+
|
|
306
|
+
return (
|
|
307
|
+
<Input.Root
|
|
308
|
+
type="number"
|
|
309
|
+
value={value}
|
|
310
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
311
|
+
min={min}
|
|
312
|
+
max={max}
|
|
313
|
+
>
|
|
314
|
+
<Input.Slot position="leading">
|
|
315
|
+
<Button
|
|
316
|
+
variant="ghost"
|
|
317
|
+
size="1"
|
|
318
|
+
icon={{ icon: Minus, alt: "Decrease" }}
|
|
319
|
+
onClick={decrement}
|
|
320
|
+
disabled={value <= min}
|
|
321
|
+
/>
|
|
322
|
+
</Input.Slot>
|
|
323
|
+
<Input.Slot position="trailing">
|
|
324
|
+
<Button
|
|
325
|
+
variant="ghost"
|
|
326
|
+
size="1"
|
|
327
|
+
icon={{ icon: Plus, alt: "Increase" }}
|
|
328
|
+
onClick={increment}
|
|
329
|
+
disabled={value >= max}
|
|
330
|
+
/>
|
|
331
|
+
</Input.Slot>
|
|
332
|
+
</Input.Root>
|
|
333
|
+
);
|
|
334
|
+
};
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Polymorphic Usage
|
|
338
|
+
|
|
339
|
+
```tsx
|
|
340
|
+
import { Input } from "@telegraph/input";
|
|
341
|
+
import { forwardRef } from "react";
|
|
342
|
+
|
|
343
|
+
// Custom textarea component
|
|
344
|
+
const TextAreaInput = forwardRef<HTMLTextAreaElement>((props, ref) => (
|
|
345
|
+
<Input.Root as="textarea" tgphRef={ref} {...props} />
|
|
346
|
+
));
|
|
347
|
+
|
|
348
|
+
// Usage
|
|
349
|
+
<TextAreaInput placeholder="Enter your message..." rows={4} />;
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Input with Validation
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
import { Icon } from "@telegraph/icon";
|
|
356
|
+
import { Input } from "@telegraph/input";
|
|
357
|
+
import { AlertCircle, CheckCircle } from "lucide-react";
|
|
358
|
+
import { useEffect, useState } from "react";
|
|
359
|
+
|
|
360
|
+
export const ValidatedInput = ({ validate, ...props }) => {
|
|
361
|
+
const [value, setValue] = useState("");
|
|
362
|
+
const [isValid, setIsValid] = useState<boolean | null>(null);
|
|
363
|
+
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (value) {
|
|
366
|
+
setIsValid(validate(value));
|
|
367
|
+
} else {
|
|
368
|
+
setIsValid(null);
|
|
369
|
+
}
|
|
370
|
+
}, [value, validate]);
|
|
371
|
+
|
|
372
|
+
return (
|
|
373
|
+
<Input
|
|
374
|
+
{...props}
|
|
375
|
+
value={value}
|
|
376
|
+
onChange={(e) => setValue(e.target.value)}
|
|
377
|
+
errored={isValid === false}
|
|
378
|
+
TrailingComponent={
|
|
379
|
+
isValid !== null && (
|
|
380
|
+
<Icon
|
|
381
|
+
icon={isValid ? CheckCircle : AlertCircle}
|
|
382
|
+
color={isValid ? "green" : "red"}
|
|
383
|
+
alt={isValid ? "Valid" : "Invalid"}
|
|
384
|
+
/>
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
/>
|
|
388
|
+
);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Usage
|
|
392
|
+
<ValidatedInput
|
|
393
|
+
placeholder="Enter email"
|
|
394
|
+
validate={(email) => /\S+@\S+\.\S+/.test(email)}
|
|
395
|
+
/>;
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Design Tokens & Styling
|
|
399
|
+
|
|
400
|
+
The input component uses Telegraph design tokens for consistent styling:
|
|
401
|
+
|
|
402
|
+
### Size Tokens
|
|
403
|
+
|
|
404
|
+
**Container Heights:**
|
|
405
|
+
|
|
406
|
+
- Size `"1"`: `var(--tgph-spacing-6)` (24px)
|
|
407
|
+
- Size `"2"`: `var(--tgph-spacing-8)` (32px)
|
|
408
|
+
- Size `"3"`: `var(--tgph-spacing-10)` (40px)
|
|
409
|
+
|
|
410
|
+
**Text Sizes:**
|
|
411
|
+
|
|
412
|
+
- Size `"1"`: `var(--tgph-text-1)`
|
|
413
|
+
- Size `"2"`: `var(--tgph-text-2)`
|
|
414
|
+
- Size `"3"`: `var(--tgph-text-3)`
|
|
415
|
+
|
|
416
|
+
### Color Tokens
|
|
417
|
+
|
|
418
|
+
**Outline Variant:**
|
|
419
|
+
|
|
420
|
+
- Background: `var(--tgph-surface-1)`
|
|
421
|
+
- Border: `var(--tgph-gray-6)`
|
|
422
|
+
- Hover Border: `var(--tgph-gray-7)`
|
|
423
|
+
- Focus Border: `var(--tgph-blue-8)`
|
|
424
|
+
|
|
425
|
+
**Ghost Variant:**
|
|
426
|
+
|
|
427
|
+
- Background: `transparent`
|
|
428
|
+
- Hover Background: `var(--tgph-gray-3)`
|
|
429
|
+
- Focus Background: `var(--tgph-gray-4)`
|
|
430
|
+
|
|
431
|
+
**Error State:**
|
|
432
|
+
|
|
433
|
+
- Border: `var(--tgph-red-6)`
|
|
434
|
+
|
|
435
|
+
## Accessibility
|
|
436
|
+
|
|
437
|
+
- ✅ **Keyboard Navigation**: Full keyboard support with proper tab order
|
|
438
|
+
- ✅ **Screen Reader Support**: Proper labeling and state announcements
|
|
439
|
+
- ✅ **Focus Management**: Clear focus indicators and focus trapping
|
|
440
|
+
- ✅ **ARIA Attributes**: Proper `aria-invalid`, `aria-describedby` support
|
|
441
|
+
- ✅ **Color Contrast**: All variants meet WCAG AA standards
|
|
442
|
+
|
|
443
|
+
### Accessibility Guidelines
|
|
444
|
+
|
|
445
|
+
1. **Labels**: Always provide labels for inputs
|
|
446
|
+
2. **Error Messages**: Associate error messages with inputs using `aria-describedby`
|
|
447
|
+
3. **Required Fields**: Use `aria-required` for required inputs
|
|
448
|
+
4. **Placeholder Text**: Don't rely solely on placeholders for instructions
|
|
449
|
+
5. **Interactive Elements**: Ensure buttons and icons in slots are keyboard accessible
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
// ✅ Good accessibility practices
|
|
453
|
+
<Stack gap="1">
|
|
454
|
+
<Text as="label" htmlFor="search">Search Products</Text>
|
|
455
|
+
<Input
|
|
456
|
+
id="search"
|
|
457
|
+
placeholder="Enter product name..."
|
|
458
|
+
aria-describedby={error ? "search-error" : undefined}
|
|
459
|
+
aria-invalid={!!error}
|
|
460
|
+
errored={!!error}
|
|
461
|
+
LeadingComponent={<Icon icon={Search} alt="Search" />}
|
|
462
|
+
/>
|
|
463
|
+
{error && (
|
|
464
|
+
<Text id="search-error" size="1" color="red">
|
|
465
|
+
{error}
|
|
466
|
+
</Text>
|
|
467
|
+
)}
|
|
468
|
+
</Stack>
|
|
469
|
+
|
|
470
|
+
// ❌ Poor accessibility
|
|
471
|
+
<Input placeholder="Search" /> {/* No label */}
|
|
472
|
+
<Input
|
|
473
|
+
LeadingComponent={<Icon icon={Search} />} {/* No alt text */}
|
|
474
|
+
/>
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Keyboard Shortcuts
|
|
478
|
+
|
|
479
|
+
| Key | Action |
|
|
480
|
+
| ------------- | ------------------------------------- |
|
|
481
|
+
| `Tab` | Focus next element |
|
|
482
|
+
| `Shift + Tab` | Focus previous element |
|
|
483
|
+
| `Enter` | Submit form (if in form) |
|
|
484
|
+
| `Escape` | Clear input (if custom clear handler) |
|
|
485
|
+
|
|
486
|
+
### Best Practices
|
|
487
|
+
|
|
488
|
+
1. **Label Association**: Use `htmlFor` and `id` to associate labels with inputs
|
|
489
|
+
2. **Error Handling**: Provide clear, actionable error messages
|
|
490
|
+
3. **Required Indicators**: Mark required fields visually and with `aria-required`
|
|
491
|
+
4. **Placeholder Usage**: Use placeholders for examples, not instructions
|
|
492
|
+
5. **Focus Order**: Ensure logical tab order through form fields
|
|
493
|
+
|
|
494
|
+
## Examples
|
|
495
|
+
|
|
496
|
+
### Contact Form
|
|
497
|
+
|
|
498
|
+
```tsx
|
|
499
|
+
import { Button } from "@telegraph/button";
|
|
500
|
+
import { Icon } from "@telegraph/icon";
|
|
501
|
+
import { Input } from "@telegraph/input";
|
|
502
|
+
import { Stack } from "@telegraph/layout";
|
|
503
|
+
import { Text } from "@telegraph/typography";
|
|
504
|
+
import { Mail, MessageSquare, User } from "lucide-react";
|
|
505
|
+
|
|
506
|
+
export const ContactForm = () => (
|
|
507
|
+
<Stack gap="4" as="form">
|
|
508
|
+
<Stack gap="1">
|
|
509
|
+
<Text as="label" htmlFor="name">
|
|
510
|
+
Full Name
|
|
511
|
+
</Text>
|
|
512
|
+
<Input
|
|
513
|
+
id="name"
|
|
514
|
+
placeholder="Enter your full name"
|
|
515
|
+
LeadingComponent={<Icon icon={User} alt="Name" />}
|
|
516
|
+
/>
|
|
517
|
+
</Stack>
|
|
518
|
+
|
|
519
|
+
<Stack gap="1">
|
|
520
|
+
<Text as="label" htmlFor="email">
|
|
521
|
+
Email Address
|
|
522
|
+
</Text>
|
|
523
|
+
<Input
|
|
524
|
+
id="email"
|
|
525
|
+
type="email"
|
|
526
|
+
placeholder="Enter your email"
|
|
527
|
+
LeadingComponent={<Icon icon={Mail} alt="Email" />}
|
|
528
|
+
/>
|
|
529
|
+
</Stack>
|
|
530
|
+
|
|
531
|
+
<Stack gap="1">
|
|
532
|
+
<Text as="label" htmlFor="message">
|
|
533
|
+
Message
|
|
534
|
+
</Text>
|
|
535
|
+
<Input.Root
|
|
536
|
+
as="textarea"
|
|
537
|
+
id="message"
|
|
538
|
+
placeholder="Enter your message"
|
|
539
|
+
rows={4}
|
|
540
|
+
>
|
|
541
|
+
<Input.Slot position="leading">
|
|
542
|
+
<Icon icon={MessageSquare} alt="Message" />
|
|
543
|
+
</Input.Slot>
|
|
544
|
+
</Input.Root>
|
|
545
|
+
</Stack>
|
|
546
|
+
|
|
547
|
+
<Button type="submit">Send Message</Button>
|
|
548
|
+
</Stack>
|
|
549
|
+
);
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### E-commerce Search
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
import { Button } from "@telegraph/button";
|
|
556
|
+
import { Icon } from "@telegraph/icon";
|
|
557
|
+
import { Input } from "@telegraph/input";
|
|
558
|
+
import { Filter, ScanBarcode, Search } from "lucide-react";
|
|
559
|
+
import { useState } from "react";
|
|
560
|
+
|
|
561
|
+
export const ProductSearch = () => {
|
|
562
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
563
|
+
|
|
564
|
+
return (
|
|
565
|
+
<Input
|
|
566
|
+
value={searchQuery}
|
|
567
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
568
|
+
placeholder="Search products..."
|
|
569
|
+
size="3"
|
|
570
|
+
LeadingComponent={<Icon icon={Search} alt="Search" />}
|
|
571
|
+
TrailingComponent={
|
|
572
|
+
<Stack direction="row" gap="1">
|
|
573
|
+
<Button
|
|
574
|
+
variant="ghost"
|
|
575
|
+
size="1"
|
|
576
|
+
icon={{ icon: ScanBarcode, alt: "Scan barcode" }}
|
|
577
|
+
onClick={openBarcodeScanner}
|
|
578
|
+
/>
|
|
579
|
+
<Button
|
|
580
|
+
variant="ghost"
|
|
581
|
+
size="1"
|
|
582
|
+
icon={{ icon: Filter, alt: "Filter results" }}
|
|
583
|
+
onClick={openFilters}
|
|
584
|
+
/>
|
|
585
|
+
</Stack>
|
|
586
|
+
}
|
|
587
|
+
/>
|
|
588
|
+
);
|
|
589
|
+
};
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Authentication Inputs
|
|
593
|
+
|
|
594
|
+
```tsx
|
|
595
|
+
import { Button } from "@telegraph/button";
|
|
596
|
+
import { Icon } from "@telegraph/icon";
|
|
597
|
+
import { Input } from "@telegraph/input";
|
|
598
|
+
import { Stack } from "@telegraph/layout";
|
|
599
|
+
import { Eye, EyeOff, Lock, Mail } from "lucide-react";
|
|
600
|
+
import { useState } from "react";
|
|
601
|
+
|
|
602
|
+
export const LoginForm = () => {
|
|
603
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
604
|
+
|
|
605
|
+
return (
|
|
606
|
+
<Stack gap="4" as="form">
|
|
607
|
+
<Input
|
|
608
|
+
type="email"
|
|
609
|
+
placeholder="Email address"
|
|
610
|
+
LeadingComponent={<Icon icon={Mail} alt="Email" />}
|
|
611
|
+
/>
|
|
612
|
+
|
|
613
|
+
<Input
|
|
614
|
+
type={showPassword ? "text" : "password"}
|
|
615
|
+
placeholder="Password"
|
|
616
|
+
LeadingComponent={<Icon icon={Lock} alt="Password" />}
|
|
617
|
+
TrailingComponent={
|
|
618
|
+
<Button
|
|
619
|
+
variant="ghost"
|
|
620
|
+
size="1"
|
|
621
|
+
icon={{
|
|
622
|
+
icon: showPassword ? EyeOff : Eye,
|
|
623
|
+
alt: showPassword ? "Hide password" : "Show password",
|
|
624
|
+
}}
|
|
625
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
626
|
+
/>
|
|
627
|
+
}
|
|
628
|
+
/>
|
|
629
|
+
|
|
630
|
+
<Button type="submit">Sign In</Button>
|
|
631
|
+
</Stack>
|
|
632
|
+
);
|
|
633
|
+
};
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
## References
|
|
637
|
+
|
|
638
|
+
- [Storybook Demo](https://storybook.telegraph.dev/?path=/docs/input)
|
|
639
|
+
|
|
640
|
+
## Contributing
|
|
641
|
+
|
|
642
|
+
See our [Contributing Guide](../../CONTRIBUTING.md) for more details.
|
|
643
|
+
|
|
644
|
+
## License
|
|
645
|
+
|
|
646
|
+
MIT License - see [LICENSE](../../LICENSE) for details.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../../src/Input/Input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAClB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,SAAS,WAAW,IAAI,aAAa,GAAG;IACtD,SAAS,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;CAC9D,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAYrD,QAAA,MAAM,IAAI,GAAI,CAAC,SAAS,WAAW,
|
|
1
|
+
{"version":3,"file":"Input.d.ts","sourceRoot":"","sources":["../../../src/Input/Input.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAEhB,kBAAkB,EAClB,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC7C,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,KAAK,aAAa,GAAG;IACnB,IAAI,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IACvB,OAAO,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,KAAK,SAAS,CAAC,CAAC,SAAS,WAAW,IAAI,aAAa,GAAG;IACtD,SAAS,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;CAC9D,GAAG,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAYrD,QAAA,MAAM,IAAI,GAAI,CAAC,SAAS,WAAW,EAAE,kFAUlC,SAAS,CAAC,CAAC,CAAC,4CAyDd,CAAC;AAQF,QAAA,MAAM,IAAI;WALD,GAAG,GAAG,GAAG,GAAG,GAAG;eACX,SAAS,GAAG,UAAU;qCAsBlC,CAAC;AAEF,KAAK,YAAY,CAAC,CAAC,SAAS,WAAW,IAAI,gBAAgB,CAAC,CAAC,CAAC,GAC5D,kBAAkB,CAAC,OAAO,IAAI,CAAC,GAAG;IAChC,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACnC,iBAAiB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACrC,CAAC;AAEJ,QAAA,MAAM,OAAO,GAAI,CAAC,SAAS,WAAW,EAAE,mDAIrC,YAAY,CAAC,CAAC,CAAC,4CASjB,CAAC;AAIF,QAAA,MAAM,KAAK,EAAc,OAAO,OAAO,GAAG;IACxC,IAAI,EAAE,OAAO,IAAI,CAAC;IAClB,IAAI,EAAE,OAAO,IAAI,CAAC;CACnB,CAAC;AAEF,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telegraph/input",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Input component for Telegraph",
|
|
5
5
|
"repository": "https://github.com/knocklabs/telegraph/tree/main/packages/input",
|
|
6
6
|
"author": "@knocklabs",
|
|
@@ -35,21 +35,21 @@
|
|
|
35
35
|
"@radix-ui/react-slot": "^1.2.3",
|
|
36
36
|
"@telegraph/compose-refs": "^0.0.7",
|
|
37
37
|
"@telegraph/helpers": "^0.0.13",
|
|
38
|
-
"@telegraph/layout": "^0.2.
|
|
39
|
-
"@telegraph/typography": "^0.1.
|
|
38
|
+
"@telegraph/layout": "^0.2.3",
|
|
39
|
+
"@telegraph/typography": "^0.1.25",
|
|
40
40
|
"clsx": "^2.1.1"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@knocklabs/eslint-config": "^0.0.
|
|
43
|
+
"@knocklabs/eslint-config": "^0.0.5",
|
|
44
44
|
"@knocklabs/typescript-config": "^0.0.2",
|
|
45
|
-
"@telegraph/postcss-config": "^0.0.
|
|
45
|
+
"@telegraph/postcss-config": "^0.0.30",
|
|
46
46
|
"@telegraph/prettier-config": "^0.0.7",
|
|
47
47
|
"@telegraph/vite-config": "^0.0.15",
|
|
48
|
-
"@types/react": "^
|
|
48
|
+
"@types/react": "^19.1.11",
|
|
49
49
|
"eslint": "^8.56.0",
|
|
50
|
-
"react": "^
|
|
51
|
-
"react-dom": "^
|
|
52
|
-
"typescript": "^5.
|
|
50
|
+
"react": "^19.1.1",
|
|
51
|
+
"react-dom": "^19.1.1",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
53
|
"vite": "^6.0.11"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|