skills/creating-dashboards/references/library-guide.md

804 lines
18 KiB
Markdown
Raw 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.

# Dashboard Libraries Implementation Guide
## Table of Contents
- [Tremor Quick Start](#tremor-quick-start)
- [React Grid Layout Setup](#react-grid-layout-setup)
- [Combining Libraries](#combining-libraries)
- [Migration Strategies](#migration-strategies)
- [Advanced Configurations](#advanced-configurations)
- [Troubleshooting](#troubleshooting)
## Tremor Quick Start
### Installation & Setup
```bash
# Install Tremor with Tailwind CSS
npm install @tremor/react tailwindcss @tailwindcss/forms
# Initialize Tailwind if not already configured
npx tailwindcss init -p
```
### Tailwind Configuration for Tremor
```javascript
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@tremor/**/*.{js,ts,jsx,tsx}', // Tremor components
],
theme: {
extend: {
colors: {
tremor: {
brand: {
faint: '#eff6ff',
muted: '#bfdbfe',
subtle: '#60a5fa',
DEFAULT: '#3b82f6',
emphasis: '#1d4ed8',
inverted: '#ffffff',
},
background: {
muted: '#f9fafb',
subtle: '#f3f4f6',
DEFAULT: '#ffffff',
emphasis: '#374151',
},
// Additional Tremor colors...
}
}
}
},
plugins: [require('@tailwindcss/forms')]
};
```
### Basic Tremor Dashboard
```tsx
import {
Card,
Title,
Text,
Tab,
TabList,
TabGroup,
TabPanel,
TabPanels,
Grid,
Metric,
BadgeDelta,
Flex,
ProgressBar,
AreaChart,
BarChart,
DonutChart,
Table,
TableHead,
TableRow,
TableHeaderCell,
TableBody,
TableCell
} from '@tremor/react';
function TremorDashboard() {
return (
<main className="p-6 sm:p-10">
<Title>Analytics Dashboard</Title>
<Text>Track your key metrics and performance indicators</Text>
{/* KPI Cards Row */}
<Grid numItemsSm={2} numItemsLg={4} className="gap-6 mt-6">
<Card>
<Flex alignItems="start">
<div>
<Text>Revenue</Text>
<Metric>$45,231.89</Metric>
</div>
<BadgeDelta deltaType="increase">12.5%</BadgeDelta>
</Flex>
</Card>
<Card>
<Flex alignItems="start">
<div>
<Text>New Customers</Text>
<Metric>1,234</Metric>
</div>
<BadgeDelta deltaType="decrease">-2.3%</BadgeDelta>
</Flex>
</Card>
<Card>
<Flex alignItems="start">
<div>
<Text>Active Users</Text>
<Metric>8,456</Metric>
</div>
<BadgeDelta deltaType="increase">5.1%</BadgeDelta>
</Flex>
</Card>
<Card>
<Flex alignItems="start">
<div>
<Text>Retention Rate</Text>
<Metric>92.5%</Metric>
</div>
<BadgeDelta deltaType="unchanged">0.0%</BadgeDelta>
</Flex>
</Card>
</Grid>
{/* Charts Section */}
<div className="mt-6">
<Card>
<Title>Revenue Trend</Title>
<AreaChart
className="h-72 mt-4"
data={revenueData}
index="date"
categories={["Revenue", "Target"]}
colors={["blue", "gray"]}
valueFormatter={dataFormatter}
/>
</Card>
</div>
{/* Table Section */}
<Card className="mt-6">
<Title>Recent Transactions</Title>
<Table className="mt-5">
<TableHead>
<TableRow>
<TableHeaderCell>Transaction</TableHeaderCell>
<TableHeaderCell>Amount</TableHeaderCell>
<TableHeaderCell>Status</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{transactions.map(item => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.amount}</TableCell>
<TableCell>{item.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</main>
);
}
```
### Advanced Tremor Components
#### Multi-Metric Cards
```tsx
function MultiMetricCard() {
return (
<Card className="max-w-lg mx-auto">
<Flex className="space-x-8" justifyContent="start" alignItems="baseline">
<div>
<Text>Sales</Text>
<Metric>$12,699</Metric>
<Text className="mt-2">from $10,456</Text>
</div>
<div>
<Text>Profit</Text>
<Metric>$4,034</Metric>
<Text className="mt-2">from $3,210</Text>
</div>
</Flex>
<ProgressBar value={32} className="mt-4" />
<Text className="mt-2">32% of annual target</Text>
</Card>
);
}
```
#### Tabbed Dashboard
```tsx
function TabbedDashboard() {
return (
<Card>
<TabGroup>
<TabList className="mt-8">
<Tab>Overview</Tab>
<Tab>Detail</Tab>
<Tab>Analysis</Tab>
</TabList>
<TabPanels>
<TabPanel>
<Grid numItemsMd={2} numItemsLg={3} className="gap-6 mt-6">
{/* Overview widgets */}
</Grid>
</TabPanel>
<TabPanel>
{/* Detailed charts */}
</TabPanel>
<TabPanel>
{/* Analysis tables */}
</TabPanel>
</TabPanels>
</TabGroup>
</Card>
);
}
```
## React Grid Layout Setup
### Installation
```bash
npm install react-grid-layout
# Required CSS
npm install react-resizable
```
### Basic Setup
```tsx
import GridLayout from 'react-grid-layout';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
function BasicGridDashboard() {
const layout = [
{ i: 'a', x: 0, y: 0, w: 4, h: 2 },
{ i: 'b', x: 4, y: 0, w: 4, h: 2 },
{ i: 'c', x: 8, y: 0, w: 4, h: 2 },
{ i: 'd', x: 0, y: 2, w: 12, h: 4 }
];
return (
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={80}
width={1200}
>
<div key="a" className="widget">
<KPICard title="Revenue" value="$45K" />
</div>
<div key="b" className="widget">
<KPICard title="Users" value="1.2K" />
</div>
<div key="c" className="widget">
<KPICard title="Orders" value="342" />
</div>
<div key="d" className="widget">
<ChartWidget />
</div>
</GridLayout>
);
}
```
### Responsive Grid Layout
```tsx
import { Responsive, WidthProvider } from 'react-grid-layout';
const ResponsiveGridLayout = WidthProvider(Responsive);
function ResponsiveDashboard() {
const [layouts, setLayouts] = useState(getFromLS('layouts') || {});
const handleLayoutChange = (layout, layouts) => {
saveToLS('layouts', layouts);
setLayouts(layouts);
};
return (
<ResponsiveGridLayout
className="layout"
layouts={layouts}
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }}
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }}
rowHeight={60}
onLayoutChange={handleLayoutChange}
>
<div key="1">Widget 1</div>
<div key="2">Widget 2</div>
<div key="3">Widget 3</div>
</ResponsiveGridLayout>
);
}
// Local Storage helpers
function getFromLS(key) {
let ls = {};
if (global.localStorage) {
try {
ls = JSON.parse(global.localStorage.getItem('rgl-8')) || {};
} catch (e) {}
}
return ls[key];
}
function saveToLS(key, value) {
if (global.localStorage) {
global.localStorage.setItem(
'rgl-8',
JSON.stringify({
[key]: value
})
);
}
}
```
### Advanced Grid Features
#### Drag Handle & Resize Restrictions
```tsx
function RestrictedGridDashboard() {
const layout = [
{
i: 'header',
x: 0,
y: 0,
w: 12,
h: 1,
static: true // Cannot be moved or resized
},
{
i: 'kpi1',
x: 0,
y: 1,
w: 3,
h: 2,
minW: 2,
maxW: 4,
minH: 1,
maxH: 3
},
{
i: 'chart',
x: 3,
y: 1,
w: 9,
h: 4,
minW: 6,
minH: 3
}
];
return (
<GridLayout
layout={layout}
cols={12}
rowHeight={60}
draggableHandle=".drag-handle" // Only draggable by handle
resizeHandles={['se', 'sw']} // Only bottom corners
compactType="vertical"
preventCollision={false}
>
<div key="header" className="widget">
<h1>Dashboard Header (Static)</h1>
</div>
<div key="kpi1" className="widget">
<div className="drag-handle">⋮⋮</div>
<KPIWidget />
</div>
<div key="chart" className="widget">
<div className="drag-handle">⋮⋮</div>
<ChartWidget />
</div>
</GridLayout>
);
}
```
#### Widget Catalog with Add/Remove
```tsx
function CustomizableDashboard() {
const [widgets, setWidgets] = useState([
{ id: 'widget-1', type: 'kpi', x: 0, y: 0, w: 3, h: 2 }
]);
const widgetCatalog = [
{ type: 'kpi', name: 'KPI Card', defaultSize: { w: 3, h: 2 } },
{ type: 'chart', name: 'Chart', defaultSize: { w: 6, h: 4 } },
{ type: 'table', name: 'Table', defaultSize: { w: 6, h: 3 } }
];
const addWidget = (type) => {
const catalogItem = widgetCatalog.find(w => w.type === type);
const newWidget = {
id: `widget-${Date.now()}`,
type,
x: 0,
y: Infinity, // Will be placed at bottom
...catalogItem.defaultSize
};
setWidgets([...widgets, newWidget]);
};
const removeWidget = (id) => {
setWidgets(widgets.filter(w => w.id !== id));
};
return (
<div>
{/* Widget Catalog */}
<div className="widget-catalog">
{widgetCatalog.map(item => (
<button
key={item.type}
onClick={() => addWidget(item.type)}
>
Add {item.name}
</button>
))}
</div>
{/* Grid Dashboard */}
<ResponsiveGridLayout>
{widgets.map(widget => (
<div key={widget.id} data-grid={widget}>
<div className="widget-wrapper">
<button
className="remove-btn"
onClick={() => removeWidget(widget.id)}
>
×
</button>
{renderWidget(widget)}
</div>
</div>
))}
</ResponsiveGridLayout>
</div>
);
}
```
## Combining Libraries
### Tremor Components in Grid Layout
```tsx
import { Card, Metric, Text, AreaChart } from '@tremor/react';
import { Responsive, WidthProvider } from 'react-grid-layout';
const ResponsiveGridLayout = WidthProvider(Responsive);
function HybridDashboard() {
const layouts = {
lg: [
{ i: 'revenue-kpi', x: 0, y: 0, w: 3, h: 2 },
{ i: 'users-kpi', x: 3, y: 0, w: 3, h: 2 },
{ i: 'chart', x: 0, y: 2, w: 6, h: 4 },
{ i: 'table', x: 6, y: 0, w: 6, h: 6 }
]
};
return (
<ResponsiveGridLayout
layouts={layouts}
breakpoints={{ lg: 1200, md: 996, sm: 768 }}
cols={{ lg: 12, md: 10, sm: 6 }}
rowHeight={60}
>
{/* Tremor KPI Card */}
<div key="revenue-kpi">
<Card className="h-full">
<Text>Revenue</Text>
<Metric>$45,231</Metric>
</Card>
</div>
{/* Tremor KPI Card */}
<div key="users-kpi">
<Card className="h-full">
<Text>Active Users</Text>
<Metric>1,234</Metric>
</Card>
</div>
{/* Tremor Chart */}
<div key="chart">
<Card className="h-full">
<AreaChart
data={chartData}
index="date"
categories={["Sales"]}
colors={["blue"]}
/>
</Card>
</div>
{/* Custom Table Widget */}
<div key="table">
<Card className="h-full">
<CustomTableWidget />
</Card>
</div>
</ResponsiveGridLayout>
);
}
```
### Custom Widget Wrapper
```tsx
function WidgetWrapper({ widget, children }) {
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
// Simulate loading
setTimeout(() => setIsLoading(false), 1000);
}, []);
return (
<Card className="h-full relative">
{/* Widget Header */}
<div className="widget-header drag-handle flex justify-between items-center mb-4">
<Title>{widget.title}</Title>
<div className="widget-actions">
<button className="refresh-btn"></button>
<button className="settings-btn"></button>
</div>
</div>
{/* Widget Content */}
<div className="widget-content">
{isLoading ? (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-8 bg-gray-200 rounded w-1/2"></div>
</div>
) : hasError ? (
<div className="error-state">
<Text>Failed to load widget</Text>
<button onClick={() => setHasError(false)}>Retry</button>
</div>
) : (
children
)}
</div>
{/* Resize Handle (for react-grid-layout) */}
<span className="react-resizable-handle react-resizable-handle-se" />
</Card>
);
}
```
## Migration Strategies
### Migrating from Static to Grid Layout
```tsx
// Before: Static Dashboard
function StaticDashboard() {
return (
<div className="dashboard">
<div className="row">
<KPICard />
<KPICard />
<KPICard />
</div>
<div className="row">
<ChartWidget />
<TableWidget />
</div>
</div>
);
}
// After: Grid Layout Dashboard
function MigratedDashboard() {
// Convert static positions to grid layout
const staticToGridLayout = () => {
return [
{ i: 'kpi-1', x: 0, y: 0, w: 4, h: 2 },
{ i: 'kpi-2', x: 4, y: 0, w: 4, h: 2 },
{ i: 'kpi-3', x: 8, y: 0, w: 4, h: 2 },
{ i: 'chart', x: 0, y: 2, w: 6, h: 4 },
{ i: 'table', x: 6, y: 2, w: 6, h: 4 }
];
};
const [layout, setLayout] = useState(staticToGridLayout());
const [isCustomizable, setIsCustomizable] = useState(false);
return (
<>
<button onClick={() => setIsCustomizable(!isCustomizable)}>
{isCustomizable ? 'Lock Layout' : 'Customize Layout'}
</button>
<GridLayout
layout={layout}
cols={12}
rowHeight={60}
width={1200}
isDraggable={isCustomizable}
isResizable={isCustomizable}
onLayoutChange={setLayout}
>
<div key="kpi-1"><KPICard /></div>
<div key="kpi-2"><KPICard /></div>
<div key="kpi-3"><KPICard /></div>
<div key="chart"><ChartWidget /></div>
<div key="table"><TableWidget /></div>
</GridLayout>
</>
);
}
```
## Advanced Configurations
### Dynamic Grid Configuration
```tsx
function DynamicGridConfig() {
const [config, setConfig] = useState({
cols: 12,
rowHeight: 60,
compactType: 'vertical',
margin: [10, 10]
});
const updateConfig = (key, value) => {
setConfig({ ...config, [key]: value });
};
return (
<div>
{/* Configuration Panel */}
<div className="config-panel">
<label>
Columns:
<input
type="number"
value={config.cols}
onChange={(e) => updateConfig('cols', parseInt(e.target.value))}
/>
</label>
<label>
Row Height:
<input
type="number"
value={config.rowHeight}
onChange={(e) => updateConfig('rowHeight', parseInt(e.target.value))}
/>
</label>
<label>
Compact Type:
<select
value={config.compactType}
onChange={(e) => updateConfig('compactType', e.target.value)}
>
<option value="vertical">Vertical</option>
<option value="horizontal">Horizontal</option>
<option value={null}>None</option>
</select>
</label>
</div>
{/* Grid with dynamic config */}
<GridLayout
{...config}
width={1200}
>
{/* Widgets */}
</GridLayout>
</div>
);
}
```
### Theme Integration
```tsx
// Tremor with custom theme
const customTheme = {
dark: {
background: '#1a1a1a',
card: '#2a2a2a',
text: '#ffffff',
border: '#3a3a3a'
},
light: {
background: '#ffffff',
card: '#f9fafb',
text: '#111827',
border: '#e5e7eb'
}
};
function ThemedDashboard({ theme = 'light' }) {
const colors = customTheme[theme];
return (
<div
className="dashboard"
style={{
backgroundColor: colors.background,
color: colors.text
}}
>
<ResponsiveGridLayout>
{widgets.map(widget => (
<div key={widget.id}>
<Card
style={{
backgroundColor: colors.card,
borderColor: colors.border
}}
>
{renderWidget(widget)}
</Card>
</div>
))}
</ResponsiveGridLayout>
</div>
);
}
```
## Troubleshooting
### Common Issues and Solutions
#### 1. Grid Layout CSS Not Loading
```tsx
// Ensure CSS imports are correct
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
// Or import in your main CSS file
/* styles.css */
@import '~react-grid-layout/css/styles.css';
@import '~react-resizable/css/styles.css';
```
#### 2. Widgets Overlapping
```tsx
// Set preventCollision to true
<GridLayout
preventCollision={true}
compactType="vertical"
// This will prevent widgets from overlapping
>
```
#### 3. Responsive Layout Not Working
```tsx
// Must wrap with WidthProvider
const ResponsiveGridLayout = WidthProvider(Responsive);
// Don't pass width prop when using WidthProvider
<ResponsiveGridLayout
// width={1200} // Don't do this
layouts={layouts}
>
```
#### 4. Tremor Components Not Styled
```javascript
// Ensure Tailwind processes Tremor files
// tailwind.config.js
module.exports = {
content: [
'./src/**/*.{js,jsx,ts,tsx}',
'./node_modules/@tremor/**/*.{js,ts,jsx,tsx}', // Important!
]
};
```
#### 5. Performance Issues with Many Widgets
```tsx
// Use React.memo for widget components
const MemoizedWidget = React.memo(({ data }) => {
return <Card>{/* Widget content */}</Card>;
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.data === nextProps.data;
});
// Virtualize if > 50 widgets
import { FixedSizeGrid } from 'react-window';
```