@scripso-homepad/ui 0.3.6 → 0.3.7
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 +232 -78
- package/dist/index.cjs +1128 -85
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +322 -18
- package/dist/index.d.ts +322 -18
- package/dist/index.js +1107 -89
- package/dist/index.js.map +1 -1
- package/package.json +18 -6
- package/src/components/Button.stories.tsx +77 -57
- package/src/components/Button.tsx +146 -124
- package/src/components/CountryCodeSelector.stories.tsx +61 -0
- package/src/components/CountryCodeSelector.tsx +273 -0
- package/src/components/Input.stories.tsx +117 -0
- package/src/components/Input.tsx +177 -0
- package/src/components/Label.tsx +56 -0
- package/src/components/PhoneInput.stories.tsx +85 -0
- package/src/components/PhoneInput.tsx +172 -0
- package/src/data/countries.ts +98 -0
- package/src/icons/ArrowUpRightIcon.tsx +29 -0
- package/src/icons/ArrowUpRightIcon.web.tsx +35 -0
- package/src/icons/ChevronDownIcon.tsx +29 -0
- package/src/icons/ChevronDownIcon.web.tsx +35 -0
- package/src/icons/EyeIcon.tsx +48 -0
- package/src/icons/KeyIcon.tsx +36 -0
- package/src/icons/KeyIcon.web.tsx +42 -0
- package/src/icons/arrowUpRightPath.ts +2 -0
- package/src/icons/chevronDownPath.ts +1 -0
- package/src/icons/keyIconPaths.ts +5 -0
- package/src/index.ts +42 -0
- package/src/theme/input.ts +139 -0
- package/src/theme/tokens.ts +272 -0
- package/src/utils/countryDropdownScrollStyles.ts +52 -0
- package/src/utils/countryFlag.ts +9 -0
- package/src/utils/useApplyWebClassName.ts +3 -2
package/README.md
CHANGED
|
@@ -1,35 +1,63 @@
|
|
|
1
1
|
# @scripso-homepad/ui
|
|
2
2
|
|
|
3
|
-
Cross-platform UI component library for Homepad.
|
|
3
|
+
Cross-platform UI component library for Homepad. Built with React Native primitives — works in **Expo / React Native** and **React web** (via [react-native-web](https://necolas.github.io/react-native-web/)).
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
| Component | Description |
|
|
8
|
+
|-----------|-------------|
|
|
9
|
+
| `Button` | Primary actions with variants, sizes, and optional icon badge |
|
|
10
|
+
| `Input` | Text field with label, left/right icons, error, and hint |
|
|
11
|
+
| `PhoneInput` | Country code selector + phone number field |
|
|
12
|
+
| `CountryCodeSelector` | Standalone country dial-code dropdown |
|
|
13
|
+
| `Label` | Form field label |
|
|
4
14
|
|
|
5
15
|
## Installation
|
|
6
16
|
|
|
17
|
+
### React Native (Expo)
|
|
18
|
+
|
|
7
19
|
```bash
|
|
8
|
-
npm install @scripso-homepad/ui react
|
|
20
|
+
npm install @scripso-homepad/ui react-native-svg
|
|
9
21
|
```
|
|
10
22
|
|
|
11
|
-
|
|
23
|
+
Expo resolves the package from TypeScript source automatically (`react-native` export). Icons use `react-native-svg` on native.
|
|
12
24
|
|
|
13
|
-
|
|
25
|
+
### React web
|
|
14
26
|
|
|
15
27
|
```bash
|
|
16
|
-
npm install react-native-web
|
|
28
|
+
npm install @scripso-homepad/ui react-native-web
|
|
17
29
|
```
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
Configure your bundler to alias `react-native` → `react-native-web` and resolve `.web.tsx` icon files.
|
|
20
32
|
|
|
21
|
-
#### Vite
|
|
33
|
+
#### Vite
|
|
22
34
|
|
|
23
35
|
```ts
|
|
24
36
|
// vite.config.ts
|
|
37
|
+
import path from "node:path";
|
|
38
|
+
import { existsSync } from "node:fs";
|
|
25
39
|
import { defineConfig } from "vite";
|
|
26
40
|
import react from "@vitejs/plugin-react";
|
|
27
41
|
|
|
42
|
+
function resolveUiPackageEntry() {
|
|
43
|
+
const workspaceEntry = path.resolve(__dirname, "../homepad-ui/src/index.ts");
|
|
44
|
+
if (existsSync(workspaceEntry)) return workspaceEntry;
|
|
45
|
+
return path.resolve(__dirname, "node_modules/@scripso-homepad/ui/src/index.ts");
|
|
46
|
+
}
|
|
47
|
+
|
|
28
48
|
export default defineConfig({
|
|
29
|
-
plugins: [
|
|
49
|
+
plugins: [
|
|
50
|
+
react({
|
|
51
|
+
include: [
|
|
52
|
+
/\.[tj]sx?$/,
|
|
53
|
+
/node_modules\/(@scripso-homepad\/ui|react-native|react-native-web)/,
|
|
54
|
+
],
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
30
57
|
resolve: {
|
|
31
58
|
alias: {
|
|
32
59
|
"react-native": "react-native-web",
|
|
60
|
+
"@scripso-homepad/ui": resolveUiPackageEntry(),
|
|
33
61
|
},
|
|
34
62
|
extensions: [
|
|
35
63
|
".web.tsx",
|
|
@@ -44,6 +72,19 @@ export default defineConfig({
|
|
|
44
72
|
},
|
|
45
73
|
optimizeDeps: {
|
|
46
74
|
include: ["react-native-web", "@scripso-homepad/ui"],
|
|
75
|
+
exclude: ["react-native-svg"],
|
|
76
|
+
esbuildOptions: {
|
|
77
|
+
resolveExtensions: [
|
|
78
|
+
".web.js",
|
|
79
|
+
".web.jsx",
|
|
80
|
+
".web.ts",
|
|
81
|
+
".web.tsx",
|
|
82
|
+
".js",
|
|
83
|
+
".jsx",
|
|
84
|
+
".ts",
|
|
85
|
+
".tsx",
|
|
86
|
+
],
|
|
87
|
+
},
|
|
47
88
|
},
|
|
48
89
|
});
|
|
49
90
|
```
|
|
@@ -59,31 +100,64 @@ module.exports = {
|
|
|
59
100
|
...config.resolve.alias,
|
|
60
101
|
"react-native$": "react-native-web",
|
|
61
102
|
};
|
|
103
|
+
config.resolve.extensions = [
|
|
104
|
+
".web.tsx",
|
|
105
|
+
".web.ts",
|
|
106
|
+
".web.jsx",
|
|
107
|
+
".web.js",
|
|
108
|
+
...config.resolve.extensions,
|
|
109
|
+
];
|
|
62
110
|
return config;
|
|
63
111
|
},
|
|
64
112
|
};
|
|
65
113
|
```
|
|
66
114
|
|
|
67
|
-
### React Native (Expo)
|
|
68
|
-
|
|
69
|
-
Install from npm — no extra config needed:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
npm install @scripso-homepad/ui
|
|
73
|
-
```
|
|
74
|
-
|
|
75
115
|
## Usage
|
|
76
116
|
|
|
77
117
|
```tsx
|
|
78
|
-
import {
|
|
79
|
-
|
|
80
|
-
|
|
118
|
+
import {
|
|
119
|
+
Button,
|
|
120
|
+
Input,
|
|
121
|
+
PhoneInput,
|
|
122
|
+
CountryCodeSelector,
|
|
123
|
+
colors,
|
|
124
|
+
} from "@scripso-homepad/ui";
|
|
125
|
+
import { KeyIcon } from "@scripso-homepad/ui/src/icons/KeyIcon";
|
|
126
|
+
import { EyeIcon } from "@scripso-homepad/ui/src/icons/EyeIcon";
|
|
127
|
+
|
|
128
|
+
export function SignUpForm() {
|
|
81
129
|
return (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
130
|
+
<>
|
|
131
|
+
<Button
|
|
132
|
+
title="Continue"
|
|
133
|
+
variant="primary"
|
|
134
|
+
size="lg"
|
|
135
|
+
showIcon
|
|
136
|
+
onPress={() => {}}
|
|
137
|
+
className="w-full"
|
|
138
|
+
/>
|
|
139
|
+
|
|
140
|
+
<Input
|
|
141
|
+
label="Password"
|
|
142
|
+
placeholder="placeholder"
|
|
143
|
+
leftIcon={<KeyIcon />}
|
|
144
|
+
rightIcon={<EyeIcon />}
|
|
145
|
+
hint="Use at least 8 characters"
|
|
146
|
+
secureTextEntry
|
|
147
|
+
/>
|
|
148
|
+
|
|
149
|
+
<PhoneInput
|
|
150
|
+
label="Phone"
|
|
151
|
+
placeholder="placeholder"
|
|
152
|
+
hint="Include your area code"
|
|
153
|
+
onCountryCodeChange={(code) => console.log(code)}
|
|
154
|
+
/>
|
|
155
|
+
|
|
156
|
+
<CountryCodeSelector
|
|
157
|
+
value="AM"
|
|
158
|
+
onValueChange={(code) => console.log(code)}
|
|
159
|
+
/>
|
|
160
|
+
</>
|
|
87
161
|
);
|
|
88
162
|
}
|
|
89
163
|
```
|
|
@@ -92,93 +166,173 @@ export function Example() {
|
|
|
92
166
|
|
|
93
167
|
### `Button`
|
|
94
168
|
|
|
95
|
-
| Prop
|
|
96
|
-
|
|
97
|
-
| `title`
|
|
98
|
-
| `onPress`
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
169
|
+
| Prop | Type | Default | Description |
|
|
170
|
+
|------|------|---------|-------------|
|
|
171
|
+
| `title` | `string` | — | Button label (use `children` for custom content) |
|
|
172
|
+
| `onPress` | `(event) => void` | — | Press handler |
|
|
173
|
+
| `variant` | `"white" \| "primary" \| "green" \| "gray"` | `"primary"` | Visual style |
|
|
174
|
+
| `size` | `"lg" \| "sm"` | `"lg"` | Size preset |
|
|
175
|
+
| `showIcon` | `boolean` | `false` | Show icon badge |
|
|
176
|
+
| `icon` | `ReactNode` | — | Custom icon (defaults to arrow icon when `showIcon` is true) |
|
|
177
|
+
| `disabled` | `boolean` | `false` | Disable interaction |
|
|
178
|
+
| `style` | `StyleProp<ViewStyle>` | — | Container styles |
|
|
179
|
+
| `textStyle` | `StyleProp<TextStyle>` | — | Label styles |
|
|
180
|
+
| `className` | `string` | — | CSS classes on container (web) |
|
|
181
|
+
| `textClassName` | `string` | — | CSS classes on label (web) |
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Button title="Save" variant="green" size="sm" showIcon onPress={handleSave} />
|
|
185
|
+
<Button title="Custom" showIcon icon={<MyIcon />} onPress={handleSave} />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `Input`
|
|
189
|
+
|
|
190
|
+
Extends React Native `TextInput` props.
|
|
191
|
+
|
|
192
|
+
| Prop | Type | Default | Description |
|
|
193
|
+
|------|------|---------|-------------|
|
|
194
|
+
| `label` | `string` | — | Label above the field |
|
|
195
|
+
| `leftIcon` | `ReactNode` | — | Icon on the left (20px, color follows state) |
|
|
196
|
+
| `rightIcon` | `ReactNode` | — | Icon on the right |
|
|
197
|
+
| `error` | `string` | — | Error message (shown instead of hint) |
|
|
198
|
+
| `hint` | `string` | — | Helper text below the field |
|
|
199
|
+
| `className` | `string` | — | Wrapper CSS classes (web) |
|
|
200
|
+
| `labelClassName` | `string` | — | Label CSS classes (web) |
|
|
201
|
+
| `inputClassName` | `string` | — | Input CSS classes (web) |
|
|
202
|
+
| `errorClassName` | `string` | — | Error message CSS classes (web) |
|
|
203
|
+
| `hintClassName` | `string` | — | Hint message CSS classes (web) |
|
|
204
|
+
| `containerStyle` | `StyleProp<ViewStyle>` | — | Wrapper styles |
|
|
205
|
+
| `style` | `StyleProp<TextStyle>` | — | Input text styles |
|
|
206
|
+
|
|
207
|
+
Field height is **52px**. Icons receive `size` and `color` automatically when they accept those props.
|
|
208
|
+
|
|
209
|
+
### `PhoneInput`
|
|
210
|
+
|
|
211
|
+
Same props as `Input`, plus:
|
|
212
|
+
|
|
213
|
+
| Prop | Type | Description |
|
|
214
|
+
|------|------|-------------|
|
|
215
|
+
| `countryCode` | `string` | Selected country code (e.g. `"AM"`) |
|
|
216
|
+
| `onCountryCodeChange` | `(code: string) => void` | Country selection handler |
|
|
106
217
|
|
|
107
|
-
|
|
218
|
+
Error and hint messages render under the phone number field (not under the country selector).
|
|
219
|
+
|
|
220
|
+
### `CountryCodeSelector`
|
|
221
|
+
|
|
222
|
+
| Prop | Type | Default | Description |
|
|
223
|
+
|------|------|---------|-------------|
|
|
224
|
+
| `value` | `string` | `"AM"` | Selected country code |
|
|
225
|
+
| `onValueChange` | `(code: string) => void` | — | Selection handler |
|
|
226
|
+
| `options` | `Country[]` | built-in list | Country list override |
|
|
227
|
+
| `disabled` | `boolean` | `false` | Disable interaction |
|
|
228
|
+
| `style` | `StyleProp<ViewStyle>` | — | Wrapper styles |
|
|
229
|
+
| `className` | `string` | — | CSS classes (web) |
|
|
230
|
+
|
|
231
|
+
Also exports `countries`, `defaultCountry`, and `findCountry` for country data.
|
|
232
|
+
|
|
233
|
+
### `Label`
|
|
234
|
+
|
|
235
|
+
| Prop | Type | Default | Description |
|
|
236
|
+
|------|------|---------|-------------|
|
|
237
|
+
| `children` | `ReactNode` | — | Label text |
|
|
238
|
+
| `required` | `boolean` | `false` | Show required indicator |
|
|
239
|
+
| `disabled` | `boolean` | `false` | Muted disabled color |
|
|
240
|
+
| `className` | `string` | — | CSS classes (web) |
|
|
241
|
+
|
|
242
|
+
## Design tokens
|
|
108
243
|
|
|
109
244
|
```tsx
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
245
|
+
import {
|
|
246
|
+
colors,
|
|
247
|
+
brand,
|
|
248
|
+
stormGray,
|
|
249
|
+
navy,
|
|
250
|
+
rubyRed,
|
|
251
|
+
emeraldGreen,
|
|
252
|
+
spacing,
|
|
253
|
+
radii,
|
|
254
|
+
fonts,
|
|
255
|
+
buttonTypography,
|
|
256
|
+
labelTypography,
|
|
257
|
+
} from "@scripso-homepad/ui";
|
|
114
258
|
```
|
|
115
259
|
|
|
116
|
-
|
|
260
|
+
### Brand colors
|
|
261
|
+
|
|
262
|
+
| Name | Token | Hex |
|
|
263
|
+
|------|-------|-----|
|
|
264
|
+
| Slate Blue | `brand.slateBlue` | `#182E3C` |
|
|
265
|
+
| Storm Gray | `brand.stormGray` | `#455765` |
|
|
266
|
+
| Navy | `brand.navy` | `#08275D` |
|
|
267
|
+
| Ruby Red | `brand.rubyRed` | `#AE0011` |
|
|
268
|
+
| Emerald Green | `brand.emeraldGreen` | `#279D54` |
|
|
117
269
|
|
|
118
|
-
|
|
270
|
+
### Color scales
|
|
271
|
+
|
|
272
|
+
Each palette has steps `0`, `0.5`, `1`, `1.5`, `2` … `8`, `8.5`, exposed as flat tokens:
|
|
119
273
|
|
|
120
274
|
```tsx
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
275
|
+
colors.stormGray0 // #FBFCFC
|
|
276
|
+
colors.stormGray50 // #ECEEF0 (Figma 0.5)
|
|
277
|
+
colors.stormGray500 // #455765 (brand)
|
|
278
|
+
colors.stormGray850 // #151A1E (Figma 8.5)
|
|
279
|
+
|
|
280
|
+
colors.navy500 // brand navy
|
|
281
|
+
colors.rubyRed500 // brand ruby red
|
|
282
|
+
colors.emeraldGreen500
|
|
127
283
|
```
|
|
128
284
|
|
|
129
|
-
|
|
285
|
+
Or use the scale objects: `stormGray["0.5"]`, `navy["5"]`, `rubyRed["5"]`, `emeraldGreen["5"]`.
|
|
286
|
+
|
|
287
|
+
## Styling on web
|
|
130
288
|
|
|
131
|
-
`className`
|
|
289
|
+
`className` props apply Tailwind / CSS classes to the underlying DOM node on web only. On native they are no-ops.
|
|
132
290
|
|
|
133
291
|
```tsx
|
|
134
292
|
<Button
|
|
135
|
-
title="
|
|
136
|
-
onPress={
|
|
137
|
-
className="
|
|
138
|
-
textClassName="text-sm font-bold uppercase"
|
|
293
|
+
title="Submit"
|
|
294
|
+
onPress={handleSubmit}
|
|
295
|
+
className="w-full max-w-md"
|
|
139
296
|
/>
|
|
140
|
-
```
|
|
141
297
|
|
|
142
|
-
|
|
298
|
+
<Input
|
|
299
|
+
label="Email"
|
|
300
|
+
className="w-full"
|
|
301
|
+
inputClassName="text-base"
|
|
302
|
+
/>
|
|
303
|
+
```
|
|
143
304
|
|
|
144
|
-
|
|
305
|
+
When overriding default colors with Tailwind, use `!` (important) because react-native-web applies inline styles:
|
|
145
306
|
|
|
146
|
-
```
|
|
147
|
-
|
|
307
|
+
```tsx
|
|
308
|
+
<Button title="Delete" className="!bg-red-600" onPress={handleDelete} />
|
|
148
309
|
```
|
|
149
310
|
|
|
150
311
|
## Development
|
|
151
312
|
|
|
152
313
|
```bash
|
|
153
314
|
npm install
|
|
154
|
-
npm run build
|
|
155
315
|
npm run typecheck
|
|
156
316
|
npm run lint
|
|
317
|
+
npm run build
|
|
318
|
+
npm run storybook # http://localhost:6006
|
|
319
|
+
npm run build-storybook
|
|
157
320
|
```
|
|
158
321
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
Preview components in the browser with hot reload:
|
|
322
|
+
Clear Storybook cache if needed:
|
|
162
323
|
|
|
163
324
|
```bash
|
|
164
|
-
|
|
325
|
+
rm -rf node_modules/.cache/storybook node_modules/.vite
|
|
165
326
|
```
|
|
166
327
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
Build a static Storybook site:
|
|
328
|
+
## Architecture
|
|
170
329
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
330
|
+
- **Shared implementation** — one component per platform, React Native primitives throughout
|
|
331
|
+
- **Web icons** — `.web.tsx` files use plain SVG (no `react-native-svg` on web)
|
|
332
|
+
- **Native icons** — `react-native-svg` (peer dependency)
|
|
333
|
+
- **Source resolution** — bundlers import from `src/index.ts` for correct `.web.tsx` resolution
|
|
334
|
+
- **Metro / Expo** — `"react-native"` package export points to TypeScript source
|
|
174
335
|
|
|
175
336
|
## Publishing
|
|
176
337
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
## Architecture
|
|
180
|
-
|
|
181
|
-
- **Single shared implementation** — no `.web.tsx` / `.native.tsx` split
|
|
182
|
-
- **React Native primitives** — `TouchableOpacity`, `Text`, `StyleSheet`
|
|
183
|
-
- **Web via react-native-web** — consumers alias `react-native` at build time
|
|
184
|
-
- **Metro source resolution** — `"react-native"` export points to TypeScript source for native bundlers
|
|
338
|
+
See [PUBLISHING.md](./PUBLISHING.md).
|