@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 CHANGED
@@ -1,103 +1,294 @@
1
- # starasia Input
1
+ # @starasia/input
2
2
 
3
- starasia Input is a strict and customizable Input component for web development projects, designed for simplicity and adherence to strict design guidelines.
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
- ## Screenshots
8
+ ## Installation
6
9
 
7
- ![App Screenshot](https://github.com/primaramadhaniputra/image/blob/main/IInput.png)
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
- ## Package instalation
18
+ ## Quick start
10
19
 
11
- Instal package using pnpm
20
+ ```tsx
21
+ import {Input} from "@starasia/input";
12
22
 
13
- ```bash
14
- pnpm add @starasia/input
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
- Instal package using yarn
39
+ ## Props
18
40
 
19
- ```bash
20
- yarn add @starasia/input
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
- Instal package using npm
96
+ ## Sizes
24
97
 
25
- ```bash
26
- npm i @starasia/input
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
- ## Usage/Examples (you can combine using icon package starasia)
111
+ `status="success"` automatically renders a green checkmark on the right side of
112
+ the field.
30
113
 
31
- ```javascript
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
- export interface IconProps extends SVGProps<SVGSVGElement> {}
116
+ ### `outside-top` (default)
37
117
 
38
- export const IcBel = ({...rest}: IconProps) => {
39
- return (
40
- <svg
41
- xmlns="http://www.w3.org/2000/svg"
42
- {...rest}
43
- viewBox="0 0 24 26"
44
- fill="none"
45
- >
46
- <path
47
- d="M8.68924 1.76023C9.69268 1.23925 10.8152 0.947739 12 0.947739C16.2663 0.947739 19.7249 4.7276 19.7249 9.39031V10.2395C19.7249 11.2587 20.001 12.2549 20.5182 13.1029L21.7857 15.1809C22.9436 17.0788 22.0597 19.6588 20.046 20.259C14.7782 21.8291 9.2218 21.8291 3.954 20.259C1.94033 19.6588 1.05645 17.0788 2.21424 15.1809L3.48179 13.1029C3.99905 12.2549 4.27507 11.2587 4.27507 10.2395V9.39031C4.27507 8.09543 4.5418 6.86864 5.0185 5.77198"
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
- const App = () => {
63
- const ref = useRef<HTMLInputElement | null>(null);
64
- return (
65
- <div>
66
- <h1>Component test</h1>
67
- <div style={{margin: "auto", width: "50vw"}}>
68
- <Input
69
- leftIcon={<IcBel />}
70
- rightIcon={<IcBel />}
71
- leftAddons={"Rp"}
72
- rightAddons={"Rp"}
73
- onClickRightIcon={() => alert("woke")}
74
- placeholder="CTRL + K to search Query"
75
- highlightPlaceholder="CTRL + K"
76
- size={"md"}
77
- onChange={(e) => console.log("e", e.target.value)}
78
- ref={ref}
79
- />
80
- </div>
81
- </div>
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
- ReactDOM.createRoot(document.getElementById("app")!).render(<App />);
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
- ## Props @starasia/Input
90
-
91
- #### Props that you can pass to <Input {...props}/>
92
-
93
- | Prop Name | Value | required |
94
- | :------------------- | :------------------------------------------ | :------- |
95
- | leftIcon | <IcBel /> (icon svg) | false |
96
- | rightIcon | <IcBel /> (icon svg) | false |
97
- | size | "sm"/"md"/"lg" | false |
98
- | status | "error" / "warning" / "disable" / "default" | false |
99
- | inputUse | "icon" / "addons" | false |
100
- | variant | "outline" / "filled" / "flushed" | false |
101
- | placeholder | string | false |
102
- | highlightPlaceholder | string | false |
103
- | currency | boolean | false |
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
@@ -1,3 +1,13 @@
1
- export declare const renderIconSize: (size: "sm" | "md" | "lg") => 16 | 20 | undefined;
2
- export declare const renderColorLeftIcon: (status: "error" | "warning" | "disable" | "default") => "#939E99" | "#EF4444" | "#EAB308";
3
- export declare const renderColorRightIcon: (status: "error" | "warning" | "disable" | "default") => "#939E99" | "#EF4444" | "#EAB308";
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;