PSA/packages/ui-kit/README.md
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

531 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# @alga-psa/ui-kit
Zero-dependency React component library for Alga extensions. All components use CSS custom properties for theming and work seamlessly inside extension iframes.
> See the **showcase extension** (`ee/extensions/showcase`) for interactive demos of every component.
## Install
Import the theme stylesheet once at the root of your app:
```ts
import '@alga-psa/ui-kit/theme.css';
```
---
## Theme Tokens
Tokens are exposed as CSS variables. When running inside an extension iframe the host automatically injects them into `:root`.
| Variable | Light | Dark | Purpose |
|----------|-------|------|---------|
| `--alga-bg` | `#ffffff` | `#0b0f14` | Background |
| `--alga-fg` | `#111111` | `#e5e7eb` | Foreground / text |
| `--alga-muted` | `#f5f5f7` | `#0f1720` | Muted background |
| `--alga-muted-fg` | `#4b5563` | `#9ca3af` | Muted text |
| `--alga-primary` | `#9855ee` | `#8a4dea` | Primary (purple) |
| `--alga-primary-foreground` | `#ffffff` | `#ffffff` | Text on primary |
| `--alga-secondary` | `#53d7fa` | `#40cff9` | Secondary (blue) |
| `--alga-secondary-foreground` | `#111111` | `#0b0f14` | Text on secondary |
| `--alga-border` | `#e5e7eb` | `#1f2937` | Borders |
| `--alga-radius` | `8px` | `8px` | Border radius |
| `--alga-danger` | `#dc2626` | `#ef4444` | Danger / error |
| `--alga-warning` | `#d97706` | `#f59e0b` | Warning |
| `--alga-success` | `#16a34a` | `#22c55e` | Success |
You can also access tokens programmatically:
```ts
import { tokens } from '@alga-psa/ui-kit';
tokens.primary // 'var(--alga-primary)'
tokens.bg // 'var(--alga-bg)'
```
---
## Components
### Core
#### `Button`
Themed button with multiple variants and sizes.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'primary' \| 'secondary' \| 'destructive' \| 'outline' \| 'ghost' \| 'link' \| 'soft' \| 'dashed'` | `'primary'` | Visual style |
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg' \| 'icon'` | `'md'` | Controls padding and font size |
```tsx
<Button variant="secondary" size="sm">Save</Button>
```
#### `Text`
Typography component with size, tone, and weight presets.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `as` | `'span' \| 'p' \| 'label' \| 'strong'` | `'span'` | HTML element to render |
| `size` | `'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Font size preset |
| `tone` | `'default' \| 'muted' \| 'danger' \| 'warning' \| 'success'` | `'default'` | Text colour |
| `weight` | `400 \| 500 \| 600 \| 700` | `400` | Font weight |
```tsx
<Text as="p" size="lg" tone="muted" weight={600}>Hello</Text>
```
#### `Card`
Bordered container with background, shadow, and rounded corners. Extends standard `<div>` attributes.
```tsx
<Card style={{ padding: 24 }}>Content here</Card>
```
#### `Badge`
Small pill-shaped label for status indicators.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tone` | `'default' \| 'info' \| 'success' \| 'warning' \| 'danger'` | `'default'` | Colour scheme |
```tsx
<Badge tone="success">Active</Badge>
```
#### `Alert`, `AlertTitle`, `AlertDescription`
Contextual alert banner with optional icon.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tone` | `'info' \| 'success' \| 'warning' \| 'danger'` | `'info'` | Visual tone |
| `showIcon` | `boolean` | `true` | Show tone-specific icon |
```tsx
<Alert tone="warning">
<AlertTitle>Heads up</AlertTitle>
<AlertDescription>This action cannot be undone.</AlertDescription>
</Alert>
```
#### `Separator`
Thin divider line for separating content.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction of the line |
| `style` | `CSSProperties` | — | Additional inline styles |
---
### Form
#### `Input`
Text input with error state support.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `error` | `boolean` | — | Show error styling |
| `errorMessage` | `string` | — | Error message displayed below input |
```tsx
<Input placeholder="Email" error={!!err} errorMessage={err} />
```
#### `TextArea`
Multi-line text input. Extends `<textarea>` attributes.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `error` | `boolean` | — | Show error border |
| `errorMessage` | `string` | — | Error text below the field |
| `resize` | `'none' \| 'vertical' \| 'horizontal' \| 'both'` | `'vertical'` | Resize behaviour |
#### `CustomSelect`
Dropdown with search filtering.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `options` | `SelectOption[]` | — | Available options |
| `value` | `string` | — | Selected value |
| `onChange` | `(value: string) => void` | — | Change callback |
| `placeholder` | `string` | — | Placeholder text |
| `searchable` | `boolean` | `true` | Enable search filtering |
| `disabled` | `boolean` | `false` | Disable the select |
#### `SearchInput`
Input with built-in search icon and optional debounce.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `onSearch` | `(value: string) => void` | — | Debounced search callback |
| `debounceMs` | `number` | `300` | Debounce delay in ms |
| `size` | `'sm' \| 'md'` | `'md'` | Input size |
#### `Checkbox`
Themed checkbox with label.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `label` | `string` | — | Label text |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size preset |
#### `RadioGroup`
Group of mutually exclusive radio buttons.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `options` | `RadioOption[]` | — | Available options |
| `value` | `string` | — | Selected value |
| `onChange` | `(value: string) => void` | — | Change callback |
| `name` | `string` | auto | HTML name attribute |
| `disabled` | `boolean` | `false` | Disable all options |
| `orientation` | `'horizontal' \| 'vertical'` | `'vertical'` | Layout direction |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size preset |
`RadioOption`: `{ value: string; label: string; disabled?: boolean }`
```tsx
<RadioGroup
options={[{ value: 'a', label: 'Alpha' }, { value: 'b', label: 'Beta' }]}
value={selected}
onChange={setSelected}
/>
```
#### `Switch`
Toggle switch for boolean values.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `checked` | `boolean` | — | Controlled state |
| `onChange` | `(checked: boolean) => void` | — | Change callback |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size preset |
| `disabled` | `boolean` | `false` | Disable the switch |
| `label` | `string` | — | Label text |
#### `Label`
Styled `<label>` element.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `required` | `boolean` | — | Show required indicator |
| `disabled` | `boolean` | — | Apply muted styling |
---
### Data Display
#### `DataTable`
Paginated table with sorting, search, and custom cell rendering.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `data` | `Row[]` | — | Array of row objects |
| `columns` | `Column<Row>[]` | — | Column definitions |
| `initialSortKey` | `string` | — | Initial sort column |
| `searchable` | `boolean` | `false` | Show search input |
| `pageSize` | `number` | `10` | Rows per page |
`Column<Row>`: `{ key: string; header: string; width?: number; sortable?: boolean; render?: (row) => ReactNode }`
```tsx
<DataTable
data={items}
columns={[
{ key: 'name', header: 'Name', sortable: true },
{ key: 'status', header: 'Status', render: (row) => <Badge>{row.status}</Badge> },
]}
/>
```
---
### Navigation
#### `Tabs`
Horizontal tab bar.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `tabs` | `TabItem[]` | — | Tab definitions |
| `activeTab` | `string` | — | Currently active tab id |
| `onChange` | `(id: string) => void` | — | Tab change callback |
`TabItem`: `{ id: string; label: string; icon?: ComponentType; disabled?: boolean }`
```tsx
<Tabs
tabs={[{ id: 'one', label: 'Tab 1' }, { id: 'two', label: 'Tab 2' }]}
activeTab={tab}
onChange={setTab}
/>
```
#### `ViewSwitcher`
Segmented button group for switching between views.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `currentView` | `T` | — | Active view value |
| `onChange` | `(view: T) => void` | — | View change callback |
| `options` | `ViewSwitcherOption<T>[]` | — | Available views |
`ViewSwitcherOption<T>`: `{ value: T; label: string; icon?: ComponentType }`
```tsx
<ViewSwitcher
currentView={view}
onChange={setView}
options={[{ value: 'list', label: 'List' }, { value: 'grid', label: 'Grid' }]}
/>
```
#### `Breadcrumbs`
Horizontal breadcrumb navigation trail.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `items` | `BreadcrumbItem[]` | — | Breadcrumb segments |
| `separator` | `ReactNode` | `'/'` | Custom separator |
`BreadcrumbItem`: `{ label: ReactNode; href?: string; onClick?: () => void }`
```tsx
<Breadcrumbs items={[
{ label: 'Home', href: '/' },
{ label: 'Settings', onClick: goSettings },
{ label: 'Profile' },
]} />
```
#### `DropdownMenu`
Popover menu triggered by a button.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `trigger` | `ReactNode` | — | Trigger element |
| `items` | `DropdownMenuItem[]` | — | Menu items |
`DropdownMenuItem`: `{ label: string; onClick?: () => void; icon?: ComponentType; danger?: boolean; disabled?: boolean; separator?: boolean }`
---
### Layout
#### `Stack`
Flexbox layout helper for stacking children with consistent spacing.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `direction` | `'row' \| 'column'` | `'column'` | Flex direction |
| `gap` | `number \| string` | `8` | Gap between children (numbers = px) |
| `align` | `'stretch' \| 'flex-start' \| 'center' \| 'flex-end' \| 'baseline'` | — | Cross-axis alignment |
| `justify` | `'flex-start' \| 'center' \| 'flex-end' \| 'space-between' \| 'space-around' \| 'space-evenly'` | — | Main-axis alignment |
```tsx
<Stack direction="row" gap={12} align="center">
<Text>Left</Text>
<Button>Right</Button>
</Stack>
```
#### `Drawer`
Slide-in panel from the right edge with focus trapping and overlay.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `open` | `boolean` | — | Whether the drawer is open |
| `onClose` | `() => void` | — | Close callback |
| `width` | `string` | `'fit-content'` | CSS width |
| `maxWidth` | `string` | `'60vw'` | Max width |
| `title` | `ReactNode` | — | Drawer title |
| `overlay` | `boolean` | `true` | Show backdrop overlay |
| `closeOnOverlayClick` | `boolean` | `true` | Close on overlay click |
| `closeOnEscape` | `boolean` | `true` | Close on Escape key |
#### `Dialog` / `ConfirmDialog`
Modal dialogs.
**Dialog** — base modal with title and children.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isOpen` | `boolean` | — | Open state |
| `onClose` | `() => void` | — | Close callback |
| `title` | `string` | — | Dialog title |
**ConfirmDialog** — confirm / cancel modal.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isOpen` | `boolean` | — | Open state |
| `title` | `string` | — | Title |
| `message` | `string` | — | Body text |
| `variant` | `'default' \| 'danger'` | `'default'` | Visual tone |
| `onConfirm` | `() => void` | — | Confirm callback |
| `onCancel` | `() => void` | — | Cancel callback |
```tsx
<ConfirmDialog
isOpen={open}
title="Delete Item"
message="Are you sure?"
variant="danger"
onConfirm={handleDelete}
onCancel={() => setOpen(false)}
/>
```
#### `Popover`
Positioned content popover anchored to a trigger element.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `trigger` | `ReactNode` | — | Trigger element |
| `open` | `boolean` | — | Controlled open state |
| `onOpenChange` | `(open: boolean) => void` | — | Open state callback |
| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'bottom'` | Popover placement |
---
### Feedback
#### `Spinner` / `LoadingIndicator`
**Spinner** — animated circular loading indicator.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `size` | `'button' \| 'xs' \| 'sm' \| 'md' \| 'lg'` | `'md'` | Diameter preset |
| `variant` | `'default' \| 'inverted'` | `'default'` | Use `'inverted'` on coloured backgrounds |
**LoadingIndicator** — spinner with optional text.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `size` | same as Spinner | `'md'` | Spinner size |
| `text` | `string` | — | Label next to spinner |
| `layout` | `'inline' \| 'stacked'` | `'inline'` | Text position |
```tsx
<Spinner size="sm" />
<LoadingIndicator text="Loading..." layout="stacked" />
```
#### `Tooltip`
Hover tooltip anchored to its children.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `content` | `ReactNode` | — | Tooltip content |
| `placement` | `'top' \| 'bottom' \| 'left' \| 'right'` | `'top'` | Position |
| `delay` | `number` | `200` | Show delay in ms |
```tsx
<Tooltip content="More info">
<Button variant="ghost">?</Button>
</Tooltip>
```
#### `Progress`
Horizontal progress bar.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `value` | `number` | — | Current value (0100) |
| `max` | `number` | `100` | Maximum value |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Bar height |
| `tone` | `'primary' \| 'success' \| 'warning' \| 'danger'` | `'primary'` | Bar colour |
```tsx
<Progress value={75} tone="success" />
```
#### `Skeleton` / `SkeletonText` / `SkeletonCircle` / `SkeletonRectangle`
Placeholder loading shapes.
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `width` | `string \| number` | `'100%'` | Width |
| `height` | `string \| number` | `'1em'` | Height |
| `variant` | `'text' \| 'circular' \| 'rectangular'` | `'text'` | Shape |
| `animation` | `'pulse' \| 'wave' \| 'none'` | `'pulse'` | Animation type |
| `lines` | `number` | `3` | Number of lines (text variant) |
```tsx
<SkeletonText lines={3} />
<SkeletonCircle width={40} height={40} />
```
---
## Hooks
### `useTheme()`
Returns `{ setMode, getMode }` for reading and setting the current theme mode (`'light' | 'dark'`).
```tsx
import { useTheme } from '@alga-psa/ui-kit';
const { setMode, getMode } = useTheme();
setMode('dark');
```
### `applyThemeVars(vars)`
Applies a `Record<string, string>` of CSS custom property values to the document root.
```tsx
import { applyThemeVars } from '@alga-psa/ui-kit';
applyThemeVars({ 'alga-primary': '#ff6600' });
```
---
## Extension Theme Bridge
For extensions running in iframes, use the built-in theme bridge to receive host theme variables:
```tsx
import { applyThemeVars } from '@alga-psa/ui-kit';
window.addEventListener('message', (ev) => {
const data = ev.data;
if (data?.alga === true && data?.version === '1' && data?.type === 'theme') {
applyThemeVars(data.payload || {});
}
});
// Signal readiness to the host
window.parent.postMessage({ alga: true, version: '1', type: 'ready' }, '*');
```