@starasia/input 1.0.3 → 2.0.0
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/README.md +272 -81
- package/dist/functions.d.ts +13 -3
- package/dist/input.es.js +824 -457
- package/dist/input.umd.js +473 -255
- package/dist/types.d.ts +22 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,103 +1,294 @@
|
|
|
1
|
-
# starasia
|
|
1
|
+
# @starasia/input
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A flexible, design-system-driven input component for React. Implements the
|
|
4
|
+
Starasia "TextField" Figma spec — three variants × four sizes × eight states ×
|
|
5
|
+
three label positions, plus currency formatting, autocomplete, and a built-in
|
|
6
|
+
clear button.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Installation
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @starasia/input
|
|
12
|
+
# or
|
|
13
|
+
yarn add @starasia/input
|
|
14
|
+
# or
|
|
15
|
+
npm i @starasia/input
|
|
16
|
+
```
|
|
8
17
|
|
|
9
|
-
##
|
|
18
|
+
## Quick start
|
|
10
19
|
|
|
11
|
-
|
|
20
|
+
```tsx
|
|
21
|
+
import {Input} from "@starasia/input";
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
23
|
+
function Example() {
|
|
24
|
+
const [value, setValue] = useState("");
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Input
|
|
28
|
+
label="Email"
|
|
29
|
+
required
|
|
30
|
+
placeholder="example@gmail.com"
|
|
31
|
+
helperText="We'll never share your email."
|
|
32
|
+
value={value}
|
|
33
|
+
onChange={(e) => setValue(e.target.value)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
15
37
|
```
|
|
16
38
|
|
|
17
|
-
|
|
39
|
+
## Props
|
|
18
40
|
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
### Layout & sizing
|
|
42
|
+
|
|
43
|
+
| Prop | Type | Default | Description |
|
|
44
|
+
| --------------- | ------------------------------------------------- | -------------- | -------------------------------------------- |
|
|
45
|
+
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | Field height & typography scale. |
|
|
46
|
+
| `variant` | `"standard" \| "outline" \| "flushed"` | `"outline"` | Visual style of the input box. |
|
|
47
|
+
| `status` | `"default" \| "error" \| "success" \| "disable"` | `"default"` | Border color & helper-text styling. |
|
|
48
|
+
| `labelPosition` | `"outside-top" \| "outside-left" \| "inside"` | `"outside-top"`| Where the label sits relative to the field. |
|
|
49
|
+
| `fullWidth` | `boolean` | `false` | Stretch the component to fill its container. |
|
|
50
|
+
|
|
51
|
+
### Label & helpers
|
|
52
|
+
|
|
53
|
+
| Prop | Type | Description |
|
|
54
|
+
| ------------- | ----------- | ---------------------------------------------------------------------------- |
|
|
55
|
+
| `label` | `ReactNode` | Field label. |
|
|
56
|
+
| `required` | `boolean` | Show a red `*` next to the label. |
|
|
57
|
+
| `optional` | `boolean` | Show an "Optional" pill next to the label. |
|
|
58
|
+
| `description` | `string` | Subtle help text rendered above the field. |
|
|
59
|
+
| `helperText` | `string` | Subtle help text rendered below the field. |
|
|
60
|
+
| `errorText` | `string` | Red error text rendered below the field — only shown when `status="error"`. |
|
|
61
|
+
|
|
62
|
+
### Adornments
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Description |
|
|
65
|
+
| ------------------- | -------------------------- | -------------------------------------------------------------------------- |
|
|
66
|
+
| `leftIcon` | `ReactElement` | SVG element placed inside the field on the left. |
|
|
67
|
+
| `rightIcon` | `ReactElement` | SVG element placed inside the field on the right. |
|
|
68
|
+
| `onClickLeftIcon` | `() => void` | Click handler for the left icon (icon container becomes clickable). |
|
|
69
|
+
| `onClickRightIcon` | `() => void` | Click handler for the right icon. |
|
|
70
|
+
| `leftAddons` | `ReactNode` | Block content attached to the left edge (e.g. `"https://"`). |
|
|
71
|
+
| `rightAddons` | `ReactNode` | Block content attached to the right edge (e.g. `".com"`). |
|
|
72
|
+
|
|
73
|
+
### Behavior
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Default | Description |
|
|
76
|
+
| ---------------------- | -------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| `clearable` | `boolean` | `true` | Show a × button to clear the value when the field has content. The slot is reserved so the input width stays constant. |
|
|
78
|
+
| `onClear` | `() => void` | | Fires after the clear button is pressed. |
|
|
79
|
+
| `currency` | `boolean` | `false` | Format the value as `1.234.567` while typing. `onChange` receives the raw numeric string. Caret stays in place when editing in the middle. |
|
|
80
|
+
| `highlightPlaceholder` | `string` | | Substring of the placeholder rendered in the primary text color (visual emphasis only — placeholder stays inert). |
|
|
81
|
+
| `options` | `string[]` | | Enables an autocomplete dropdown filtered against the current value. |
|
|
82
|
+
| `onOptionChange` | `(value: string) => void` | | Fires when the user picks an option from the dropdown. |
|
|
83
|
+
|
|
84
|
+
All other native `<input>` HTML attributes (`name`, `type`, `disabled`,
|
|
85
|
+
`onFocus`, `onBlur`, `onKeyDown`, `value`, `defaultValue`, …) are forwarded to
|
|
86
|
+
the underlying element.
|
|
87
|
+
|
|
88
|
+
## Variants
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
<Input variant="standard" label="Standard" /> {/* filled gray box, no border */}
|
|
92
|
+
<Input variant="outline" label="Outline" /> {/* white box, 0.8px border */}
|
|
93
|
+
<Input variant="flushed" label="Flushed" /> {/* bottom border only */}
|
|
21
94
|
```
|
|
22
95
|
|
|
23
|
-
|
|
96
|
+
## Sizes
|
|
24
97
|
|
|
25
|
-
|
|
26
|
-
|
|
98
|
+
`sm` (32 px) · `md` (40 px) · `lg` (48 px) · `xl` (56 px). Sizes affect typography
|
|
99
|
+
and helper-text scale too — `sm` uses 12 px input text and 10 px helpers; the
|
|
100
|
+
others use 14 px input text and 12 px helpers.
|
|
101
|
+
|
|
102
|
+
## Status
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
<Input status="default" /> {/* subtle border */}
|
|
106
|
+
<Input status="error" errorText="Please enter a valid email." />{/* red border + red text */}
|
|
107
|
+
<Input status="success" helperText="Email is available." /> {/* green border + ✓ icon */}
|
|
108
|
+
<Input status="disable" /> {/* greyed out */}
|
|
27
109
|
```
|
|
28
110
|
|
|
29
|
-
|
|
111
|
+
`status="success"` automatically renders a green checkmark on the right side of
|
|
112
|
+
the field.
|
|
30
113
|
|
|
31
|
-
|
|
32
|
-
import React, {SVGProps, useRef} from "react";
|
|
33
|
-
import {Input} from "@starasia/input";
|
|
34
|
-
import ReactDOM from "react-dom/client";
|
|
114
|
+
## Label positions
|
|
35
115
|
|
|
36
|
-
|
|
116
|
+
### `outside-top` (default)
|
|
37
117
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
stroke="currentColor"
|
|
49
|
-
stroke-width="1.55"
|
|
50
|
-
stroke-linecap="round"
|
|
51
|
-
/>
|
|
52
|
-
<path
|
|
53
|
-
d="M6.84937 21.4366C7.5991 23.5431 9.62207 25.0522 12 25.0522C12.2798 25.0522 12.5548 25.0314 12.8232 24.991M17.1506 21.4366C16.8449 22.2954 16.3276 23.0549 15.6609 23.6533"
|
|
54
|
-
stroke="currentColor"
|
|
55
|
-
stroke-width="1.55"
|
|
56
|
-
stroke-linecap="round"
|
|
57
|
-
/>
|
|
58
|
-
</svg>
|
|
59
|
-
);
|
|
60
|
-
};
|
|
118
|
+
```tsx
|
|
119
|
+
<Input
|
|
120
|
+
label="Field label"
|
|
121
|
+
required
|
|
122
|
+
optional
|
|
123
|
+
description="Enable multi-factor authentication."
|
|
124
|
+
placeholder="example@gmail.com"
|
|
125
|
+
helperText="I am some friendly help text."
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
61
128
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
129
|
+
### `outside-left`
|
|
130
|
+
|
|
131
|
+
The label and description occupy a column to the left of the field.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
<Input
|
|
135
|
+
fullWidth
|
|
136
|
+
labelPosition="outside-left"
|
|
137
|
+
label="Field label"
|
|
138
|
+
description="Enable multi-factor authentication."
|
|
139
|
+
placeholder="example@gmail.com"
|
|
140
|
+
helperText="I am some friendly help text."
|
|
141
|
+
/>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `inside` (floating label)
|
|
145
|
+
|
|
146
|
+
The label sits inside the field at rest. As soon as the field is focused or has
|
|
147
|
+
a value, the label animates upward (font size shrinks, color changes) and the
|
|
148
|
+
input field appears below — animation is 180 ms `cubic-bezier(0.4, 0, 0.2, 1)`.
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<Input
|
|
152
|
+
labelPosition="inside"
|
|
153
|
+
label="Field label"
|
|
154
|
+
required
|
|
155
|
+
rightIcon={<IcMail />}
|
|
156
|
+
helperText="Click to start typing."
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
84
159
|
|
|
85
|
-
|
|
160
|
+
## Adornments
|
|
86
161
|
|
|
162
|
+
```tsx
|
|
163
|
+
<Input leftIcon={<IcSearch />} placeholder="Search…" />
|
|
164
|
+
<Input rightIcon={<IcEye />} placeholder="Password" />
|
|
165
|
+
<Input
|
|
166
|
+
leftIcon={<IcSearch />}
|
|
167
|
+
onClickLeftIcon={() => doSomething()}
|
|
168
|
+
rightIcon={<IcQrCode />}
|
|
169
|
+
onClickRightIcon={() => scanQr()}
|
|
170
|
+
/>
|
|
171
|
+
<Input leftAddons="https://" placeholder="yourdomain.com" />
|
|
172
|
+
<Input rightAddons=".com" placeholder="yourdomain" />
|
|
173
|
+
<Input leftAddons="Rp" rightAddons=",-" placeholder="Nominal" />
|
|
87
174
|
```
|
|
88
175
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
176
|
+
Pass `onClickLeftIcon` / `onClickRightIcon` to make an icon clickable. The
|
|
177
|
+
component wraps the icon in its own clickable container — your icon's own
|
|
178
|
+
`onClick` is not consumed.
|
|
179
|
+
|
|
180
|
+
## Clear button
|
|
181
|
+
|
|
182
|
+
The × clear button appears automatically whenever the field has a value and is
|
|
183
|
+
not disabled. The slot is *always reserved*, so the input width stays constant
|
|
184
|
+
whether or not the × is visible — the button just fades in/out.
|
|
185
|
+
|
|
186
|
+
```tsx
|
|
187
|
+
<Input clearable /> {/* default: true */}
|
|
188
|
+
<Input clearable={false} /> {/* opt out */}
|
|
189
|
+
<Input onClear={() => track()} />
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Currency
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
const [amount, setAmount] = useState("");
|
|
196
|
+
|
|
197
|
+
<Input
|
|
198
|
+
currency
|
|
199
|
+
leftAddons="Rp"
|
|
200
|
+
placeholder="0"
|
|
201
|
+
value={amount}
|
|
202
|
+
onChange={(e) => setAmount(e.target.value)} // raw "12345"
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
While typing, the field displays `12.345` (thousands separator). The value
|
|
207
|
+
delivered to `onChange` is the raw numeric string (no separators), so it can be
|
|
208
|
+
saved or sent as-is. Editing in the middle of the value preserves the caret
|
|
209
|
+
position; backspacing on a thousands separator deletes the digit before it.
|
|
210
|
+
|
|
211
|
+
## Autocomplete
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
const [value, setValue] = useState("");
|
|
215
|
+
|
|
216
|
+
<Input
|
|
217
|
+
label="Fruit"
|
|
218
|
+
placeholder="Type to filter…"
|
|
219
|
+
value={value}
|
|
220
|
+
onChange={(e) => setValue(e.target.value)}
|
|
221
|
+
options={["Apple", "Banana", "Cherry", "Durian"]}
|
|
222
|
+
onOptionChange={(picked) => setValue(picked)}
|
|
223
|
+
/>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Options are filtered by `startsWith` against the current value and rendered in a
|
|
227
|
+
floating dropdown.
|
|
228
|
+
|
|
229
|
+
## Highlight placeholder
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
<Input
|
|
233
|
+
placeholder="Full Name (Required)"
|
|
234
|
+
highlightPlaceholder="Required"
|
|
235
|
+
/>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The matched substring is rendered in the primary text color while the rest of
|
|
239
|
+
the placeholder stays subtle.
|
|
240
|
+
|
|
241
|
+
## Imperative ref
|
|
242
|
+
|
|
243
|
+
`Input` forwards a ref to the underlying `<input>` element — useful for focus
|
|
244
|
+
management, integrating with form libraries, etc.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
const ref = useRef<HTMLInputElement>(null);
|
|
248
|
+
|
|
249
|
+
<Input ref={ref} />
|
|
250
|
+
|
|
251
|
+
// later
|
|
252
|
+
ref.current?.focus();
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Design tokens
|
|
256
|
+
|
|
257
|
+
The component reads its colors from CSS custom properties prefixed with
|
|
258
|
+
`--sa-input-*`. The defaults match the Starasia Figma library; override them on
|
|
259
|
+
your `:root` (or any ancestor) to retheme:
|
|
260
|
+
|
|
261
|
+
| Token | Default | Used for |
|
|
262
|
+
| ------------------------------- | ------------------------ | ------------------------------- |
|
|
263
|
+
| `--sa-input-text-primary` | `#292a2e` | Input value & label |
|
|
264
|
+
| `--sa-input-text-subtle` | `#505258` | Standard placeholder |
|
|
265
|
+
| `--sa-input-text-subtlest` | `#6b6e76` | Helper / description / outline placeholder |
|
|
266
|
+
| `--sa-input-text-error` | `#a4133c` | Error text |
|
|
267
|
+
| `--sa-input-border-subtle` | `rgba(11,18,14,0.14)` | Default border |
|
|
268
|
+
| `--sa-input-border-brand` | `#1976d2` | Focus border |
|
|
269
|
+
| `--sa-input-border-brand-subtle`| `#90caf9` | Focus ring (inside-label) |
|
|
270
|
+
| `--sa-input-border-danger` | `#c9184a` | Error border |
|
|
271
|
+
| `--sa-input-border-danger-subtle`| `#ffb3c1` | Error focus ring |
|
|
272
|
+
| `--sa-input-border-success` | `#28ac6e` | Success border |
|
|
273
|
+
| `--sa-input-border-disabled` | `rgba(24,26,25,0.56)` | Disabled border |
|
|
274
|
+
| `--sa-input-bg-neutral` | `#f0f1f2` | Standard variant background |
|
|
275
|
+
| `--sa-input-bg-error-subtlest` | `#fff0f3` | Standard + error background |
|
|
276
|
+
| `--sa-input-bg-success-subtlest`| `#ecfff6` | Standard + success background |
|
|
277
|
+
|
|
278
|
+
## TypeScript
|
|
279
|
+
|
|
280
|
+
All public types are exported alongside the component:
|
|
281
|
+
|
|
282
|
+
```tsx
|
|
283
|
+
import type {
|
|
284
|
+
IInputProps,
|
|
285
|
+
InputSize,
|
|
286
|
+
InputVariant,
|
|
287
|
+
InputStatus,
|
|
288
|
+
InputLabelPosition,
|
|
289
|
+
} from "@starasia/input";
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
ISC
|
package/dist/functions.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
1
|
+
import { InputSize, InputStatus } from "./types";
|
|
2
|
+
export declare const renderIconSize: (size: InputSize | undefined) => 18 | 16;
|
|
3
|
+
export declare const renderIconColor: (status: InputStatus, disabled?: boolean) => string;
|
|
4
|
+
export declare const renderColorLeftIcon: (status: InputStatus, disabled?: boolean) => string;
|
|
5
|
+
export declare const renderColorRightIcon: (status: InputStatus, disabled?: boolean) => string;
|
|
6
|
+
export declare function formatNumber(text: string): number;
|
|
7
|
+
export declare function handleCurrency(e: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Maps a digit-position (number of digits before cursor in the raw input)
|
|
10
|
+
* to a character index in the formatted currency string. Used to keep the
|
|
11
|
+
* caret in place after re-formatting inserts/removes thousands separators.
|
|
12
|
+
*/
|
|
13
|
+
export declare function digitsBeforeToFormattedIndex(formatted: string, digits: number): number;
|