Initial commit: @ampeco/ewa-ui v1.0.0

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>
This commit is contained in:
Kaloyan Danchev
2026-03-18 19:34:11 +02:00
commit cdc8829ce7
17 changed files with 3389 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
# @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
```