cdc8829ce7
Shared React component library with Tailwind plugin for AMPECO EWAs. 8 components: Button, Card, Input, StatusBadge, Loader, EmptyState, Toast, BottomSheet. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
262 lines
6.1 KiB
Markdown
262 lines
6.1 KiB
Markdown
# @ampeco/ewa-ui
|
|
|
|
Shared React component library and Tailwind CSS plugin for AMPECO EWA (Embedded Web App) projects. Provides themed, accessible UI primitives driven entirely by CSS custom properties.
|
|
|
|
## Install
|
|
|
|
```bash
|
|
npm install @ampeco/ewa-ui
|
|
```
|
|
|
|
Peer dependencies: `react >= 18`, `react-dom >= 18`.
|
|
|
|
## Tailwind Plugin Setup
|
|
|
|
The package ships a Tailwind plugin at `@ampeco/ewa-ui/plugin` that registers design tokens (colors, spacing, radii, typography, animations) and status-color utilities.
|
|
|
|
```js
|
|
// tailwind.config.js
|
|
import { ewaUiPlugin } from '@ampeco/ewa-ui/plugin';
|
|
|
|
export default {
|
|
content: [
|
|
'./src/**/*.{ts,tsx}',
|
|
'./node_modules/@ampeco/ewa-ui/dist/**/*.{js,cjs}', // scan library classes
|
|
],
|
|
plugins: [ewaUiPlugin],
|
|
};
|
|
```
|
|
|
|
You must define the CSS custom properties the plugin references. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full variable list.
|
|
|
|
## Components
|
|
|
|
All components are named exports from `@ampeco/ewa-ui`:
|
|
|
|
```tsx
|
|
import { Button, Card, Input, StatusBadge, Loader, EmptyState, Toast, BottomSheet } from '@ampeco/ewa-ui';
|
|
```
|
|
|
|
Type-only imports are also available (e.g. `import type { ButtonProps } from '@ampeco/ewa-ui'`).
|
|
|
|
---
|
|
|
|
### Button
|
|
|
|
Themed button with four variants and a built-in loading spinner.
|
|
|
|
```tsx
|
|
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
variant?: 'primary' | 'secondary' | 'danger' | 'text';
|
|
fullWidth?: boolean;
|
|
loading?: boolean;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Button variant="primary" onClick={handleSave}>Save Booking</Button>
|
|
<Button variant="secondary" fullWidth>Cancel</Button>
|
|
<Button variant="danger" loading={isDeleting}>Delete</Button>
|
|
<Button variant="text">Learn more</Button>
|
|
```
|
|
|
|
- Height is fixed at `h-11` (44px) for touch-target compliance.
|
|
- `loading` shows an animated spinner overlay and hides children via `invisible`.
|
|
- `disabled` and `loading` both set `opacity-50 cursor-not-allowed`.
|
|
- Focus ring uses `focus-visible:ring-2` with the primary button color.
|
|
|
|
---
|
|
|
|
### Card
|
|
|
|
Surface container with optional click interactivity.
|
|
|
|
```tsx
|
|
interface CardProps {
|
|
className?: string;
|
|
onClick?: () => void;
|
|
children: React.ReactNode;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Card>
|
|
<p>Static content</p>
|
|
</Card>
|
|
|
|
<Card onClick={() => navigate(`/bookings/${id}`)}>
|
|
<p>Clickable card with hover scale and keyboard support</p>
|
|
</Card>
|
|
```
|
|
|
|
- When `onClick` is provided, the card renders with `role="button"`, `tabIndex={0}`, and `Enter`/`Space` key handlers.
|
|
- Hover applies `scale-[1.01]`, active applies `scale-[0.99]`.
|
|
|
|
---
|
|
|
|
### Input
|
|
|
|
Labeled text input with error and hint support. Forwards refs.
|
|
|
|
```tsx
|
|
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'> {
|
|
label: string;
|
|
hint?: string;
|
|
error?: string;
|
|
className?: string;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Input label="Connector ID" placeholder="e.g. CPI-001" />
|
|
<Input label="Email" error="Invalid email address" />
|
|
<Input label="Duration" hint="In minutes" type="number" />
|
|
```
|
|
|
|
- Auto-generates `id` from label text if none is provided.
|
|
- Error state turns the border red; hint is hidden when error is present.
|
|
- Height is `h-11` (44px touch target).
|
|
|
|
---
|
|
|
|
### StatusBadge
|
|
|
|
Colored pill for booking/reservation status display.
|
|
|
|
```tsx
|
|
interface StatusBadgeProps {
|
|
status: string;
|
|
label: string;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<StatusBadge status="accepted" label="Accepted" />
|
|
<StatusBadge status="no_show" label="No Show" />
|
|
<StatusBadge status="completed" label="Completed" />
|
|
```
|
|
|
|
Recognized statuses: `accepted`, `reserved`, `completed`, `cancelled`, `no_show` / `no-show`, `failed`. Text color uses the corresponding `--status-*` variable; background uses a 15% opacity `color-mix()` utility (see architecture docs).
|
|
|
|
---
|
|
|
|
### Loader
|
|
|
|
Spinning ring indicator with optional full-screen centering.
|
|
|
|
```tsx
|
|
interface LoaderProps {
|
|
fullScreen?: boolean;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Loader />
|
|
<Loader fullScreen />
|
|
```
|
|
|
|
- `fullScreen` wraps the spinner in a `min-h-[60dvh]` flex container.
|
|
- Uses `role="status"` and `aria-label="Loading"`.
|
|
|
|
---
|
|
|
|
### EmptyState
|
|
|
|
Centered placeholder for empty lists or zero-data screens.
|
|
|
|
```tsx
|
|
interface EmptyStateProps {
|
|
icon?: React.ReactNode;
|
|
title: string;
|
|
description?: string;
|
|
action?: { label: string; onClick: () => void };
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<EmptyState
|
|
icon={<CalendarIcon />}
|
|
title="No bookings yet"
|
|
description="Create your first booking to get started."
|
|
action={{ label: 'New Booking', onClick: () => navigate('/create') }}
|
|
/>
|
|
```
|
|
|
|
- Internally renders a `Button` with `variant="primary"` for the action.
|
|
|
|
---
|
|
|
|
### Toast
|
|
|
|
Auto-dismissing notification banner pinned to the top of the viewport.
|
|
|
|
```tsx
|
|
interface ToastProps {
|
|
message: string;
|
|
type: 'success' | 'error';
|
|
onDismiss: () => void;
|
|
duration?: number; // ms, default 3000
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<Toast message="Booking created" type="success" onDismiss={() => setToast(null)} />
|
|
<Toast message="Failed to save" type="error" onDismiss={() => setToast(null)} duration={5000} />
|
|
```
|
|
|
|
- Uses `animate-slide-in` for entry animation.
|
|
- Dismiss button is 44px square for touch accessibility.
|
|
- Auto-clears via `setTimeout`; cleans up on unmount.
|
|
|
|
---
|
|
|
|
### BottomSheet
|
|
|
|
Modal drawer that slides up from the bottom with a backdrop overlay.
|
|
|
|
```tsx
|
|
interface BottomSheetProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
children: React.ReactNode;
|
|
}
|
|
```
|
|
|
|
```tsx
|
|
<BottomSheet open={isOpen} onClose={() => setIsOpen(false)}>
|
|
<h2>Select a time slot</h2>
|
|
{/* ... */}
|
|
</BottomSheet>
|
|
```
|
|
|
|
- Locks body scroll when open (`overflow: hidden`), restores on close.
|
|
- Overlay click triggers `onClose`.
|
|
- Bottom padding accounts for `safe-area-inset-bottom` (notched devices).
|
|
- Includes a drag-handle bar at the top.
|
|
- Uses `role="dialog"` and `aria-modal="true"`.
|
|
|
|
## Build
|
|
|
|
```bash
|
|
npm run build # Production build via tsup (ESM + CJS + .d.ts)
|
|
npm run dev # Watch mode
|
|
npm run typecheck # Type-check without emitting
|
|
```
|
|
|
|
## Source Files
|
|
|
|
```
|
|
src/
|
|
index.ts # Public re-exports
|
|
plugin.ts # Tailwind CSS plugin (design tokens + utilities)
|
|
components/
|
|
Button.tsx
|
|
Card.tsx
|
|
Input.tsx
|
|
StatusBadge.tsx
|
|
Loader.tsx
|
|
EmptyState.tsx
|
|
Toast.tsx
|
|
BottomSheet.tsx
|
|
```
|