Skip to Content

UI Package

The UI package (@sustentus/ui) is a shared component library built with React, TypeScript, and Tailwind CSS, providing 45+ production-ready components.

Overview

Package Name: @sustentus/ui Path: packages/ui/ Build Tool: TSUP Version: 1.0.0

Purpose

Provides reusable UI components for all Sustentus applications, ensuring:

  • Consistency: Unified design language across apps
  • Reusability: Write once, use everywhere
  • Maintainability: Single source of truth for components
  • Accessibility: ARIA-compliant components
  • Type Safety: Full TypeScript support

Technology Stack

Core

  • React 19: UI library
  • TypeScript 5: Type safety
  • Radix UI: Accessible component primitives
  • Tailwind CSS v4: Utility-first styling

Build

  • TSUP: TypeScript bundler powered by esbuild
  • Tailwind CLI: CSS compilation
  • Concurrently: Run multiple build processes

UI Libraries

  • class-variance-authority: Variant-based component styling
  • tailwind-merge: Intelligent class merging
  • Lucide React: Icon library

Form Libraries

  • React Hook Form: Form state management
  • Zod: Schema validation
  • @hookform/resolvers: Integration layer

Additional

  • Sonner: Toast notifications
  • Recharts: Chart components
  • Embla Carousel: Carousel implementation
  • date-fns: Date utilities
  • React Day Picker: Date picker

Directory Structure

ui/ ├── src/ │ ├── components/ │ │ ├── accordion.tsx │ │ ├── alert.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ └── ... (45+ components) │ ├── lib/ │ │ └── utils.ts # Utility functions │ ├── globals.css # Global styles & Tailwind │ └── index.ts # Package entry point ├── dist/ │ ├── index.js # Bundled JavaScript │ ├── index.d.ts # TypeScript declarations │ ├── index.js.map # Source map │ └── globals.css # Compiled CSS ├── components.json # shadcn/ui config ├── package.json ├── tsconfig.json └── tsup.config.ts

Components

The package includes 45+ components organized by category:

Form Components

  • Button: Clickable button with variants
  • Input: Text input field
  • Textarea: Multi-line text input
  • Checkbox: Boolean selection
  • Radio Group: Single selection from options
  • Select: Dropdown selection
  • Switch: Toggle switch
  • Label: Form field label
  • Form: Form wrapper with validation

Layout Components

  • Card: Content container with header/footer
  • Separator: Visual divider
  • Tabs: Tabbed interface
  • Accordion: Collapsible sections
  • Collapsible: Expandable content
  • Resizable: Resizable panels
  • Aspect Ratio: Maintain aspect ratio
  • Scroll Area: Custom scrollbars

Overlay Components

  • Dialog: Modal dialog
  • Drawer: Slide-out drawer (mobile)
  • Sheet: Slide-out panel
  • Popover: Floating content
  • Tooltip: Hover hint
  • Hover Card: Rich hover content
  • Dropdown Menu: Contextual menu
  • Context Menu: Right-click menu
  • Alert Dialog: Confirmation dialog

Feedback Components

  • Alert: Inline message
  • Toast: Notification (via Sonner)
  • Progress: Progress indicator
  • Skeleton: Loading placeholder
  • Navigation Menu: Main navigation
  • Menubar: Menu bar
  • Breadcrumb: Breadcrumb trail
  • Pagination: Page navigation

Data Display

  • Table: Data table
  • Badge: Status indicator
  • Avatar: User avatar
  • Calendar: Date picker calendar
  • Carousel: Image/content carousel
  • Chart: Data visualization

Input Components

  • Input OTP: One-time password input
  • Slider: Range slider
  • Command: Command palette
  • Toggle: Toggle button
  • Toggle Group: Toggle button group

Utilities

  • use-mobile: Mobile detection hook

AI Components

Compound components for building AI chat interfaces — used by the BRD Agent UI:

  • PromptInput: Controlled text input for submitting messages to the AI agent
  • Attachments: File attachment management within the chat interface
  • Confirmation: Approval prompt for AI-generated outputs (e.g. BRD approval)
  • Image: Image rendering within chat messages
  • Shimmer: Skeleton loading state for streaming AI responses
  • Message: Chat message bubble with role-aware styling (user vs. assistant)

Usage

Installation

In an application:

pnpm add @sustentus/ui@workspace:*

Importing Components

import { Button, Card, Input } from "@sustentus/ui"; const MyComponent = () => ( <Card> <Input placeholder="Enter text" /> <Button>Submit</Button> </Card> );

Importing Styles

Import global CSS in your app layout:

import "@sustentus/ui/globals.css";

Using the cn Utility

import { cn } from "@sustentus/ui"; const className = cn( "base-class", condition && "conditional-class", anotherClass, );

Component Examples

Button

import { Button } from "@sustentus/ui"; // Default button <Button>Click me</Button> // Variant buttons <Button variant="destructive">Delete</Button> <Button variant="outline">Cancel</Button> <Button variant="ghost">Ghost</Button> <Button variant="link">Link</Button> // Sizes <Button size="sm">Small</Button> <Button size="lg">Large</Button> <Button size="icon"><Icon /></Button>

Form

import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage, Input, Button } from "@sustentus/ui"; const schema = z.object({ email: z.string().email(), }); const MyForm = () => { const form = useForm({ resolver: zodResolver(schema), }); return ( <Form {...form}> <FormField control={form.control} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <FormControl> <Input {...field} /> </FormControl> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </Form> ); };

Development

Running in Watch Mode

# From root pnpm ui:dev # From ui directory cd packages/ui pnpm dev

This runs TSUP in watch mode (rebuilds on code changes) and Tailwind in watch mode (recompiles CSS).

Building

# From root pnpm ui:build # From ui directory cd packages/ui pnpm build

The build bundles TypeScript to JavaScript, generates declarations, and compiles CSS. pnpm clean removes the dist/ folder.

Configuration

TSUP Config

tsup.config.ts:

import { defineConfig } from "tsup"; export default defineConfig({ entry: ["src/index.ts"], format: ["esm"], // ESM output dts: true, // Generate .d.ts clean: true, // Clean dist/ sourcemap: true, // Source maps minify: false, // No minification target: "es2022", // Modern JS outDir: "dist", external: ["react", "react-dom"], banner: { js: '"use client";', // Mark as client component }, });

Package Exports

package.json:

{ "exports": { ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts" }, "./globals.css": "./dist/globals.css" } }

React and React DOM are peer dependencies (^19); all other Radix UI, validation, and utility libraries are bundled.

Styling

Components use Tailwind utility classes and class-variance-authority for variants:

import { cva } from "class-variance-authority"; const buttonVariants = cva("inline-flex items-center justify-center", { variants: { variant: { default: "bg-primary text-primary-foreground", destructive: "bg-destructive text-destructive-foreground", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8", }, }, defaultVariants: { variant: "default", size: "default", }, });

The cn utility merges classes intelligently:

import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));

Accessibility

All components built on Radix UI primitives are keyboard navigable, screen-reader friendly (proper ARIA attributes), focus-managed, and WCAG compliant.

Adding New Components

Using shadcn/ui CLI

If the component is in shadcn/ui:

cd packages/ui npx shadcn@latest add [component-name]

Manual Addition

  1. Create the component file in src/components/
  2. Export it from src/index.ts
  3. Build the package
  4. Add a story to Storybook
// src/components/new-component.tsx import { type ComponentProps } from "react"; import { cn } from "../lib/utils.js"; export const NewComponent = ({ className, ...props }: ComponentProps<"div">) => ( <div className={cn("base-styles", className)} {...props} /> );
// src/index.ts export * from "./components/new-component.js";

Learn More

Last updated on