Package Exports
- equal-ds-ui
- equal-ds-ui/animations.css
- equal-ds-ui/tailwind-preset
- equal-ds-ui/theme.css
- equal-ds-ui/tokens.css
Readme
🎨 Equal DS UI
Production-ready React components for the Equal Design System. Built with TypeScript, featuring comprehensive design tokens, and optimized for modern React applications.
🚀 Quick Start
npm install equal-ds-ui
npm install @radix-ui/react-collapsible @radix-ui/react-tooltip @radix-ui/react-visually-hidden lucide-react tailwindcss
Basic Usage
import { FinProSidebar, SidebarProvider } from 'equal-ds-ui'
import 'equal-ds-ui/tokens.css'
import 'equal-ds-ui/animations.css'
function App() {
return (
<SidebarProvider>
<FinProSidebar headerText="My App" />
</SidebarProvider>
)
}
📦 What's Included
🎯 Core Components
Component | Description | Key Features |
---|---|---|
FinProSidebar |
Advanced sidebar with filtering | Multi-select dropdowns, drag & drop, animations |
Sidebar |
Basic sidebar container | Responsive, accessible, customizable |
Dropdown |
Versatile dropdown menu | Single/multi-select, search, custom triggers |
DatePicker |
Date selection component | Calendar, time picker, range selection |
🎨 Design System
Feature | Description |
---|---|
Design Tokens | Comprehensive CSS variables for colors, typography, spacing |
Tailwind Preset | Pre-configured Tailwind configuration |
Animations | Smooth transitions and micro-interactions |
Accessibility | Full ARIA support, keyboard navigation, screen readers |
🔧 Utilities
Utility | Purpose | Example |
---|---|---|
cn |
Class name utility | cn('base-class', conditional && 'active') |
useHoverAnimation |
Hover state management | useHoverAnimation({ duration: 200 }) |
ChevronIcon |
Animated chevron component | <ChevronIcon isOpen={isOpen} /> |
✨ What's New in v1.3.0
- 📅 Complete Date Picker Suite: New date picker, time picker, and date range picker components
- 🎯 Accessible Date Selection: Full keyboard navigation and screen reader support
- ⏰ Time Picker Integration: Combined date and time selection capabilities
- 📊 Date Range Selection: Visual date range picker with start/end date selection
- 🎨 Design System Integration: Seamless integration with Equal DS UI design tokens
- 📱 Mobile Responsive: Touch-friendly date picker interfaces for all devices
📦 Installation
npm install equal-ds-ui
# Required peer dependencies
npm install @radix-ui/react-collapsible @radix-ui/react-tooltip @radix-ui/react-visually-hidden lucide-react tailwindcss
# Development dependencies (for custom setup)
npm install -D tailwindcss postcss autoprefixer
🎯 Selective Format Installation
Choose your preferred module format for optimal bundle sizes and compatibility. This package supports both ESM and CommonJS with automatic format detection.
Quick Format Selection
# Install the package
npm install equal-ds-ui
# Choose your format (choose one):
npx equal-ds-format esm # ESM format (~34KB) - Modern bundlers
npx equal-ds-format cjs # CommonJS format (~36KB) - Legacy bundlers
npx equal-ds-format auto # Auto-detect based on your project
📊 Format Comparison
Format | Bundle Size | Best For | Command |
---|---|---|---|
ESM Only | ~34KB | Vite, Next.js, esbuild | npx equal-ds-format esm |
CommonJS Only | ~36KB | Webpack, CRA, legacy | npx equal-ds-format cjs |
Auto-Detect | Optimized | Any project | npx equal-ds-format auto |
Both Formats | ~70KB | Maximum compatibility | Default |
🔧 For Package Publishers
# Build format-specific versions
npm run build:esm # ESM-only build
npm run build:cjs # CommonJS-only build
npm run build # Both formats (default)
# Publish selective versions
npm run prepublishOnly:esm # Publish ESM-only
npm run prepublishOnly:cjs # Publish CommonJS-only
npm run prepublishOnly # Publish both formats
💡 Pro Tip: The CLI automatically detects your project's bundler (Vite, Webpack, Next.js, etc.) and recommends the optimal format!
🚨 Migration Notice (v1.2.0)
Dropdown components now include automatic rotating chevrons by default. If you're upgrading from v1.1.1 or earlier:
- ✅ Simple migration: Just remove manual chevron state management
- 📖 Full guide: See Dropdown Migration Guide
- 🎯 Breaking change: Default behavior now includes rotating chevrons
// Before (v1.1.1)
<Dropdown onOpenChange={setIsOpen}>
<DropdownTrigger>
Select Option
{isOpen ? <ChevronUp /> : <ChevronDown />}
</DropdownTrigger>
</Dropdown>
// After (v1.2.0) - Automatic!
<Dropdown>
<DropdownTrigger>
Select Option
</DropdownTrigger>
</Dropdown>
⚙️ Quick Setup
Get started with Equal DS UI in just 3 simple steps:
1. Initialize Tailwind CSS
npx tailwindcss init -p # Creates tailwind.config.js and postcss.config.js
2. Configure with our Design System
// tailwind.config.js
import preset from 'equal-ds-ui/tailwind-preset'
export default {
content: [
'./index.html',
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/equal-ds-ui/dist/**/*.{js,jsx,ts,tsx}', // Important!
],
presets: [preset], // Our complete design system
}
3. Import Design System Styles
/* src/index.css */
/* Import Equal DS UI design system FIRST */
@import 'equal-ds-ui/tokens.css';
@import 'equal-ds-ui/animations.css';
/* Then Tailwind directives */
@tailwind base;
@tailwind components;
@tailwind utilities;
🚀 Quick Start
Here's a complete example to get you started:
import {
SidebarProvider,
Sidebar,
SidebarHeader,
SidebarContent,
SidebarGroup,
SidebarGroupTrigger,
SidebarGroupLabel,
SidebarGroupContent,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarTrigger,
// New in v1.2.0: Automatic rotating chevrons!
Dropdown,
DropdownTrigger,
DropdownContent,
DropdownItem,
// New in v1.3.0: Complete date picker suite!
DatePicker,
DatePickerTrigger,
DatePickerContent,
TimePickerContent,
DateRangePickerContent,
} from 'equal-ds-ui'
export default function App() {
return (
<SidebarProvider>
<div className="flex h-screen bg-background-secondary">
{/* Sidebar */}
<Sidebar>
<SidebarHeader className="border-b border-border-default">
<div className="px-4 py-3">
<h1 className="font-semibold text-text-primary">My App</h1>
</div>
</SidebarHeader>
<SidebarContent>
<SidebarGroup defaultOpen>
<SidebarGroupTrigger>
<SidebarGroupLabel>Menu</SidebarGroupLabel>
</SidebarGroupTrigger>
<SidebarGroupContent>
<SidebarMenu>
<SidebarMenuItem>
<SidebarMenuButton itemId="home">Home</SidebarMenuButton>
</SidebarMenuItem>
<SidebarMenuItem>
<SidebarMenuButton itemId="settings">Settings</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
</Sidebar>
{/* Main Content */}
<main className="flex-1 p-6">
<SidebarTrigger className="mb-4">☰ Toggle Sidebar</SidebarTrigger>
<h1 className="text-2xl font-bold text-text-primary">Welcome to Equal DS UI!</h1>
<p className="text-text-secondary mt-2">Your sidebar is ready to use.</p>
</main>
</div>
</SidebarProvider>
)
}
Dropdown with Automatic Rotating Chevrons ✨
import { useState } from 'react'
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, DropdownItemMultiselect } from 'equal-ds-ui'
export default function DropdownExamples() {
const [selectedValue, setSelectedValue] = useState('')
const [selectedItems, setSelectedItems] = useState<string[]>([])
return (
<div className="p-6 space-y-6">
{/* Basic Dropdown */}
<div>
<h3 className="text-lg font-semibold mb-2">Basic Dropdown</h3>
<Dropdown>
<DropdownTrigger className="w-64">
{selectedValue || 'Select an option'}
</DropdownTrigger>
<DropdownContent>
<DropdownItem onClick={() => setSelectedValue('Option 1')}>
Option 1
</DropdownItem>
<DropdownItem onClick={() => setSelectedValue('Option 2')}>
Option 2
</DropdownItem>
<DropdownItem onClick={() => setSelectedValue('Option 3')}>
Option 3
</DropdownItem>
</DropdownContent>
</Dropdown>
</div>
{/* Multi-select Dropdown */}
<div>
<h3 className="text-lg font-semibold mb-2">Multi-select Dropdown</h3>
<Dropdown>
<DropdownTrigger className="w-64">
{selectedItems.length ? `${selectedItems.length} selected` : 'Select items'}
</DropdownTrigger>
<DropdownContent>
<DropdownItemMultiselect
checked={selectedItems.includes('item1')}
onCheckedChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, 'item1'])
} else {
setSelectedItems(selectedItems.filter(item => item !== 'item1'))
}
}}
>
Item 1
</DropdownItemMultiselect>
<DropdownItemMultiselect
checked={selectedItems.includes('item2')}
onCheckedChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, 'item2'])
} else {
setSelectedItems(selectedItems.filter(item => item !== 'item2'))
}
}}
>
Item 2
</DropdownItemMultiselect>
<DropdownSeparator />
<DropdownItemMultiselect
checked={selectedItems.includes('item3')}
onCheckedChange={(checked) => {
if (checked) {
setSelectedItems([...selectedItems, 'item3'])
} else {
setSelectedItems(selectedItems.filter(item => item !== 'item3'))
}
}}
>
Item 3
</DropdownItemMultiselect>
</DropdownContent>
</Dropdown>
</div>
{/* Custom Styled Dropdown */}
<div>
<h3 className="text-lg font-semibold mb-2">Custom Chevron Icons</h3>
<Dropdown>
<DropdownTrigger
className="w-64 bg-primary-50 border-primary-200"
chevronIcons={{
open: <span className="text-primary-600">▲</span>,
closed: <span className="text-primary-600">▼</span>
}}
>
Custom styled dropdown
</DropdownTrigger>
<DropdownContent>
<DropdownItem>Custom Option 1</DropdownItem>
<DropdownItem>Custom Option 2</DropdownItem>
<DropdownItem>Custom Option 3</DropdownItem>
</DropdownContent>
</Dropdown>
</div>
</div>
)
}
💡 Ready to go! This example uses our design tokens for consistent styling. Make sure you've completed the setup steps above.
📅 Date Picker Components
Equal DS UI now includes a complete suite of date picker components with full accessibility support and seamless design system integration.
Basic Date Picker
import { useState } from 'react'
import { DatePicker, DatePickerTrigger, DatePickerContent } from 'equal-ds-ui'
export default function DatePickerExample() {
const [date, setDate] = useState<Date | undefined>(new Date())
return (
<div className="p-6">
<h2 className="text-lg font-semibold mb-4">Select a Date</h2>
<DatePicker value={date} onChange={setDate}>
<DatePickerTrigger className="w-64">
{date ? date.toLocaleDateString() : 'Pick a date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
</div>
)
}
Time Picker
import { useState } from 'react'
import { DatePicker, DatePickerTrigger, TimePickerContent } from 'equal-ds-ui'
export default function TimePickerExample() {
const [date, setDate] = useState<Date | undefined>(new Date())
return (
<div className="p-6">
<h2 className="text-lg font-semibold mb-4">Select Time</h2>
<DatePicker value={date} onChange={setDate}>
<DatePickerTrigger className="w-64">
{date ? date.toLocaleTimeString() : 'Pick a time'}
</DatePickerTrigger>
<TimePickerContent />
</DatePicker>
</div>
)
}
Date Range Picker
import { useState } from 'react'
import { DatePicker, DatePickerTrigger, DateRangePickerContent } from 'equal-ds-ui'
export default function DateRangeExample() {
const [dateRange, setDateRange] = useState<{start?: Date, end?: Date}>({})
return (
<div className="p-6">
<h2 className="text-lg font-semibold mb-4">Select Date Range</h2>
<DatePicker value={dateRange} onChange={setDateRange}>
<DatePickerTrigger className="w-80">
{dateRange.start && dateRange.end
? `${dateRange.start.toLocaleDateString()} - ${dateRange.end.toLocaleDateString()}`
: 'Pick date range'
}
</DatePickerTrigger>
<DateRangePickerContent />
</DatePicker>
</div>
)
}
Advanced Date Picker with Custom Formatting
import { useState } from 'react'
import { DatePicker, DatePickerTrigger, DatePickerContent } from 'equal-ds-ui'
export default function AdvancedDatePicker() {
const [date, setDate] = useState<Date | undefined>()
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
return (
<div className="p-6">
<h2 className="text-lg font-semibold mb-4">Advanced Date Picker</h2>
<DatePicker
value={date}
onChange={setDate}
minDate={new Date()} // Disable past dates
maxDate={new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)} // 1 year from now
>
<DatePickerTrigger className="w-96">
{date ? formatDate(date) : 'Choose a future date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
</div>
)
}
📋 Date Picker Features
Feature | Description |
---|---|
Accessibility | Full ARIA support, keyboard navigation, screen reader compatible |
Internationalization | Locale-aware date formatting and parsing |
Responsive Design | Mobile-friendly interfaces with touch support |
Customizable Styling | Uses design tokens for consistent theming |
Flexible Date Formats | Support for various date display formats |
Validation | Min/max date constraints and custom validation |
Time Selection | Combined date and time picker functionality |
Range Selection | Visual date range picker with start/end dates |
Animation | Smooth 300ms ease-out transitions |
🎨 Date Picker Props
DatePicker
interface DatePickerProps {
value?: Date | {start?: Date, end?: Date}
onChange?: (date: Date | {start?: Date, end?: Date}) => void
minDate?: Date
maxDate?: Date
disabled?: boolean
placeholder?: string
className?: string
children: React.ReactNode
}
DatePickerTrigger
interface DatePickerTriggerProps {
className?: string
disabled?: boolean
children: React.ReactNode
}
DatePickerContent
interface DatePickerContentProps {
className?: string
showTimeSelect?: boolean
timeIntervals?: number
dateFormat?: string
}
🧪 Testing & Development
Quick Test with Vite
# Create and setup a test project
mkdir test-equal-ds && cd test-equal-ds
npm create vite@latest . -- --template react
npm install equal-ds-ui @radix-ui/react-collapsible @radix-ui/react-tooltip @radix-ui/react-visually-hidden lucide-react
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Essential Configuration
// tailwind.config.js
import preset from 'equal-ds-ui/tailwind-preset'
export default {
content: [
'./index.html',
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/equal-ds-ui/dist/**/*.{js,jsx,ts,tsx}', // Required!
],
presets: [preset],
}
/* src/index.css */
@import 'equal-ds-ui/tokens.css';
@import 'equal-ds-ui/animations.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
⚠️ Common Issues
Problem | Solution |
---|---|
Unstyled sidebar | Add node_modules/equal-ds-ui/dist/**/* to Tailwind content |
CSS variables not working | Import design system CSS before Tailwind |
Module resolution errors | Check your bundler configuration |
Format compatibility | Run npx equal-ds-format auto after installation |
🎨 Custom Design Tokens
Extend Equal DS UI with your own design tokens using our design-tokens-sync
tool.
Quick Setup
# Install the sync tool
npm install -D design-tokens-sync
# Create your tokens file
echo '{
"color": {
"primary": {"500": "#your-color"},
"text": {"primary": "#your-text"}
}
}' > design-tokens.json
# Generate CSS and Tailwind preset
npx design-tokens-sync sync
Integration
// tailwind.config.js
import equalDSPreset from 'equal-ds-ui/tailwind-preset'
import customPreset from './tokens.tailwind.preset.js'
export default {
presets: [equalDSPreset, customPreset], // Custom overrides Equal DS
content: ['./src/**/*.{js,jsx,ts,tsx}']
}
/* src/index.css */
@import './tokens.css'; /* Your tokens first */
@import 'equal-ds-ui/tokens.css'; /* Equal DS base */
@import 'equal-ds-ui/animations.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
🎯 Token Priority
- Your custom tokens (highest)
- Equal DS UI tokens (medium)
- Tailwind defaults (lowest)
Your tokens override ours, giving you full customization control!
🔀 Drag & Drop Reordering
Equal DS UI supports intuitive drag-and-drop for both menu items and groups.
Menu Items Reordering
const [itemOrder, setItemOrder] = useState(['home', 'settings', 'profile'])
<SidebarMenu reorderable onReorder={setItemOrder}>
{itemOrder.map(id => (
<SidebarMenuItem key={id} draggable dragId={id}>
<SidebarMenuButton itemId={id}>
{menuItems[id].label}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
Group Reordering
const [groupOrder, setGroupOrder] = useState(['main', 'admin', 'tools'])
<SidebarContent reorderableGroups onGroupReorder={setGroupOrder}>
{groupOrder.map(id => (
<SidebarGroup key={id} groupId={id}>
{/* Group content */}
</SidebarGroup>
))}
</SidebarContent>
✨ Features
- Handle-only dragging - Prevents accidental moves
- Smooth animations - Visual feedback during reordering
- Auto-collapse - Groups collapse during drag for clarity
- Container drops - Drop anywhere in the column
- Theme integration - Uses your design tokens for indicators
🚀 What's New in v1.0.7
- 🎯 Selective Format Installation: Choose ESM or CommonJS for ~50% smaller bundles
- ⚡ Smart CLI Tools:
npx equal-ds-format
for easy format selection - 🔍 Auto-Detection: Automatically detects your project's bundler preferences
- 🎨 Enhanced Design System: Improved token system and customization options
🚨 Migration Guide (v1.0.6 → v1.0.7)
✨ New Features (Non-Breaking)
- Format Selection: Choose ESM or CommonJS during installation
- CLI Tools: New
equal-ds-format
command for format optimization - Auto-Detection: Smart bundler detection for optimal format selection
Migration Steps
# Update to latest version
npm install equal-ds-ui@latest
# Optional: Optimize format for your project
npx equal-ds-format auto
Breaking Changes from v0.2.2 → v1.0.4
If upgrading from older versions, update your color classes:
// OLD → NEW
'text-foreground' → 'text-text-primary'
'bg-background' → 'bg-background-secondary'
'border-border' → 'border-border-default'
'focus:ring-ring' → 'focus:ring-primary-400'
📚 Component API Reference
🎯 FinProSidebar Component
The most advanced sidebar component with built-in filtering, drag & drop, and multi-select functionality.
Basic Usage
import { FinProSidebar, SidebarProvider } from 'equal-ds-ui'
function App() {
return (
<SidebarProvider>
<FinProSidebar
headerText="My Application"
defaultSelected="dashboard"
defaultExpanded={true}
/>
</SidebarProvider>
)
}
Props
interface FinProSidebarProps {
// Basic configuration
headerText?: string // Header text (default: "/*workasaur 🦖")
defaultSelected?: string // Initially selected menu item
defaultExpanded?: boolean // Whether filters start expanded
// Custom configuration (optional)
customItems?: Record<string, any> // Override default menu items
customFilters?: Record<string, any> // Override default filters
// Styling
className?: string // Additional CSS classes
}
Features
- ✅ Multi-select dropdowns for templates, purpose codes, statuses, aggregators
- ✅ Date range picker with start/end date selection
- ✅ Drag & drop reordering for menu items and groups
- ✅ Responsive design with mobile support
- ✅ Keyboard navigation and accessibility
- ✅ Smooth animations and hover effects
🎨 Sidebar Components
SidebarProvider
Context provider that manages sidebar state across your application.
<SidebarProvider defaultOpen={true} onOpenChange={setIsOpen}>
<App />
</SidebarProvider>
Props:
defaultOpen?: boolean
- Initial open stateopen?: boolean
- Controlled open stateonOpenChange?: (open: boolean) => void
- State change handler
Sidebar
Main sidebar container with responsive behavior.
<Sidebar aria-label="Main navigation">
<SidebarHeader>...</SidebarHeader>
<SidebarContent>...</SidebarContent>
<SidebarFooter>...</SidebarFooter>
</Sidebar>
Props:
className?: string
- CSS classesaria-label?: string
- Accessibility label
SidebarMenu
Container for menu items with optional drag & drop reordering.
<SidebarMenu reorderable onReorder={handleReorder}>
{items.map(item => (
<SidebarMenuItem key={item.id} draggable dragId={item.id}>
<SidebarMenuButton itemId={item.id}>
{item.label}
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
Props:
reorderable?: boolean
- Enable drag & droponReorder?: (newOrder: string[]) => void
- Reorder handler
📂 Dropdown Components
Basic Dropdown
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from 'equal-ds-ui'
<Dropdown>
<DropdownTrigger>Select Option</DropdownTrigger>
<DropdownContent>
<DropdownItem onClick={() => setValue('option1')}>
Option 1
</DropdownItem>
<DropdownItem onClick={() => setValue('option2')}>
Option 2
</DropdownItem>
</DropdownContent>
</Dropdown>
Multi-Select Dropdown
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItemMultiselect } from 'equal-ds-ui'
<Dropdown>
<DropdownTrigger>
{selected.length ? `${selected.length} selected` : 'Select items'}
</DropdownTrigger>
<DropdownContent>
{options.map(option => (
<DropdownItemMultiselect
key={option.id}
checked={selected.includes(option.id)}
onCheckedChange={(checked) => {
if (checked) {
setSelected([...selected, option.id])
} else {
setSelected(selected.filter(id => id !== option.id))
}
}}
>
{option.label}
</DropdownItemMultiselect>
))}
</DropdownContent>
</Dropdown>
Dropdown with Search
<Dropdown>
<DropdownTrigger>Search items</DropdownTrigger>
<DropdownContent enableSearch searchPlaceholder="Type to search...">
{filteredOptions.map(option => (
<DropdownItem key={option.id} onClick={() => handleSelect(option.id)}>
{option.label}
</DropdownItem>
))}
</DropdownContent>
</Dropdown>
Custom Trigger Icons
<Dropdown>
<DropdownTrigger
chevronIcons={{
open: <span className="text-green-500">▲</span>,
closed: <span className="text-red-500">▼</span>
}}
>
Custom chevrons
</DropdownTrigger>
<DropdownContent>
<DropdownItem>Item 1</DropdownItem>
</DropdownContent>
</Dropdown>
📅 Date Picker Components
Basic Date Picker
import { DatePicker, DatePickerTrigger, DatePickerContent } from 'equal-ds-ui'
function BasicDatePicker() {
const [date, setDate] = useState<Date>()
return (
<DatePicker value={date} onChange={setDate}>
<DatePickerTrigger>
{date ? date.toLocaleDateString() : 'Pick a date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
)
}
Date Range Picker
import { DatePicker, DatePickerTrigger, DateRangePickerContent } from 'equal-ds-ui'
function DateRangePicker() {
const [dateRange, setDateRange] = useState<{start?: Date, end?: Date}>({})
return (
<DatePicker value={dateRange} onChange={setDateRange}>
<DatePickerTrigger>
{dateRange.start && dateRange.end
? `${dateRange.start.toLocaleDateString()} - ${dateRange.end.toLocaleDateString()}`
: 'Select date range'
}
</DatePickerTrigger>
<DateRangePickerContent />
</DatePicker>
)
}
Time Picker
import { DatePicker, DatePickerTrigger, TimePickerContent } from 'equal-ds-ui'
function TimePicker() {
const [time, setTime] = useState<{hours: number, minutes: number, period: 'AM'|'PM'}>()
return (
<DatePicker value={time} onChange={setTime}>
<DatePickerTrigger>
{time ? `${time.hours}:${time.minutes.toString().padStart(2, '0')} ${time.period}` : 'Pick time'}
</DatePickerTrigger>
<TimePickerContent />
</DatePicker>
)
}
Advanced Configuration
<DatePicker
value={date}
onChange={setDate}
minDate={new Date()} // Disable past dates
maxDate={new Date(Date.now() + 365 * 24 * 60 * 60 * 1000)} // 1 year from now
>
<DatePickerTrigger className="w-64" disabled={false}>
{date ? date.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
}) : 'Choose a date'}
</DatePickerTrigger>
<DatePickerContent
showTimeSelect={true}
timeIntervals={15}
dateFormat="MMMM dd, yyyy"
/>
</DatePicker>
🎨 Design Tokens & Styling
Color System
/* Background Colors */
--color-background-primary: #ffffff;
--color-background-secondary: #f8fafc;
--color-background-tertiary: #f1f5f9;
/* Text Colors */
--color-text-primary: #0f3340;
--color-text-secondary: #64748b;
--color-text-muted: #94a3b8;
/* Border Colors */
--color-border-default: #e2e8f0;
--color-border-hover: #cbd5e1;
--color-border-focus: #0f3340;
/* Status Colors */
--color-status-success: #10b981;
--color-status-error: #ef4444;
--color-status-warning: #f59e0b;
Typography Scale
/* Font Sizes */
--typography-fontSize-xs: 0.75rem; /* 12px */
--typography-fontSize-sm: 0.875rem; /* 14px */
--typography-fontSize-md: 1rem; /* 16px */
--typography-fontSize-lg: 1.125rem; /* 18px */
--typography-fontSize-xl: 1.25rem; /* 20px */
/* Font Weights */
--typography-fontWeight-normal: 400;
--typography-fontWeight-medium: 500;
--typography-fontWeight-semibold: 600;
--typography-fontWeight-bold: 700;
Spacing Scale
--spacing-0: 0;
--spacing-1: 0.25rem; /* 4px */
--spacing-2: 0.5rem; /* 8px */
--spacing-3: 0.75rem; /* 12px */
--spacing-4: 1rem; /* 16px */
--spacing-6: 1.5rem; /* 24px */
--spacing-8: 2rem; /* 32px */
Using Design Tokens in Components
// Direct CSS usage
<div style={{
backgroundColor: 'var(--color-background-primary)',
color: 'var(--color-text-primary)',
padding: 'var(--spacing-4)',
borderRadius: 'var(--border-radius-lg)'
}}>
Styled with design tokens
</div>
// Tailwind classes (when using preset)
<div className="bg-background-primary text-text-primary p-4 rounded-lg">
Styled with Tailwind
</div>
🔧 Utility Functions
cn()
- Class Name Utility
Combines and conditionally applies CSS classes.
import { cn } from 'equal-ds-ui'
// Basic usage
const className = cn('base-class', 'another-class')
// Conditional classes
const className = cn(
'button',
isActive && 'button--active',
isDisabled && 'button--disabled'
)
// Multiple conditions
const className = cn(
'card',
{
'card--featured': isFeatured,
'card--selected': isSelected,
'card--disabled': isDisabled
}
)
useHoverAnimation()
- Hover State Management
Manages hover animations and interactions.
import { useHoverAnimation } from 'equal-ds-ui'
function AnimatedButton() {
const { indicator, handleMouseMove, handleMouseLeave, setContainerRef } = useHoverAnimation({
itemSelector: '[data-hoverable]',
duration: 200,
enabled: true
})
return (
<div ref={setContainerRef} onMouseMove={handleMouseMove} onMouseLeave={handleMouseLeave}>
<HoverIndicator indicator={indicator} />
<button data-hoverable>Hover me</button>
</div>
)
}
ChevronIcon
- Animated Chevron
Animated chevron icon with rotation states.
import { ChevronIcon } from 'equal-ds-ui'
<ChevronIcon
isOpen={isExpanded}
size="default" // 'sm' | 'default' | 'lg'
opacity={70} // 0-100
className="custom-class"
/>
🎯 Advanced Usage Patterns
Custom Sidebar with Theming
import { SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarTrigger } from 'equal-ds-ui'
function CustomSidebar() {
return (
<SidebarProvider defaultOpen={true}>
<div className="flex h-screen">
<Sidebar className="bg-gradient-to-b from-blue-600 to-blue-800 text-white">
<SidebarHeader className="border-b border-blue-500">
<div className="p-4">
<h1 className="text-xl font-bold">My App</h1>
</div>
</SidebarHeader>
<SidebarContent>
{/* Custom sidebar content */}
</SidebarContent>
</Sidebar>
<main className="flex-1 p-6">
<SidebarTrigger className="bg-blue-600 text-white hover:bg-blue-700">
☰
</SidebarTrigger>
</main>
</div>
</SidebarProvider>
)
}
Form Integration with Validation
import { DatePicker, Dropdown } from 'equal-ds-ui'
function BookingForm() {
const [formData, setFormData] = useState({
checkIn: undefined,
checkOut: undefined,
roomType: ''
})
const [errors, setErrors] = useState({})
const validateDates = () => {
if (formData.checkIn && formData.checkOut && formData.checkIn >= formData.checkOut) {
setErrors({ dates: 'Check-out must be after check-in' })
return false
}
setErrors({})
return true
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<DatePicker
value={formData.checkIn}
onChange={(date) => setFormData(prev => ({ ...prev, checkIn: date }))}
>
<DatePickerTrigger className="w-full">
{formData.checkIn?.toLocaleDateString() || 'Check-in date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
<DatePicker
value={formData.checkOut}
onChange={(date) => setFormData(prev => ({ ...prev, checkOut: date }))}
minDate={formData.checkIn}
>
<DatePickerTrigger className="w-full">
{formData.checkOut?.toLocaleDateString() || 'Check-out date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
</div>
<Dropdown>
<DropdownTrigger className="w-full">
{formData.roomType || 'Select room type'}
</DropdownTrigger>
<DropdownContent className="w-full">
<DropdownItem onClick={() => setFormData(prev => ({ ...prev, roomType: 'Standard' }))}>
Standard Room
</DropdownItem>
<DropdownItem onClick={() => setFormData(prev => ({ ...prev, roomType: 'Deluxe' }))}>
Deluxe Room
</DropdownItem>
<DropdownItem onClick={() => setFormData(prev => ({ ...prev, roomType: 'Suite' }))}>
Suite
</DropdownItem>
</DropdownContent>
</Dropdown>
{errors.dates && (
<div className="text-red-500 text-sm">{errors.dates}</div>
)}
<button
type="submit"
className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700"
disabled={!formData.checkIn || !formData.checkOut || !formData.roomType}
>
Book Room
</button>
</form>
)
}
🤖 LLM Usage Guide
This section is specifically designed to help AI assistants and developers understand and use Equal DS UI effectively.
🎯 Most Common Use Cases
1. Basic Application Layout with Sidebar
// Pattern: Sidebar + Main Content Layout
import { SidebarProvider, FinProSidebar } from 'equal-ds-ui'
import 'equal-ds-ui/tokens.css'
function AppLayout() {
return (
<SidebarProvider defaultOpen={true}>
<div className="flex h-screen bg-background-secondary">
<FinProSidebar headerText="My App" />
<main className="flex-1 p-6 overflow-auto">
{/* Your main content here */}
<h1 className="text-2xl font-bold text-text-primary">Dashboard</h1>
</main>
</div>
</SidebarProvider>
)
}
2. Form with Multiple Dropdowns and Date Picker
// Pattern: Complex Form with Multiple Components
import { useState } from 'react'
import {
Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownItemMultiselect,
DatePicker, DatePickerTrigger, DatePickerContent
} from 'equal-ds-ui'
function ComplexForm() {
const [formState, setFormState] = useState({
selectedOption: '',
selectedItems: [] as string[],
selectedDate: undefined as Date | undefined
})
const options = [
{ id: 'option1', label: 'Option 1' },
{ id: 'option2', label: 'Option 2' },
{ id: 'option3', label: 'Option 3' }
]
return (
<div className="space-y-4 p-6">
{/* Single Select Dropdown */}
<Dropdown>
<DropdownTrigger className="w-64">
{formState.selectedOption || 'Select option'}
</DropdownTrigger>
<DropdownContent>
{options.map(option => (
<DropdownItem
key={option.id}
onClick={() => setFormState(prev => ({ ...prev, selectedOption: option.label }))}
>
{option.label}
</DropdownItem>
))}
</DropdownContent>
</Dropdown>
{/* Multi-Select Dropdown */}
<Dropdown>
<DropdownTrigger className="w-64">
{formState.selectedItems.length ? `${formState.selectedItems.length} selected` : 'Select items'}
</DropdownTrigger>
<DropdownContent>
{options.map(option => (
<DropdownItemMultiselect
key={option.id}
checked={formState.selectedItems.includes(option.id)}
onCheckedChange={(checked) => {
if (checked) {
setFormState(prev => ({
...prev,
selectedItems: [...prev.selectedItems, option.id]
}))
} else {
setFormState(prev => ({
...prev,
selectedItems: prev.selectedItems.filter(id => id !== option.id)
}))
}
}}
>
{option.label}
</DropdownItemMultiselect>
))}
</DropdownContent>
</Dropdown>
{/* Date Picker */}
<DatePicker
value={formState.selectedDate}
onChange={(date) => setFormState(prev => ({ ...prev, selectedDate: date }))}
>
<DatePickerTrigger className="w-64">
{formState.selectedDate?.toLocaleDateString() || 'Pick a date'}
</DatePickerTrigger>
<DatePickerContent />
</DatePicker>
</div>
)
}
3. Theming and Customization
// Pattern: Custom Theming with Design Tokens
import 'equal-ds-ui/tokens.css'
// Using design tokens directly in CSS
const customStyles = {
backgroundColor: 'var(--color-background-primary)',
color: 'var(--color-text-primary)',
borderColor: 'var(--color-border-default)',
padding: 'var(--spacing-4)',
borderRadius: 'var(--border-radius-lg)'
}
// Using Tailwind classes with design tokens
function ThemedComponent() {
return (
<div className="bg-background-primary text-text-primary border border-border-default p-4 rounded-lg">
<h2 className="text-xl font-semibold mb-2">Themed Component</h2>
<p className="text-text-secondary">This component uses design system tokens.</p>
</div>
)
}
🔍 Quick Component Reference
Import Patterns
// Most common imports
import { FinProSidebar, SidebarProvider } from 'equal-ds-ui'
import { Dropdown, DropdownTrigger, DropdownContent, DropdownItem } from 'equal-ds-ui'
import { DatePicker, DatePickerTrigger, DatePickerContent } from 'equal-ds-ui'
// Utility imports
import { cn, useHoverAnimation, ChevronIcon } from 'equal-ds-ui'
// Style imports (required)
import 'equal-ds-ui/tokens.css'
import 'equal-ds-ui/animations.css'
Component Props Quick Reference
// FinProSidebar
<FinProSidebar
headerText="My App" // string
defaultSelected="dashboard" // string
defaultExpanded={true} // boolean
className="custom-class" // string
/>
// Dropdown
<Dropdown>
<DropdownTrigger className="w-64" chevronIcons={customIcons}>
Trigger Text
</DropdownTrigger>
<DropdownContent enableSearch searchPlaceholder="Search...">
<DropdownItem onClick={handleClick}>Item 1</DropdownItem>
<DropdownItemMultiselect checked={isChecked} onCheckedChange={setChecked}>
Multi-select Item
</DropdownItemMultiselect>
</DropdownContent>
</Dropdown>
// DatePicker
<DatePicker
value={selectedDate} // Date | undefined
onChange={setSelectedDate} // (date: Date) => void
minDate={new Date()} // Date
maxDate={futureDate} // Date
>
<DatePickerTrigger className="w-64">
{selectedDate?.toLocaleDateString() || 'Pick date'}
</DatePickerTrigger>
<DatePickerContent showTimeSelect={false} />
</DatePicker>
🎨 Design Token Usage Examples
Color Tokens
/* Background Colors */
.primary-bg { background-color: var(--color-background-primary); }
.secondary-bg { background-color: var(--color-background-secondary); }
.tertiary-bg { background-color: var(--color-background-tertiary); }
/* Text Colors */
.primary-text { color: var(--color-text-primary); }
.secondary-text { color: var(--color-text-secondary); }
.muted-text { color: var(--color-text-muted); }
/* Status Colors */
.success { color: var(--color-status-success); }
.error { color: var(--color-status-error); }
.warning { color: var(--color-status-warning); }
Spacing & Typography
/* Spacing */
.small-padding { padding: var(--spacing-2); } /* 8px */
.medium-padding { padding: var(--spacing-4); } /* 16px */
.large-padding { padding: var(--spacing-6); } /* 24px */
/* Typography */
.small-text { font-size: var(--typography-fontSize-sm); }
.medium-text { font-size: var(--typography-fontSize-md); }
.large-text { font-size: var(--typography-fontSize-lg); }
.semibold { font-weight: var(--typography-fontWeight-semibold); }
.bold { font-weight: var(--typography-fontWeight-bold); }
Tailwind Classes
// Using design system classes
<div className="bg-background-primary text-text-primary p-4 rounded-lg">
<h2 className="text-xl font-semibold text-text-primary mb-2">Title</h2>
<p className="text-text-secondary">Description text</p>
<button className="bg-primary-500 text-primary-50 px-4 py-2 rounded-md hover:bg-primary-600">
Button
</button>
</div>
🛠️ Troubleshooting Common Issues
Issue: Components not styled correctly
# Solution: Ensure CSS imports are in correct order
import 'equal-ds-ui/tokens.css' // First: design tokens
import 'equal-ds-ui/animations.css' // Second: animations
// Then: other CSS imports
Issue: Tailwind classes not working
// Solution: Add to tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/equal-ds-ui/dist/**/*.{js,jsx,ts,tsx}', // Required!
],
presets: [require('equal-ds-ui/tailwind-preset')]
}
Issue: TypeScript errors
// Solution: Install peer dependencies
npm install @radix-ui/react-collapsible @radix-ui/react-tooltip @radix-ui/react-visually-hidden lucide-react tailwindcss
📊 Bundle Size Information
Format | Size | Best For |
---|---|---|
ESM | ~78 KB | Modern bundlers (Vite, esbuild) |
CommonJS | ~84 KB | Legacy bundlers (Webpack 4) |
Both | ~162 KB | Maximum compatibility |
🔄 Migration Guide
From v0.x to v1.x
// OLD (v0.x)
import { Sidebar } from 'equal-ds-ui'
// NEW (v1.x)
import { FinProSidebar, SidebarProvider } from 'equal-ds-ui'
function App() {
return (
<SidebarProvider>
<FinProSidebar headerText="My App" />
</SidebarProvider>
)
}
Updating Color Classes
// OLD → NEW
'text-foreground' → 'text-text-primary'
'bg-background' → 'bg-background-secondary'
'border-border' → 'border-border-default'
'focus:ring-ring' → 'focus:ring-primary-400'
📚 Documentation
📖 Complete API Reference
For detailed API documentation, see API_REFERENCE.md
- a comprehensive guide optimized for developers and AI assistants.
🚀 Usage Examples
Quick copy-paste examples for common use cases in USAGE_EXAMPLES.md
.
🤖 LLM-Friendly Documentation
This package includes documentation specifically designed for AI assistants:
- Structured API Reference with clear prop types and examples
- Copy-paste ready examples for immediate use
- Common patterns with real-world usage scenarios
- Troubleshooting guides for frequent issues
🔧 Development
# Install dependencies
npm install
# Start development
npm run storybook # Storybook development
npm run dev # Vite development
npm test # Run tests
# Build for publishing
npm run build:esm # ESM only
npm run build:cjs # CommonJS only
npm run build # Both formats
# Publish
npm run prepublishOnly # Prepares for publishing
npm publish
🤝 Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
📄 License
MIT © Equal DS
Built with ❤️ for modern React applications
Installation • Quick Start • Format Selection • Custom Tokens