@ecohouse/ui
v0.1.32
Published
Cross-platform presentation-only UI components for EcoHouse (React Native + React Native Web)
Downloads
4,178
Maintainers
Readme
@ecohouse/ui
Cross-platform UI component library for EcoHouse. Built with React Native primitives — works in Expo / React Native and React web (via react-native-web).
Components
| Component | Description |
| ----------------------- | ----------------------------------------------------------------------------- |
| Button | Pill CTA — primary / secondary, sizes lg / sm, optional arrow badge |
| Badge | Pill label chip — default / transparent, optional leading icon |
| StatusBadge | Compact status chip — active / completed / cancelled (booking headers) |
| PaginationDots | Carousel dots — track 48 wide, height 6, gap 4 |
| InfoTile | Icon + title + description tile (house rules / info cards) |
| StepList | Vertical numbered process steps with connector lines |
| AccountHeader | Account page header — title, optional back/menu, badge slot, notifications |
| Input | Text field with label, hint, left/right icons, error & disabled states |
| VerificationCodeInput | Responsive numeric code cells with paste, focus, error, and disabled states |
| Checkbox | Square checkbox with optional label, controlled or uncontrolled |
| Toggle | Animated on/off switch with optional label |
| Select | Single/multi-select dropdown with search and keyboard navigation |
| DatePicker | Single/range calendar dropdown with month navigation and min/max bounds |
| Textarea | Multi-line text field, same label/hint/state chrome as Input |
| Avatar | Logged-in user chip (image/name/email) with an optional account dropdown menu |
| AvatarSimple | Compact account card (avatar + name/email + logout) for sidebars |
| Sidebar | Account sidebar — logo, nav items, collapse, AvatarSimple footer |
| SegmentedToggle | 2- or 3-option pill control with sliding active indicator |
| LanguageSwitcher | Compact controlled/uncontrolled language dropdown |
| Icon | Dynamic icon by name from the registry (<Icon name="user" />) |
Primitives
Small reusable building blocks (Storybook: Primitives /*):
| Primitive | Description |
| ----------- | ----------------------------------------------------------------------- |
| BrandLogo | Cortel brand mark — white/brand variants with configurable dimensions |
| MenuItem | Option row (icon + label + action) — used in Avatar menus or standalone |
Full props: docs/components.md.
Named icon exports (UserIcon, HomeIcon, …) are also available. Full icon list: docs/icons.md.
Installation
React Native (Expo)
npm install @ecohouse/ui react-native-svgExpo resolves the package from TypeScript source (react-native export). Icons use react-native-svg on native.
React web
npm install @ecohouse/ui react-native react-native-web react-native-svgConfigure your bundler to alias react-native → react-native-web and prefer .web.tsx icon files.
Vite
// vite.config.ts
import path from "node:path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [
react({
include: [/\.[tj]sx?$/, /node_modules\/(@ecohouse\/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"),
},
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", "@ecohouse/ui"],
esbuildOptions: {
resolveExtensions: [
".web.js",
".web.jsx",
".web.ts",
".web.tsx",
".js",
".jsx",
".ts",
".tsx",
],
},
},
});Next.js
// next.config.js / next.config.ts
module.exports = {
transpilePackages: ["@ecohouse/ui", "react-native-web", "react-native-svg"],
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
"react-native$": "react-native-web",
};
config.resolve.extensions = [
".web.js",
".web.jsx",
".web.ts",
".web.tsx",
...config.resolve.extensions,
];
return config;
},
};Optional web typography helpers (import fonts first):
import "@ecohouse/ui/web/fonts.css";
import "@ecohouse/ui/web/typography.css";Usage
import { Button, Badge, Input, Icon, HomeIcon, UserIcon, ShowIcon, colors } from "@ecohouse/ui";
export function Example() {
return (
<>
<Button title="Continue" variant="primary" size="lg" showIcon onPress={() => {}} />
<Badge label="Home" icon={HomeIcon} variant="transparent" />
<Input
label="Password"
placeholder="••••••••"
hint="Use at least 8 characters"
secureTextEntry
leftIcon={UserIcon}
rightIcon={ShowIcon}
onRightIconPress={() => {}}
onChangeText={() => {}}
/>
<Icon name="home" size={20} color={colors.white} />
</>
);
}Same API on web and mobile.
API
Quick reference for Button, Badge, Input, and Icon below. Full API for Checkbox, Toggle, Select, DatePicker, Textarea, Avatar, and MenuItem lives in docs/components.md.
Button
Pill CTA — sizes lg (44px) / sm (32px). Secondary fill uses colors.grey500.
| Prop | Type | Default | Description |
| ---------- | -------------------------- | ----------- | ----------------- |
| title | string | — | Label text |
| onPress | (event) => void | required | Press handler |
| variant | "primary" \| "secondary" | "primary" | Fill style |
| size | "lg" \| "sm" | "lg" | Height / padding |
| showIcon | boolean | false | Right arrow badge |
| icon | ReactNode | — | Custom badge icon |
| disabled | boolean | false | 60% opacity |
<Button title="Continue" variant="primary" size="lg" showIcon onPress={() => {}} />
<Button title="Cancel" variant="secondary" size="sm" onPress={() => {}} />Badge
Pill label chip — default / transparent. Optional leading HomeIcon (color follows the variant).
| Prop | Type | Default | Description |
| --------- | ---------------------------- | ----------- | -------------------------- |
| label | string | required | Chip text |
| variant | "default" \| "transparent" | "default" | Fill / border / icon color |
| icon | IconComponent | — | Optional leading icon |
| Variant | Fill | Border | Icon color |
| ------------- | --------- | --------------- | ---------- |
| default | grey750 | none | white |
| transparent | white 5% | 1px #FFFFFF0D | primary |
<Badge label="Label" />
<Badge label="Label" icon={HomeIcon} variant="transparent" />Input
Extends React Native TextInput props. Default size lg (44px pill, radius 60).
| Prop | Type | Default | Description |
| ------------------------ | ------------------------------------------------------------ | ---------- | --------------------------------------------------------------- |
| label / showLabel | string / boolean | — / true | Top label; omitted when no label is provided |
| hint / showHint | string / boolean | — / true | Bottom helper; omitted when no hint is provided |
| placeholder | string | — | Field placeholder |
| size | "lg" \| "sm" | "lg" | Field height |
| state | "default" \| "filled" \| "active" \| "error" \| "disabled" | derived | Override auto state |
| disabled / error | boolean | false | Disabled / error chrome |
| showLeftIcon | boolean | false | Defaults to UserIcon |
| showRightIcon | boolean | false | Defaults to ShowIcon |
| leftIcon / rightIcon | InputIcon | — | Custom icon component or element; receives state color and size |
| onRightIconPress | () => void | — | Makes right icon pressable |
| State | Behavior |
| ---------- | -------------------------------------------------------------------- |
| default | Grey field (grey700), white label, grey hint |
| filled | Same chrome when value is present |
| active | On focus — border primaryMuted, left icon primary |
| error | Border redMuted; label, text, placeholder, hint, left icon = red |
| disabled | Not editable, muted icons/text |
Icon
| Prop | Type | Default | Description |
| ------- | ---------- | ---------------- | ------------------- |
| name | IconName | — | Registry key |
| size | number | 20 or 24 | Matches SVG viewBox |
| color | string | colors.primary | Stroke / fill |
import { Icon, iconNames, iconGroups } from "@ecohouse/ui";
<Icon name="user" size={20} color="#fff" />;More: docs/icons.md, docs/components.md.
Design tokens
import {
colors,
grey,
fonts,
badgeTypography,
buttonTypography,
inputTypography,
} from "@ecohouse/ui";| Token | Value | Use |
| ------------------- | --------------- | ----------------------------------------- |
| primary | #B46436 | Brand / active left icon / open chevron |
| primaryMuted | #B464360F | Input active border |
| primaryHover | #B4643633 | DatePicker in-range day background |
| red | #A63E3E | Error text / icons / LogOutIcon default |
| redMuted | #A63E3E0F | Input error border / subtle danger row |
| redHover | #A63E3E33 | Danger row hover background |
| green | #34A853 | Success / positive |
| black / white | #000 / #fff | Surfaces / labels |
| grey0 … grey950 | Grey scale | e.g. grey700 = #171717 |
colors.primary;
colors.grey700;
grey["5"]; // same as colors.grey500Full token reference: docs/tokens.md.
Development
npm install
npm run typecheck
npm run lint
npm run test
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 — React Native primitives throughout (one API for web + mobile)
- Web icons —
.web.tsxuses plain SVG (noreact-native-svgon web) - Native icons —
react-native-svg(peer dependency) - Source resolution — package
react-native/importexports point atsrc/index.tsfor correct.web.tsxresolution - Presentation only — no API clients, secrets, or booking domain logic
Publishing
Push to main after CI verify passes. The Release job publishes @ecohouse/ui to npm (bumps patch if the current version already exists). Requires the NPM_TOKEN repository secret.
Consumers:
npm install @ecohouse/ui@^0.1.0Package rules
- No backend API clients, secrets, provider SDKs, or domain business logic
- App routes and booking logic stay in
eco-cottage-web/eco-cottage-mobile - Prefer published npm versions in consumers
