From ffc94c23fa60a87fb098ad9157a8f4b647fd963c Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Fri, 8 May 2026 21:16:48 -0700 Subject: [PATCH] feat(ui): add operational workflow primitives --- packages/ui/package.json | 48 +++ packages/ui/src/components/ActionMenu.tsx | 50 +++ packages/ui/src/components/AlertBanner.tsx | 55 ++++ packages/ui/src/components/Drawer.tsx | 56 ++++ packages/ui/src/components/EntityCard.tsx | 63 ++++ packages/ui/src/components/FieldGrid.tsx | 16 + packages/ui/src/components/FilterBar.tsx | 72 +++++ packages/ui/src/components/FormSection.tsx | 44 +++ packages/ui/src/components/MetricCard.tsx | 48 +++ .../components/OperationalPreview.stories.tsx | 305 ++++++++++++++++++ packages/ui/src/components/PageHeader.tsx | 57 ++++ packages/ui/src/components/Section.tsx | 48 +++ packages/ui/src/components/Skeleton.tsx | 32 ++ packages/ui/src/components/Toolbar.tsx | 25 ++ packages/ui/src/index.ts | 16 + 15 files changed, 935 insertions(+) create mode 100644 packages/ui/src/components/ActionMenu.tsx create mode 100644 packages/ui/src/components/AlertBanner.tsx create mode 100644 packages/ui/src/components/Drawer.tsx create mode 100644 packages/ui/src/components/EntityCard.tsx create mode 100644 packages/ui/src/components/FieldGrid.tsx create mode 100644 packages/ui/src/components/FilterBar.tsx create mode 100644 packages/ui/src/components/FormSection.tsx create mode 100644 packages/ui/src/components/MetricCard.tsx create mode 100644 packages/ui/src/components/OperationalPreview.stories.tsx create mode 100644 packages/ui/src/components/PageHeader.tsx create mode 100644 packages/ui/src/components/Section.tsx create mode 100644 packages/ui/src/components/Skeleton.tsx create mode 100644 packages/ui/src/components/Toolbar.tsx diff --git a/packages/ui/package.json b/packages/ui/package.json index 8c085da7..3013b93c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,6 +16,54 @@ "types": "./dist/components/Button.d.ts", "import": "./dist/components/Button.js" }, + "./page-header": { + "types": "./dist/components/PageHeader.d.ts", + "import": "./dist/components/PageHeader.js" + }, + "./section": { + "types": "./dist/components/Section.d.ts", + "import": "./dist/components/Section.js" + }, + "./toolbar": { + "types": "./dist/components/Toolbar.d.ts", + "import": "./dist/components/Toolbar.js" + }, + "./filter-bar": { + "types": "./dist/components/FilterBar.d.ts", + "import": "./dist/components/FilterBar.js" + }, + "./form-section": { + "types": "./dist/components/FormSection.d.ts", + "import": "./dist/components/FormSection.js" + }, + "./field-grid": { + "types": "./dist/components/FieldGrid.d.ts", + "import": "./dist/components/FieldGrid.js" + }, + "./alert-banner": { + "types": "./dist/components/AlertBanner.d.ts", + "import": "./dist/components/AlertBanner.js" + }, + "./skeleton": { + "types": "./dist/components/Skeleton.d.ts", + "import": "./dist/components/Skeleton.js" + }, + "./entity-card": { + "types": "./dist/components/EntityCard.d.ts", + "import": "./dist/components/EntityCard.js" + }, + "./metric-card": { + "types": "./dist/components/MetricCard.d.ts", + "import": "./dist/components/MetricCard.js" + }, + "./action-menu": { + "types": "./dist/components/ActionMenu.d.ts", + "import": "./dist/components/ActionMenu.js" + }, + "./drawer": { + "types": "./dist/components/Drawer.d.ts", + "import": "./dist/components/Drawer.js" + }, "./app-shell": { "types": "./dist/components/AppShell.d.ts", "import": "./dist/components/AppShell.js" diff --git a/packages/ui/src/components/ActionMenu.tsx b/packages/ui/src/components/ActionMenu.tsx new file mode 100644 index 00000000..bc5ad415 --- /dev/null +++ b/packages/ui/src/components/ActionMenu.tsx @@ -0,0 +1,50 @@ +'use client'; + +import * as React from 'react'; +import { MoreHorizontal } from 'lucide-react'; +import { Button } from './Button.js'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from './DropdownMenu.js'; + +export interface ActionMenuItem { + id: string; + label: React.ReactNode; + icon?: React.ReactNode; + disabled?: boolean; + destructive?: boolean; + onSelect: () => void; +} + +export interface ActionMenuProps { + label?: string; + items: ActionMenuItem[]; +} + +export function ActionMenu({ label = 'Open actions', items }: ActionMenuProps) { + return ( + + + + + + {items.map(item => ( + + {item.icon} + {item.label} + + ))} + + + ); +} diff --git a/packages/ui/src/components/AlertBanner.tsx b/packages/ui/src/components/AlertBanner.tsx new file mode 100644 index 00000000..77f37d6d --- /dev/null +++ b/packages/ui/src/components/AlertBanner.tsx @@ -0,0 +1,55 @@ +import * as React from 'react'; +import { AlertCircle, CheckCircle2, Info, TriangleAlert } from 'lucide-react'; +import { clsx } from 'clsx'; + +export type AlertBannerTone = 'info' | 'success' | 'warning' | 'error' | 'neutral'; + +export interface AlertBannerProps extends Omit, 'title'> { + tone?: AlertBannerTone; + title?: React.ReactNode; + icon?: React.ReactNode; +} + +const toneClass: Record = { + info: 'border-[var(--bl-info-border,var(--bl-border))] bg-[var(--bl-info-muted,var(--bl-surface-muted))] text-[var(--bl-info,var(--bl-accent))]', + success: + 'border-[var(--bl-success-border,var(--bl-border))] bg-[var(--bl-success-muted,var(--bl-surface-muted))] text-[var(--bl-success)]', + warning: + 'border-[var(--bl-warning-border,var(--bl-border))] bg-[var(--bl-warning-muted,var(--bl-surface-muted))] text-[var(--bl-warning)]', + error: + 'border-[var(--bl-danger-border,var(--bl-border))] bg-[var(--bl-danger-muted,var(--bl-surface-muted))] text-[var(--bl-danger)]', + neutral: 'border-[var(--bl-border)] bg-[var(--bl-surface-muted)] text-[var(--bl-text-secondary)]', +}; + +const iconByTone: Record = { + info: