# @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 { variant?: 'primary' | 'secondary' | 'danger' | 'text'; fullWidth?: boolean; loading?: boolean; } ``` ```tsx ``` - 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

Static content

navigate(`/bookings/${id}`)}>

Clickable card with hover scale and keyboard support

``` - 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, 'className'> { label: string; hint?: string; error?: string; className?: string; } ``` ```tsx ``` - 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 ``` 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 ``` - `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 } 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 setToast(null)} /> 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 setIsOpen(false)}>

Select a time slot

{/* ... */}
``` - 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 ```