@scripso-homepad/ui
v0.4.14
Published
Cross-platform UI components for Homepad (React Web + React Native)
Maintainers
Readme
@scripso-homepad/ui
Cross-platform UI component library for Homepad. Built with React Native primitives — works in Expo / React Native and React web (via react-native-web).
Components
| Component | Description |
|-----------|-------------|
| Button | Primary actions with variants, sizes, and optional icon badge |
| Input | Text field with label, left/right icons, error, and hint |
| PhoneInput | Country code selector + phone number field |
| CountryCodeSelector | Standalone country dial-code dropdown |
| Label | Form field label |
Installation
React Native (Expo)
npm install @scripso-homepad/ui react-native-svgExpo resolves the package from TypeScript source automatically (react-native export). Icons use react-native-svg on native.
React web
npm install @scripso-homepad/ui react-native-webConfigure your bundler to alias react-native → react-native-web and resolve .web.tsx icon files.
Vite
// vite.config.ts
import path from "node:path";
import { existsSync } from "node:fs";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
function resolveUiPackageEntry() {
const workspaceEntry = path.resolve(__dirname, "../homepad-ui/src/index.ts");
if (existsSync(workspaceEntry)) return workspaceEntry;
return path.resolve(__dirname, "node_modules/@scripso-homepad/ui/src/index.ts");
}
export default defineConfig({
plugins: [
react({
include: [
/\.[tj]sx?$/,
/node_modules\/(@scripso-homepad\/ui|react-native|react-native-web)/,
],
}),
],
resolve: {
dedupe: ["react", "react-dom", "react-native", "react-native-web"],
alias: {
"react-native": "react-native-web",
react: path.resolve(__dirname, "node_modules/react"),
"react-dom": path.resolve(__dirname, "node_modules/react-dom"),
"react/jsx-runtime": path.resolve(__dirname, "node_modules/react/jsx-runtime"),
"react/jsx-dev-runtime": path.resolve(__dirname, "node_modules/react/jsx-dev-runtime"),
"@scripso-homepad/ui": resolveUiPackageEntry(),
},
extensions: [
".web.tsx",
".web.ts",
".web.jsx",
".web.js",
".tsx",
".ts",
".jsx",
".js",
],
},
optimizeDeps: {
include: ["react", "react-dom", "react/jsx-runtime", "react-native-web"],
exclude: ["react-native-svg", "@scripso-homepad/ui"],
esbuildOptions: {
resolveExtensions: [
".web.js",
".web.jsx",
".web.ts",
".web.tsx",
".js",
".jsx",
".ts",
".tsx",
],
},
},
});Next.js
// next.config.js
module.exports = {
transpilePackages: ["@scripso-homepad/ui", "react-native", "react-native-web"],
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
"react-native$": "react-native-web",
};
config.resolve.extensions = [
".web.tsx",
".web.ts",
".web.jsx",
".web.js",
...config.resolve.extensions,
];
return config;
},
};Usage
import {
Button,
Input,
PhoneInput,
CountryCodeSelector,
colors,
} from "@scripso-homepad/ui";
import { KeyIcon } from "@scripso-homepad/ui/src/icons/KeyIcon";
import { EyeIcon } from "@scripso-homepad/ui/src/icons/EyeIcon";
export function SignUpForm() {
return (
<>
<Button
title="Continue"
variant="primary"
size="lg"
showIcon
onPress={() => {}}
className="w-full"
/>
<Input
label="Password"
placeholder="placeholder"
leftIcon={<KeyIcon />}
rightIcon={<EyeIcon />}
hint="Use at least 8 characters"
secureTextEntry
/>
<PhoneInput
label="Phone"
placeholder="placeholder"
hint="Include your area code"
onCountryCodeChange={(code) => console.log(code)}
/>
<CountryCodeSelector
value="AM"
onValueChange={(code) => console.log(code)}
/>
</>
);
}API
Button
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| title | string | — | Button label (use children for custom content) |
| onPress | (event) => void | — | Press handler |
| variant | "white" \| "primary" \| "green" \| "gray" | "primary" | Visual style |
| size | "lg" \| "sm" | "lg" | Size preset |
| showIcon | boolean | false | Show icon badge |
| icon | ReactNode | — | Custom icon (defaults to arrow icon when showIcon is true) |
| disabled | boolean | false | Disable interaction |
| style | StyleProp<ViewStyle> | — | Container styles |
| textStyle | StyleProp<TextStyle> | — | Label styles |
| className | string | — | CSS classes on container (web) |
| textClassName | string | — | CSS classes on label (web) |
<Button title="Save" variant="green" size="sm" showIcon onPress={handleSave} />
<Button title="Custom" showIcon icon={<MyIcon />} onPress={handleSave} />Input
Extends React Native TextInput props.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| label | string | — | Label above the field |
| leftIcon | ReactNode | — | Icon on the left (20px, color follows state) |
| rightIcon | ReactNode | — | Icon on the right |
| error | string | — | Error message (shown instead of hint) |
| hint | string | — | Helper text below the field |
| className | string | — | Wrapper CSS classes (web) |
| labelClassName | string | — | Label CSS classes (web) |
| labelStyles | StyleProp<TextStyle> | — | Label styles (overrides default 12/16/500) |
| inputClassName | string | — | Input CSS classes (web) |
| errorClassName | string | — | Error message CSS classes (web) |
| hintClassName | string | — | Hint message CSS classes (web) |
| containerStyle | StyleProp<ViewStyle> | — | Wrapper styles |
| style | StyleProp<TextStyle> | — | Input text styles |
Field height is 52px. Icons receive size and color automatically when they accept those props.
PhoneInput
Same props as Input, plus:
| Prop | Type | Description |
|------|------|-------------|
| countryCode | string | Selected country code (e.g. "AM") |
| onCountryCodeChange | (code: string) => void | Country selection handler |
Error and hint messages render under the full phone row (aligned with the country selector), matching the field label.
CountryCodeSelector
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| value | string | "AM" | Selected country code |
| onValueChange | (code: string) => void | — | Selection handler |
| options | Country[] | built-in list | Country list override |
| disabled | boolean | false | Disable interaction |
| style | StyleProp<ViewStyle> | — | Wrapper styles |
| className | string | — | CSS classes (web) |
Also exports countries, defaultCountry, and findCountry for country data.
Label
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| children | ReactNode | — | Label text |
| required | boolean | false | Show required indicator |
| disabled | boolean | false | Muted disabled color |
| className | string | — | CSS classes (web) |
Design tokens
import {
colors,
brand,
stormGray,
navy,
rubyRed,
emeraldGreen,
spacing,
radii,
fonts,
buttonTypography,
labelTypography,
} from "@scripso-homepad/ui";Brand colors
| Name | Token | Hex |
|------|-------|-----|
| Slate Blue | brand.slateBlue | #182E3C |
| Storm Gray | brand.stormGray | #455765 |
| Navy | brand.navy | #082640 |
| Ruby Red | brand.rubyRed | #AE0011 |
| Emerald Green | brand.emeraldGreen | #279D54 |
Color scales
Each palette has steps 0, 0.5, 1, 1.5, 2 … 8, 8.5, exposed as flat tokens:
colors.stormGray0 // #FBFCFC
colors.stormGray50 // #ECEEF0 (Figma 0.5)
colors.stormGray500 // #455765 (brand)
colors.stormGray850 // #151A1E (Figma 8.5)
colors.navy500 // brand navy
colors.rubyRed500 // brand ruby red
colors.emeraldGreen500Or use the scale objects: stormGray["0.5"], navy["5"], rubyRed["5"], emeraldGreen["5"].
Styling on web
className props apply Tailwind / CSS classes to the underlying DOM node on web only. On native they are no-ops.
<Button
title="Submit"
onPress={handleSubmit}
className="w-full max-w-md"
/>
<Input
label="Email"
className="w-full"
inputClassName="text-base"
/>When overriding default colors with Tailwind, use ! (important) because react-native-web applies inline styles:
<Button title="Delete" className="!bg-red-600" onPress={handleDelete} />Development
npm install
npm run typecheck
npm run lint
npm run build
npm run storybook # http://localhost:6006
npm run build-storybookClear Storybook cache if needed:
rm -rf node_modules/.cache/storybook node_modules/.viteArchitecture
- Shared implementation — one component per platform, React Native primitives throughout
- Web icons —
.web.tsxfiles use plain SVG (noreact-native-svgon web) - Native icons —
react-native-svg(peer dependency) - Source resolution — bundlers import from
src/index.tsfor correct.web.tsxresolution - Metro / Expo —
"react-native"package export points to TypeScript source
Publishing
See PUBLISHING.md.
