Files
ewa-ui/README.md
T
Kaloyan Danchev cdc8829ce7 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>
2026-03-18 19:34:11 +02:00

6.1 KiB

@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

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.

// 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 for the full variable list.

Components

All components are named exports from @ampeco/ewa-ui:

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.

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'danger' | 'text';
  fullWidth?: boolean;
  loading?: boolean;
}
<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.

interface CardProps {
  className?: string;
  onClick?: () => void;
  children: React.ReactNode;
}
<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.

interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className'> {
  label: string;
  hint?: string;
  error?: string;
  className?: string;
}
<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.

interface StatusBadgeProps {
  status: string;
  label: string;
}
<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.

interface LoaderProps {
  fullScreen?: boolean;
}
<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.

interface EmptyStateProps {
  icon?: React.ReactNode;
  title: string;
  description?: string;
  action?: { label: string; onClick: () => void };
}
<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.

interface ToastProps {
  message: string;
  type: 'success' | 'error';
  onDismiss: () => void;
  duration?: number; // ms, default 3000
}
<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.

interface BottomSheetProps {
  open: boolean;
  onClose: () => void;
  children: React.ReactNode;
}
<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

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