From 23e140009f6ed49e5af5cdbf904e62a811f5def1 Mon Sep 17 00:00:00 2001 From: Saravana Achu Mac Date: Wed, 6 May 2026 11:10:08 -0700 Subject: [PATCH] feat(ui): add operational core primitives --- packages/ui/src/components/DiffCard.tsx | 31 ++++++++ packages/ui/src/components/IconButton.tsx | 26 +++++++ packages/ui/src/components/ListItemButton.tsx | 28 +++++++ packages/ui/src/components/Panel.tsx | 77 +++++++++++++++++++ packages/ui/src/components/StatusBadge.tsx | 59 ++++++++++++++ packages/ui/src/components/Timeline.tsx | 61 +++++++++++++++ packages/ui/src/index.ts | 22 ++++++ 7 files changed, 304 insertions(+) create mode 100644 packages/ui/src/components/DiffCard.tsx create mode 100644 packages/ui/src/components/IconButton.tsx create mode 100644 packages/ui/src/components/ListItemButton.tsx create mode 100644 packages/ui/src/components/Panel.tsx create mode 100644 packages/ui/src/components/StatusBadge.tsx create mode 100644 packages/ui/src/components/Timeline.tsx diff --git a/packages/ui/src/components/DiffCard.tsx b/packages/ui/src/components/DiffCard.tsx new file mode 100644 index 00000000..fb619047 --- /dev/null +++ b/packages/ui/src/components/DiffCard.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import { clsx } from 'clsx'; + +export interface DiffCardProps extends React.HTMLAttributes { + before: React.ReactNode; + after: React.ReactNode; + beforeLabel?: string; + afterLabel?: string; +} + +export function DiffCard({ + before, + after, + beforeLabel = 'Before', + afterLabel = 'After', + className, + ...props +}: DiffCardProps) { + return ( +
+
+ {beforeLabel} +
{before}
+
+
+ {afterLabel} +
{after}
+
+
+ ); +} diff --git a/packages/ui/src/components/IconButton.tsx b/packages/ui/src/components/IconButton.tsx new file mode 100644 index 00000000..08ad6312 --- /dev/null +++ b/packages/ui/src/components/IconButton.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { clsx } from 'clsx'; +import { Button, type ButtonProps } from './Button.js'; + +export interface IconButtonProps extends Omit { + icon: React.ReactNode; + label: string; +} + +export const IconButton = React.forwardRef( + ({ icon, label, className, size = 'sm', variant = 'ghost', ...props }, ref) => ( + + ) +); + +IconButton.displayName = 'IconButton'; diff --git a/packages/ui/src/components/ListItemButton.tsx b/packages/ui/src/components/ListItemButton.tsx new file mode 100644 index 00000000..936dcbe5 --- /dev/null +++ b/packages/ui/src/components/ListItemButton.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; +import { clsx } from 'clsx'; + +export interface ListItemButtonProps extends React.ButtonHTMLAttributes { + selected?: boolean; +} + +export const ListItemButton = React.forwardRef( + ({ selected, className, children, ...props }, ref) => ( + + ) +); + +ListItemButton.displayName = 'ListItemButton'; diff --git a/packages/ui/src/components/Panel.tsx b/packages/ui/src/components/Panel.tsx new file mode 100644 index 00000000..351529f3 --- /dev/null +++ b/packages/ui/src/components/Panel.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import { clsx } from 'clsx'; + +export interface PanelProps extends React.HTMLAttributes { + as?: 'section' | 'aside' | 'article' | 'div'; + density?: 'compact' | 'normal' | 'spacious'; +} + +const panelPadding: Record, string> = { + compact: 'p-3', + normal: 'p-5', + spacious: 'p-6', +}; + +export function Panel({ + as: Comp = 'section', + density = 'normal', + className, + children, + ...props +}: PanelProps) { + return ( + + {children} + + ); +} + +export type PanelHeaderProps = React.HTMLAttributes; + +export function PanelHeader({ className, children, ...props }: PanelHeaderProps) { + return ( +
+ {children} +
+ ); +} + +export type PanelBodyProps = React.HTMLAttributes; + +export function PanelBody({ className, children, ...props }: PanelBodyProps) { + return ( +
+ {children} +
+ ); +} + +export type PanelTitleProps = React.ComponentPropsWithoutRef<'h2'>; + +export function PanelTitle({ className, children, ...props }: PanelTitleProps) { + return ( +

+ {children} +

+ ); +} + +export type PanelDescriptionProps = React.ComponentPropsWithoutRef<'p'>; + +export function PanelDescription({ className, children, ...props }: PanelDescriptionProps) { + return ( +

+ {children} +

+ ); +} diff --git a/packages/ui/src/components/StatusBadge.tsx b/packages/ui/src/components/StatusBadge.tsx new file mode 100644 index 00000000..cab7a942 --- /dev/null +++ b/packages/ui/src/components/StatusBadge.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { clsx } from 'clsx'; + +export type StatusTone = 'success' | 'warning' | 'danger' | 'info' | 'neutral' | 'accent'; + +export interface StatusBadgeProps extends React.HTMLAttributes { + tone?: StatusTone; + dot?: boolean; +} + +const toneClasses: Record = { + success: + 'bg-[var(--bl-success-muted,var(--bl-surface-muted))] text-[var(--bl-success)] border-[var(--bl-success)]', + warning: + 'bg-[var(--bl-warning-muted,var(--bl-surface-muted))] text-[var(--bl-warning)] border-[var(--bl-warning)]', + danger: + 'bg-[var(--bl-danger-muted,var(--bl-surface-muted))] text-[var(--bl-danger)] border-[var(--bl-danger)]', + info: 'bg-[var(--bl-info-muted,var(--bl-surface-muted))] text-[var(--bl-info,var(--bl-accent))] border-[var(--bl-info,var(--bl-accent))]', + neutral: 'bg-[var(--bl-surface-muted)] text-[var(--bl-text-secondary)] border-[var(--bl-border)]', + accent: + 'bg-[var(--bl-accent-muted,var(--bl-surface-muted))] text-[var(--bl-text-primary)] border-[var(--bl-accent)]', +}; + +export function StatusDot({ + tone = 'neutral', + className, +}: { + tone?: StatusTone; + className?: string; +}) { + return ( +