Zubeyr··PagesanalyticslayoutAnalytics Dashboard
A complete analytics screen: sidebar, search header, date range picker and a grid of line, bar, donut and ranked cards.
Live demo ↗Installation
Run the shadcn CLI in a project that already has components.json. Using the @tomei tab? Register the namespace once first.
pnpm dlx shadcn@latest add https://tomei.zubeyr.dev/ui/r/analytics-dashboard.json- →Installs
lucide-reactfrom npm. - →Pulls in
sidebar. - →Pulls in
input. - →Pulls in
button. - →Pulls in
date-range-picker. - →Pulls in
card. - →Pulls in
line-chart-card. - →Pulls in
bar-chart-card. - →Pulls in
donut-chart-card. - →Files land in
components/dashboard/analytics-dashboard.
Installing by hand? Add the dependencies, then copy the source code below.
pnpm add lucide-reactUsage
import { AnalyticsDashboard } from '@/components/dashboard/analytics-dashboard/analytics-dashboard'
export default function Demo() {
return <AnalyticsDashboard />
}Source code
import {
BarChart3,
Bell,
BookOpen,
Home,
Maximize2,
Megaphone,
Package,
Search,
Settings,
ShoppingBag,
Store,
Tag,
Users,
} from 'lucide-react'
import { Button } from '@/registry/dashboard/components/button/button'
import { Input } from '@/registry/dashboard/components/input/input'
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarItem,
} from '@/registry/dashboard/components/sidebar/sidebar'
import { Card, Trend } from '@/registry/dashboard/components/card/card'
import { DateRangePicker } from '@/registry/dashboard/components/date-range-picker/date-range-picker'
import { BarChartCard } from '@/registry/dashboard/blocks/bar-chart-card/bar-chart-card'
import { DonutChartCard } from '@/registry/dashboard/blocks/donut-chart-card/donut-chart-card'
import { LineChartCard } from '@/registry/dashboard/blocks/line-chart-card/line-chart-card'
import { devices, locations, netSales, products, totalSales, visitors } from './data'
const currency = { style: 'currency', currency: 'USD', maximumFractionDigits: 0 } as const
const compact = new Intl.NumberFormat('en', { ...currency, notation: 'compact' })
const topProduct = Math.max(...products.map((p) => p.current))
const expand = (
<Button variant="ghost" size="icon-sm" aria-label="Open report">
<Maximize2 />
</Button>
)
export function AnalyticsDashboard() {
return (
<div className="flex h-svh overflow-hidden bg-background text-foreground">
<Sidebar className="max-lg:hidden">
<SidebarHeader
logo={<span className="size-8 rounded-lg bg-sidebar-primary-foreground" />}
title="Tomei"
subtitle="Store admin"
/>
<SidebarContent>
<SidebarGroup>
<SidebarItem icon={<Home />}>Home</SidebarItem>
<SidebarItem icon={<ShoppingBag />} count={15}>
Orders
</SidebarItem>
<SidebarItem icon={<Package />}>Products</SidebarItem>
<SidebarItem icon={<Users />}>Customers</SidebarItem>
<SidebarItem icon={<BarChart3 />} active>
Analytics
</SidebarItem>
</SidebarGroup>
<SidebarGroup title="Growth">
<SidebarItem icon={<Megaphone />}>Marketing</SidebarItem>
<SidebarItem icon={<Tag />}>Discounts</SidebarItem>
</SidebarGroup>
<SidebarGroup title="Sales channels">
<SidebarItem icon={<Store />}>Online store</SidebarItem>
<SidebarItem icon={<ShoppingBag />}>Point of sale</SidebarItem>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarItem icon={<BookOpen />} href="#" external>
Documentation
</SidebarItem>
<SidebarItem icon={<Settings />}>Settings</SidebarItem>
</SidebarFooter>
</Sidebar>
<div className="flex min-w-0 flex-1 flex-col">
<header className="flex items-center justify-between gap-4 border-b px-6 py-3">
<Input
placeholder="Search"
leading={<Search />}
trailing={<kbd className="font-sans text-xs">⌘K</kbd>}
className="max-w-md"
/>
<Button variant="ghost" size="icon" aria-label="Notifications">
<Bell />
</Button>
</header>
<main className="flex-1 overflow-y-auto">
<div className="mx-auto flex max-w-[1200px] flex-col gap-4 p-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<h1 className="text-xl font-semibold">Analytics</h1>
<Button>Edit</Button>
</div>
<div className="flex flex-wrap gap-2">
<DateRangePicker disableFuture />
<Button>Compare to previous period</Button>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<LineChartCard
title="Total sales"
value="$250,000"
change={20}
data={totalSales}
formatOptions={currency}
action={expand}
/>
<BarChartCard
title="Net sales"
value="$15,000"
change={5}
data={netSales}
currentLabel="Net sales"
previousLabel="Comparison"
formatOptions={currency}
action={expand}
/>
<DonutChartCard
title="Sessions by device type"
change={8}
data={devices}
action={expand}
/>
<Card title="Sales by location" action={expand}>
<ul className="flex flex-col gap-1.5">
{locations.map((l) => (
<li
key={l.name}
className="flex items-center justify-between rounded-lg bg-muted px-3 py-2 text-sm"
>
<span>{l.name}</span>
<span className="flex items-center gap-2 font-medium tabular-nums">
{l.value}
<Trend value={l.change} className="w-10 justify-end" />
</span>
</li>
))}
</ul>
</Card>
<LineChartCard
title="Online store visitors"
value="84,210"
change={-3}
data={visitors}
action={expand}
/>
<Card title="Gross sales by product" action={expand}>
<ul className="flex flex-col gap-3">
{products.map((p) => (
<li key={p.name} className="flex flex-col gap-1 text-xs">
<span>{p.name}</span>
<span className="flex items-center gap-2">
<span className="flex flex-1 flex-col gap-0.5">
<span
className="h-2 rounded-full bg-chart-1"
style={{ width: `${(p.current / topProduct) * 100}%` }}
/>
<span
className="h-2 rounded-full bg-chart-2"
style={{ width: `${(p.previous / topProduct) * 100}%` }}
/>
</span>
<span className="w-12 text-right font-semibold tabular-nums">
{compact.format(p.current)}
</span>
<Trend value={p.change} className="w-9 justify-end" />
</span>
</li>
))}
</ul>
</Card>
</div>
</div>
</main>
</div>
</div>
)
}const wave = (i: number, base: number, amp: number, freq: number, phase = 0) =>
Math.round(base + amp * Math.sin(i / freq + phase) + amp * 0.25 * Math.cos(i / 1.3))
export const totalSales = Array.from({ length: 30 }, (_, i) => ({
label: `Day ${i + 1}`,
current: wave(i, 5200, 3600, 2.2),
previous: wave(i, 4300, 2400, 3.4, 1),
}))
export const netSales = [
{ label: 'Week 1', current: 8200, previous: 6100 },
{ label: 'Week 2', current: 5600, previous: 4900 },
{ label: 'Week 3', current: 4800, previous: 3900 },
{ label: 'Week 4', current: 3400, previous: 2600 },
{ label: 'Week 5', current: 1900, previous: 1200 },
]
export const devices = [
{ name: 'Mobile', value: 10000, change: 5 },
{ name: 'Desktop', value: 8000, change: 3 },
{ name: 'Tablet', value: 6000, change: 1 },
{ name: 'Other', value: 4000, change: 4 },
{ name: 'TV', value: 1200, change: -2 },
]
export const locations = [
{ name: 'Downtown', value: '$11.6K', change: 20 },
{ name: 'Riverside', value: '$7.5K', change: 14 },
{ name: 'Harbor', value: '$2.1K', change: 20 },
{ name: 'Old Town', value: '$987', change: -4 },
{ name: 'Hillcrest', value: '$261', change: 12 },
]
export const products = [
{ name: 'Wool cap', current: 10000, previous: 8200, change: 8 },
{ name: 'Crewneck', current: 9750, previous: 9100, change: 2 },
{ name: 'Blouse', current: 7500, previous: 6800, change: 4 },
{ name: 'T-shirt', current: 8500, previous: 7300, change: 12 },
{ name: 'Long sleeve', current: 6000, previous: 5600, change: 6 },
]
export const visitors = Array.from({ length: 30 }, (_, i) => ({
label: `Day ${i + 1}`,
current: wave(i, 2600, 1200, 3, 0.5),
previous: wave(i, 2100, 900, 2.6, 2),
}))