dashboard01
A Dashboard block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call dashboard01() with no arguments for a working demo, or edit the code below live.
Implementation notes
TypeScript typecheck clean, all 738 package tests pass (4 new), manual @domphy/doctor diagnose() self-check reports 0 diagnostics. Composed from existing pieces per instructions: sidebar07() reused wholesale for the app shell (aside+sticky header+backdrop), chartBarStacked() reused for the chart canvas, createDomphyTable + table() patch for the data grid. Deliberate simplifications vs. the full upstream spec (documented in the file's header comment): (1) sidebar07's shell has only one nav-main group + one secondary/projects group, so the spec's third 'pinned to bottom' secondary-utility nav group has no slot and is omitted; (2) sidebar07 owns its own sticky header (toggle+divider+breadcrumb title), which has no slot for a trailing action, so the 'ghost external link' button lives in its own thin utility row at the top of the content pane instead of being fused into that same bar; (3) chartBarStacked's props don't expose a header-aside slot, so the 7/30/90-day range toggleGroup/select control sits in a small row above the chartBarStacked card (which itself re-renders with a range-appropriate title/subtitle/trend) rather than nested inside chartBarStacked's own header; (4) the table's 'view tabs' genuinely filter rows by status via table-core's own ColumnFiltering feature (not a placeholder) with relabeled views (All Items/Done/In Progress/Not Started) instead of reproducing the upstream's differently-named tabs; (5) the row drawer's mobile-vs-desktop placement is resolved once via matchMedia at build time (drawer()'s placement prop isn't itself resize-reactive). Fully functional (not stubbed): row selection with indeterminate header checkbox, native HTML5 drag-and-drop reorder with opacity/shadow feedback, inline-editable Target/Limit numeric cells (commit on change, not per-keystroke, to avoid focus loss), reviewer dropdown, status badges, per-row kebab menu (Edit/Duplicate/Favorite/Delete), column-visibility toggling (Customize Columns), Add Section (creates+opens a new row in the drawer), and first/prev/next/last pagination with page-size select. All sample data/copy/numbers are original inventions, not reproductions of the upstream's real content.
Status: complete · Reference: shadcn/ui original
// shadcn/ui "dashboard-01" — clean-room reimplementation.
//
// A full-page admin dashboard shell: a collapsible icon-nav sidebar (reused
// wholesale from sidebar07), a slim content header, four KPI summary cards,
// a range-switchable bar chart (reused from chartBarStacked), and a rich
// drag-reorderable data table (createDomphyTable + the table() patch) with
// its own toolbar, status-filter tabs, column visibility control, inline
// numeric editing, per-row actions menu and a row-editing drawer.
//
// Composition notes (deliberate simplifications vs. the full upstream spec):
// - The sidebar is `sidebar07()` called directly with dashboard-flavored nav
// data, per the instruction to reuse that shell wholesale. sidebar07's own
// shell only exposes ONE nav-main group + ONE secondary (projects) group,
// so the spec's third "secondary utility" group (pinned to the bottom via
// margin-auto) has no slot to render into and is omitted here.
// - sidebar07 owns its own sticky header (toggle + divider + breadcrumb
// title). Since that header has no slot for an extra trailing action, the
// "ghost-styled external link button" lives in its own thin utility row at
// the top of the content pane instead of being fused into the same bar.
// - The chart region reuses the exported `chartBarStacked()` factory as
// directed, rather than the closer-fitting (but not-directed-to-use)
// `chartAreaInteractive`. Since chartBarStacked's own props don't expose a
// header-aside slot, the 7/30/90-day range control sits in a small toolbar
// row directly above the chartBarStacked card (which itself re-renders
// with a range-appropriate title/description/trend) rather than nested
// inside chartBarStacked's own header.
// - The data table's "view tabs" filter the row set by `status` using
// table-core's own column-filtering feature (genuinely functional, not a
// placeholder) rather than reproducing the upstream's differently-labeled,
// mostly-inert tabs.
// - The row drawer's mobile-vs-desktop placement is resolved once at build
// time via `matchMedia` (the `drawer()` patch's `placement` prop is not
// itself reactive to viewport resize).
//
// Implemented purely from the block's public functional/visual spec — no
// upstream shadcn/ui source was viewed or copied. Sample data, copy, and
// numbers are original inventions for this port.
import type { Column, Row } from "@domphy/table";
import type { DomphyElement, ElementNode, Listener } from "@domphy/core";
import { toState } from "@domphy/core";
import {
createColumnHelper,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
} from "@domphy/table";
import { createDomphyTable } from "@domphy/table/domphy";
import {
themeColor,
themeDensity,
themeSpacing,
type ThemeColor,
} from "@domphy/theme";
import {
button,
buttonGhost,
card,
drawer,
empty,
formGroup,
icon,
inputCheckbox,
inputNumber,
inputText,
label,
menu,
paragraph,
popover,
select,
small,
strong,
table as tableUI,
tabs,
toggleGroup,
heading,
} from "@domphy/ui";
import {
CHART_BAR_DAILY_END_DATE,
generateChartBarDailyData,
chartBarTrendIcon,
type ChartBarDailyPoint,
type ChartBarTwoSeriesPoint,
type ChartTrendDirection,
} from "../charts/chart-bar-shared.js";
import { chartBarStacked } from "../charts/chart-bar-stacked.js";
import {
ICON_BAR_CHART,
ICON_CHEVRON_RIGHT,
ICON_FOLDER,
ICON_GRID,
ICON_INBOX,
ICON_MESSAGE,
ICON_MORE,
ICON_PLUS,
ICON_USERS,
sidebarIcon,
} from "../sidebar/sidebar05-08-shared.js";
import {
sidebar07,
type Sidebar07NavMainItem,
type Sidebar07Project,
type Sidebar07User,
} from "../sidebar/sidebar07.js";
// ---------------------------------------------------------------------------
// Icons — hand-authored generic line glyphs, original to this file (24x24,
// stroke=currentColor unless noted). Not sourced from any third-party icon
// set. `ICON_CHEVRON_RIGHT`/`ICON_MORE`/`ICON_PLUS` are reused from the
// sidebar shared module instead of being re-authored here.
// ---------------------------------------------------------------------------
const ICON_EXTERNAL_LINK =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6"/><path d="M10 14L21 3"/></svg>';
const ICON_GRIP =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="none" width="1em" height="1em"><circle cx="9" cy="6" r="1.6"/><circle cx="9" cy="12" r="1.6"/><circle cx="9" cy="18" r="1.6"/><circle cx="15" cy="6" r="1.6"/><circle cx="15" cy="12" r="1.6"/><circle cx="15" cy="18" r="1.6"/></svg>';
const ICON_CHEVRON_DOWN =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M6 9l6 6 6-6"/></svg>';
const ICON_CHECK_CIRCLE =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><circle cx="12" cy="12" r="9"/><path d="M8 12l3 3 5-6"/></svg>';
const ICON_SPINNER_ARC =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="1em" height="1em"><path d="M12 3a9 9 0 1 1-9 9"/></svg>';
const ICON_CIRCLE_DASHED =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" width="1em" height="1em"><circle cx="12" cy="12" r="9" stroke-dasharray="4 3"/></svg>';
const ICON_STAR =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" stroke="none" width="1em" height="1em"><path d="M12 2l2.9 6.6 7.1.7-5.4 4.7 1.6 7-6.2-3.7-6.2 3.7 1.6-7L2 9.3l7.1-.7z"/></svg>';
const ICON_CHEVRON_DOUBLE_RIGHT =
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="1em" height="1em"><path d="M7 6l6 6-6 6"/><path d="M14 6l6 6-6 6"/></svg>';
const DASHBOARD01_SPIN_ANIMATION = "dashboard01-status-spin";
const DASHBOARD01_SPIN_KEYFRAMES = {
"0%": { transform: "rotate(0deg)" },
"100%": { transform: "rotate(360deg)" },
};
/** Wraps a raw inline SVG string in a themed `icon()` box, mirrored on the
* inline axis — reuses a single chevron glyph for both "next" and "prev" /
* "last" and "first" instead of authoring four near-identical SVGs. */
function flippedIcon(svg: string, color: ThemeColor = "neutral"): DomphyElement<"span"> {
return {
span: svg,
ariaHidden: "true",
style: { transform: "scaleX(-1)" },
$: [icon({ color })],
} as unknown as DomphyElement<"span">;
}
// ---------------------------------------------------------------------------
// Sidebar data (fed into the reused `sidebar07()` shell)
// ---------------------------------------------------------------------------
const DEFAULT_NAV_MAIN: Sidebar07NavMainItem[] = [
{ title: "Dashboard", icon: ICON_GRID, href: "#", active: true },
{ title: "Analytics", icon: ICON_BAR_CHART, href: "#" },
{ title: "Team", icon: ICON_USERS, href: "#" },
{ title: "Inbox", icon: ICON_INBOX, href: "#" },
{ title: "Documentation", icon: ICON_FOLDER, href: "#" },
];
const DEFAULT_DOCUMENTS: Sidebar07Project[] = [
{ title: "Document Library", icon: ICON_FOLDER, href: "#" },
{ title: "Report Center", icon: ICON_BAR_CHART, href: "#" },
{ title: "Content Assistant", icon: ICON_MESSAGE, href: "#" },
];
const DEFAULT_USER: Sidebar07User = {
name: "Alex Rivera",
email: "alex@example.com",
};
// ---------------------------------------------------------------------------
// KPI metric cards
// ---------------------------------------------------------------------------
interface MetricCardData {
label: string;
value: string;
badgeDelta: string;
trendDirection: ChartTrendDirection;
footerHeadline: string;
footerSubtext: string;
}
const DEFAULT_METRIC_CARDS: MetricCardData[] = [
{
label: "Active Subscriptions",
value: "8,492",
badgeDelta: "+6.4%",
trendDirection: "up",
footerHeadline: "Subscriber growth is accelerating",
footerSubtext: "Compared to the previous 30 days",
},
{
label: "Monthly Recurring Revenue",
value: "$42,150",
badgeDelta: "+3.1%",
trendDirection: "up",
footerHeadline: "Revenue is trending upward",
footerSubtext: "Includes upgrades and renewals",
},
{
label: "Churn Rate",
value: "2.3%",
badgeDelta: "-0.6%",
trendDirection: "down",
footerHeadline: "Churn improved this quarter",
footerSubtext: "Fewer cancellations than last period",
},
{
label: "Avg. Response Time",
value: "4.2h",
badgeDelta: "-18%",
trendDirection: "down",
footerHeadline: "Faster responses this week",
footerSubtext: "Average first-reply time across tickets",
},
];
/** A single elevated, gradient-tinted KPI card: muted label, huge bold value,
* a top-right trend-delta pill, and a two-line trend footer. */
function metricCard(data: MetricCardData, key: string | number): DomphyElement<"div"> {
const trendColor: ThemeColor = data.trendDirection === "up" ? "success" : "danger";
const trendBadge: DomphyElement<"aside"> = {
aside: [
{
span: [
chartBarTrendIcon(data.trendDirection, trendColor),
{ span: data.badgeDelta } as unknown as DomphyElement,
],
style: {
display: "inline-flex",
alignItems: "center",
gap: themeSpacing(1),
paddingInline: themeSpacing(2),
paddingBlock: themeSpacing(1),
borderRadius: themeSpacing(999),
border: (l: Listener) => `1px solid ${themeColor(l, "shift-4", trendColor)}`,
color: (l: Listener) => themeColor(l, "shift-9", trendColor),
},
} as unknown as DomphyElement,
],
} as unknown as DomphyElement<"aside">;
const content: DomphyElement<"div"> = {
div: [
{ small: data.label, $: [small({ color: "neutral" })] } as unknown as DomphyElement,
{ h2: data.value, $: [heading({ color: "neutral" })] } as unknown as DomphyElement,
],
style: { display: "flex", flexDirection: "column", gap: themeSpacing(1) },
} as unknown as DomphyElement<"div">;
const footer: DomphyElement<"footer"> = {
footer: [
{
div: [
chartBarTrendIcon(data.trendDirection, trendColor),
{ strong: data.footerHeadline, $: [strong({ color: "neutral" })] } as unknown as DomphyElement,
],
style: { display: "flex", alignItems: "center", gap: themeSpacing(1.5) },
} as unknown as DomphyElement,
{ small: data.footerSubtext, $: [small({ color: "neutral" })] } as unknown as DomphyElement,
],
style: { display: "flex", flexDirection: "column", gap: themeSpacing(1) },
} as unknown as DomphyElement<"footer">;
return {
div: [trendBadge, content, footer],
_key: key,
$: [card({ color: "neutral" })],
// `color` is already declared by the card() patch above — the doctor
// tool inspects only this element's own inline `style`, not patch
// contributions, so it can't see that and flags a false positive here.
_doctorDisable: "missing-color",
style: {
backgroundImage: (l: Listener) =>
`linear-gradient(to top, ${themeColor(l, "shift-1", "primary")}, transparent)`,
},
} as unknown as DomphyElement<"div">;
}
function metricCardGrid(cards: MetricCardData[]): DomphyElement<"section"> {
return {
section: cards.map((data, index) => metricCard(data, `${data.label}-${index}`)),
style: {
display: "grid",
gridTemplateColumns: "repeat(1, minmax(0, 1fr))",
gap: themeSpacing(4),
"@media (min-width: 48em)": { gridTemplateColumns: "repeat(2, minmax(0, 1fr))" },
"@media (min-width: 64em)": { gridTemplateColumns: "repeat(4, minmax(0, 1fr))" },
},
} as unknown as DomphyElement<"section">;
}
// ---------------------------------------------------------------------------
// Chart region — range-switchable, reusing chartBarStacked()
// ---------------------------------------------------------------------------
interface ChartRangePreset {
key: string;
label: string;
days: number;
}
const CHART_RANGE_PRESETS: ChartRangePreset[] = [
{ key: "90d", label: "Last 3 months", days: 90 },
{ key: "30d", label: "Last 30 days", days: 30 },
{ key: "7d", label: "Last 7 days", days: 7 },
];
function formatShortMonthDay(dateIso: string): string {
const date = new Date(`${dateIso}T00:00:00Z`);
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
}
function buildRangeSeries(data: ChartBarDailyPoint[], days: number): ChartBarTwoSeriesPoint[] {
return data.slice(-days).map((point) => ({
label: formatShortMonthDay(point.date),
desktop: point.desktop,
mobile: point.mobile,
}));
}
function computeChartTrend(series: ChartBarTwoSeriesPoint[]): { direction: ChartTrendDirection; text: string } {
if (series.length < 2) {
return { direction: "up", text: "Not enough data in this range yet" };
}
const first = series[0].desktop + series[0].mobile;
const last = series[series.length - 1].desktop + series[series.length - 1].mobile;
const deltaPercent = first === 0 ? 0 : ((last - first) / first) * 100;
const direction: ChartTrendDirection = deltaPercent >= 0 ? "up" : "down";
return { direction, text: `Trending ${direction} by ${Math.abs(deltaPercent).toFixed(1)}% in this range` };
}
function chartRegion(data: ChartBarDailyPoint[]): DomphyElement<"div"> {
const rangeKey = toState(CHART_RANGE_PRESETS[0].key);
const controlRow: DomphyElement<"div"> = {
div: [
{ small: "Date range", $: [small({ color: "neutral" })] } as unknown as DomphyElement,
{
div: null,
style: { "@media (max-width: 48em)": { display: "none" } },
$: [
toggleGroup({
items: CHART_RANGE_PRESETS.map((preset) => ({ label: preset.label, key: preset.key })),
value: rangeKey,
}),
],
} as unknown as DomphyElement,
{
select: CHART_RANGE_PRESETS.map((preset) => ({
option: preset.label,
value: preset.key,
_key: preset.key,
})),
value: (l: Listener) => rangeKey.get(l),
ariaLabel: "Select date range",
onChange: (e: Event) => rangeKey.set((e.target as HTMLSelectElement).value),
style: {
display: "none",
"@media (max-width: 48em)": { display: "block" },
},
$: [select()],
} as unknown as DomphyElement,
],
style: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
gap: themeSpacing(3),
flexWrap: "wrap",
},
} as unknown as DomphyElement<"div">;
const chartHost: DomphyElement<"div"> = {
div: (l: Listener) => {
const key = rangeKey.get(l);
const preset = CHART_RANGE_PRESETS.find((candidate) => candidate.key === key) ?? CHART_RANGE_PRESETS[0];
const series = buildRangeSeries(data, preset.days);
const trend = computeChartTrend(series);
return [
chartBarStacked({
data: series,
title: "Visitor Trends",
subtitle: `Desktop & mobile sessions — ${preset.label.toLowerCase()}`,
trendText: trend.text,
trendDirection: trend.direction,
captionText: `Showing total visitors for the ${preset.label.toLowerCase()}`,
}),
];
},
} as unknown as DomphyElement<"div">;
return {
div: [controlRow, chartHost],
style: { display: "flex", flexDirection: "column", gap: themeSpacing(3) },
} as unknown as DomphyElement<"div">;
}
// ---------------------------------------------------------------------------
// Data table region
// ---------------------------------------------------------------------------
type DashboardTableStatus = "Done" | "In Progress" | "Not Started";
interface DashboardTableRow {
id: number;
header: string;
sectionType: string;
status: DashboardTableStatus;
target: number;
limit: number;
reviewer: string;
favorite?: boolean;
}
const REVIEWER_ROSTER = [
"Maria Chen",
"Jonas Weber",
"Priya Nair",
"Liam Turner",
"Sofia Ramos",
"Noah Becker",
"Ava Kim",
"Ethan Brooks",
];
const DEFAULT_TABLE_ROWS: DashboardTableRow[] = [
{ id: 1, header: "Project Brief", sectionType: "Overview", status: "Done", target: 24, limit: 20, reviewer: "Maria Chen" },
{ id: 2, header: "Market Research", sectionType: "Analysis", status: "In Progress", target: 18, limit: 15, reviewer: "Jonas Weber" },
{ id: 3, header: "Budget Forecast", sectionType: "Finance", status: "Done", target: 32, limit: 30, reviewer: "Priya Nair" },
{ id: 4, header: "Risk Assessment", sectionType: "Analysis", status: "Not Started", target: 12, limit: 10, reviewer: "Liam Turner" },
{ id: 5, header: "Design Mockups", sectionType: "Design", status: "In Progress", target: 20, limit: 18, reviewer: "Sofia Ramos" },
{ id: 6, header: "API Specification", sectionType: "Engineering", status: "Done", target: 40, limit: 40, reviewer: "Noah Becker" },
{ id: 7, header: "Marketing Plan", sectionType: "Overview", status: "Not Started", target: 15, limit: 12, reviewer: "Ava Kim" },
{ id: 8, header: "Compliance Review", sectionType: "Finance", status: "In Progress", target: 22, limit: 20, reviewer: "Ethan Brooks" },
];
const STATUS_META: Record<DashboardTableStatus, { svg: string; color: ThemeColor; spin?: boolean }> = {
"Done": { svg: ICON_CHECK_CIRCLE, color: "success" },
"In Progress": { svg: ICON_SPINNER_ARC, color: "info", spin: true },
"Not Started": { svg: ICON_CIRCLE_DASHED, color: "neutral" },
};
function statusBadge(status: DashboardTableStatus): DomphyElement<"span"> {
const meta = STATUS_META[status];
return {
span: [
{
span: meta.svg,
ariaHidden: "true",
style: {
display: "inline-flex",
width: themeSpacing(4),
height: themeSpacing(4),
animation: meta.spin ? `${DASHBOARD01_SPIN_ANIMATION} 1s linear infinite` : undefined,
[`@keyframes ${DASHBOARD01_SPIN_ANIMATION}`]: meta.spin ? DASHBOARD01_SPIN_KEYFRAMES : undefined,
},
$: [icon({ color: meta.color })],
} as unknown as DomphyElement,
{ span: status } as unknown as DomphyElement,
],
style: {
display: "inline-flex",
alignItems: "center",
gap: themeSpacing(1),
paddingInline: themeSpacing(2),
paddingBlock: themeSpacing(0.5),
borderRadius: themeSpacing(999),
border: (l: Listener) => `1px solid ${themeColor(l, "shift-4", meta.color)}`,
color: (l: Listener) => themeColor(l, "shift-9", meta.color),
},
} as unknown as DomphyElement<"span">;
}
function categoryBadge(sectionType: string): DomphyElement<"span"> {
return {
span: sectionType,
style: {
display: "inline-flex",
paddingInline: themeSpacing(2),
paddingBlock: themeSpacing(0.5),
borderRadius: themeSpacing(2),
border: (l: Listener) => `1px solid ${themeColor(l, "shift-4", "neutral")}`,
color: (l: Listener) => themeColor(l, "shift-9", "neutral"),
},
} as unknown as DomphyElement<"span">;
}
/** Builds the full interactive table card: toolbar (status-filter tabs +
* column visibility + add-section), the table itself (checkbox select, drag
* reorder, inline-editable numeric cells, reviewer dropdown, actions menu),
* a pagination footer, and the row-editing drawer. */
function tableRegion(initialRows: DashboardTableRow[]): DomphyElement<"div"> {
const rows: DashboardTableRow[] = initialRows.map((row) => ({ ...row }));
const renderTick = toState(0);
const draggingId = toState<number | null>(null);
const activeStatusFilter = toState<string>("all");
const drawerOpen = toState(false);
const drawerRow = toState<DashboardTableRow | null>(null);
const isNarrowViewport =
typeof window !== "undefined" &&
typeof window.matchMedia === "function" &&
window.matchMedia("(max-width: 40em)").matches;
const columnHelper = createColumnHelper<DashboardTableRow>();
const columns = [
columnHelper.display({ id: "select", header: "Select", enableHiding: false }),
columnHelper.display({ id: "drag", header: "", enableHiding: false }),
columnHelper.accessor("header", { id: "header", header: "Title" }),
columnHelper.accessor("sectionType", { id: "sectionType", header: "Category" }),
columnHelper.accessor("status", { id: "status", header: "Status", filterFn: "equalsString" }),
columnHelper.accessor("target", { id: "target", header: "Target" }),
columnHelper.accessor("limit", { id: "limit", header: "Limit" }),
columnHelper.accessor("reviewer", { id: "reviewer", header: "Reviewer" }),
columnHelper.display({ id: "actions", header: "", enableHiding: false }),
];
const domphyTable = createDomphyTable<DashboardTableRow>({
data: rows,
columns,
getRowId: (row) => String(row.id),
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageIndex: 0, pageSize: 5 } },
});
function commitRows(): void {
domphyTable.table.setOptions((prev) => ({ ...prev, data: [...rows] }));
renderTick.set(renderTick.get() + 1);
}
function openDrawerForRow(row: DashboardTableRow): void {
drawerRow.set(row);
drawerOpen.set(true);
}
function reorderRows(sourceId: number | null, targetId: number): void {
if (sourceId === null || sourceId === targetId) return;
const sourceIndex = rows.findIndex((row) => row.id === sourceId);
const targetIndex = rows.findIndex((row) => row.id === targetId);
if (sourceIndex === -1 || targetIndex === -1) return;
const [moved] = rows.splice(sourceIndex, 1);
rows.splice(targetIndex, 0, moved);
commitRows();
}
function addSection(): void {
const nextId = rows.reduce((max, row) => Math.max(max, row.id), 0) + 1;
const newRow: DashboardTableRow = {
id: nextId,
header: "New section",
sectionType: "Overview",
status: "Not Started",
target: 0,
limit: 0,
reviewer: REVIEWER_ROSTER[0],
};
rows.push(newRow);
commitRows();
openDrawerForRow(newRow);
}
function duplicateRow(row: DashboardTableRow): void {
const nextId = rows.reduce((max, current) => Math.max(max, current.id), 0) + 1;
const index = rows.findIndex((candidate) => candidate.id === row.id);
const copy: DashboardTableRow = { ...row, id: nextId, header: `${row.header} (copy)` };
rows.splice(index + 1, 0, copy);
commitRows();
}
function toggleFavorite(row: DashboardTableRow): void {
row.favorite = !row.favorite;
commitRows();
}
function deleteRow(row: DashboardTableRow): void {
const index = rows.findIndex((candidate) => candidate.id === row.id);
if (index === -1) return;
rows.splice(index, 1);
commitRows();
}
function rowActionsContent(row: DashboardTableRow): DomphyElement<"div"> {
return {
div: null,
style: { minWidth: themeSpacing(36) },
$: [
menu({
selectable: false,
items: [
{ label: "Edit", key: "edit", onClick: () => openDrawerForRow(row) },
{ label: "Duplicate", key: "duplicate", onClick: () => duplicateRow(row) },
{ label: row.favorite ? "Unfavorite" : "Favorite", key: "favorite", onClick: () => toggleFavorite(row) },
{ label: "Delete", key: "delete", onClick: () => deleteRow(row) },
],
}),
],
} as unknown as DomphyElement<"div">;
}
function headerCellFor(column: Column<DashboardTableRow, unknown>): DomphyElement<"th"> {
if (column.id === "select") {
return {
th: [
{
input: null,
type: "checkbox",
ariaLabel: "Select all rows",
checked: domphyTable.getIsAllRowsSelected(),
onChange: () => domphyTable.table.toggleAllRowsSelected(),
_onMount: (node: ElementNode) => {
(node.domElement as HTMLInputElement).indeterminate =
domphyTable.getIsSomeRowsSelected() && !domphyTable.getIsAllRowsSelected();
},
_doctorDisable: "missing-color",
$: [inputCheckbox({ accentColor: "primary" })],
} as unknown as DomphyElement,
],
_key: "select",
} as unknown as DomphyElement<"th">;
}
if (column.id === "drag") {
return {
th: [{ span: "Reorder", style: { position: "absolute", width: "1px", height: "1px", overflow: "hidden" } }],
_key: "drag",
} as unknown as DomphyElement<"th">;
}
return {
th: String(column.columnDef.header ?? column.id),
_key: column.id,
} as unknown as DomphyElement<"th">;
}
function bodyCellFor(column: Column<DashboardTableRow, unknown>, row: Row<DashboardTableRow>): DomphyElement<"td"> {
const original = row.original;
switch (column.id) {
case "select":
return {
td: [
{
input: null,
type: "checkbox",
ariaLabel: `Select ${original.header}`,
checked: row.getIsSelected(),
onChange: () => row.toggleSelected(),
_doctorDisable: "missing-color",
$: [inputCheckbox({ accentColor: "primary" })],
} as unknown as DomphyElement,
],
_key: "select",
} as unknown as DomphyElement<"td">;
case "drag":
return {
td: [{ span: ICON_GRIP, ariaHidden: "true", $: [icon({ color: "neutral" })] } as unknown as DomphyElement],
_key: "drag",
style: { cursor: "grab", width: themeSpacing(6) },
} as unknown as DomphyElement<"td">;
case "header":
return {
td: [
{
button: [
...(original.favorite
? [{ span: ICON_STAR, ariaHidden: "true", $: [icon({ color: "warning" })] } as unknown as DomphyElement]
: []),
{ span: original.header } as unknown as DomphyElement,
],
type: "button",
onClick: () => openDrawerForRow(original),
style: { paddingInline: 0 },
$: [buttonGhost({ color: "primary" })],
} as unknown as DomphyElement,
],
_key: "header",
} as unknown as DomphyElement<"td">;
case "sectionType":
return { td: [categoryBadge(original.sectionType)], _key: "sectionType" } as unknown as DomphyElement<"td">;
case "status":
return { td: [statusBadge(original.status)], _key: "status" } as unknown as DomphyElement<"td">;
case "target":
case "limit":
return {
td: [
{
input: null,
type: "number",
value: original[column.id as "target" | "limit"],
ariaLabel: `${column.id === "target" ? "Target" : "Limit"} for ${original.header}`,
onChange: (e: Event) => {
original[column.id as "target" | "limit"] = Number((e.target as HTMLInputElement).value) || 0;
commitRows();
},
style: { width: themeSpacing(20) },
$: [inputNumber({ color: "neutral" })],
} as unknown as DomphyElement,
],
_key: column.id,
style: { textAlign: "right" },
} as unknown as DomphyElement<"td">;
case "reviewer":
return {
td: [
{
select: REVIEWER_ROSTER.map((name) => ({ option: name, value: name, _key: name })),
value: original.reviewer,
ariaLabel: `Reviewer for ${original.header}`,
onChange: (e: Event) => {
original.reviewer = (e.target as HTMLSelectElement).value;
commitRows();
},
$: [select({ color: "neutral" })],
} as unknown as DomphyElement,
],
_key: "reviewer",
} as unknown as DomphyElement<"td">;
case "actions":
return {
td: [
{
button: [{ span: ICON_MORE, ariaHidden: "true", $: [icon({ color: "neutral" })] } as unknown as DomphyElement],
type: "button",
ariaLabel: `${original.header} actions`,
$: [
buttonGhost({ color: "neutral" }),
popover({ placement: "left-start", content: rowActionsContent(original) }),
],
} as unknown as DomphyElement,
],
_key: "actions",
style: { textAlign: "right" },
} as unknown as DomphyElement<"td">;
default:
return { td: null, _key: column.id } as unknown as DomphyElement<"td">;
}
}
function buildRow(row: Row<DashboardTableRow>): DomphyElement<"tr"> {
const original = row.original;
return {
tr: row.getVisibleCells().map((cell) => bodyCellFor(cell.column, row)),
_key: original.id,
draggable: true,
onDragStart: () => draggingId.set(original.id),
onDragOver: (e: Event) => e.preventDefault(),
onDrop: (e: Event) => {
e.preventDefault();
reorderRows(draggingId.get(), original.id);
},
onDragEnd: () => draggingId.set(null),
style: {
opacity: (l: Listener) => (draggingId.get(l) === original.id ? "0.5" : "1"),
boxShadow: (l: Listener) =>
draggingId.get(l) === original.id
? `0 ${themeSpacing(2)} ${themeSpacing(4)} ${themeColor(l, "shift-4", "neutral")}`
: "none",
cursor: "grab",
},
} as unknown as DomphyElement<"tr">;
}
function emptyStateRow(columnCount: number): DomphyElement<"tr"> {
return {
tr: [
{
td: [
{
div: [
{ span: ICON_CIRCLE_DASHED, ariaHidden: "true", $: [icon({ color: "neutral" })] } as unknown as DomphyElement,
{ p: "No rows in this view", $: [paragraph({ color: "neutral" })] } as unknown as DomphyElement,
{ small: "Try a different tab or add a new section.", $: [small({ color: "neutral" })] } as unknown as DomphyElement,
],
$: [empty({ color: "neutral" })],
} as unknown as DomphyElement,
],
colSpan: columnCount,
_key: "empty",
} as unknown as DomphyElement,
],
_key: "empty",
} as unknown as DomphyElement<"tr">;
}
const tableElement: DomphyElement<"table"> = {
table: (l: Listener) => {
domphyTable.version(l);
renderTick.get(l);
const visibleColumns = domphyTable.getVisibleLeafColumns();
const rowsModel = domphyTable.getRowModel().rows;
return [
{
thead: [{ tr: visibleColumns.map((column) => headerCellFor(column)), _key: "header-row" }],
_key: "thead",
} as unknown as DomphyElement,
{
tbody: rowsModel.length > 0 ? rowsModel.map((row) => buildRow(row)) : [emptyStateRow(visibleColumns.length)],
_key: "tbody",
} as unknown as DomphyElement,
];
},
$: [tableUI({ color: "neutral" })],
} as unknown as DomphyElement<"table">;
const tableScroll: DomphyElement<"div"> = {
div: [tableElement],
style: { overflowX: "auto" },
} as unknown as DomphyElement<"div">;
// ── Toolbar: status-filter tabs/select + spacer + column visibility + add ──
const statusViews: { key: string; label: string }[] = [
{ key: "all", label: "All Items" },
{ key: "Done", label: "Done" },
{ key: "In Progress", label: "In Progress" },
{ key: "Not Started", label: "Not Started" },
];
const viewSelector: DomphyElement<"div"> = {
div: [
{
div: null,
style: { "@media (max-width: 40em)": { display: "none" } },
$: [
tabs({
items: statusViews.map((view) => ({
label: view.label,
key: view.key,
content: { div: null, style: { display: "none" } },
})),
activeKey: activeStatusFilter,
}),
],
} as unknown as DomphyElement,
{
select: statusViews.map((view) => ({ option: view.label, value: view.key, _key: view.key })),
value: (l: Listener) => activeStatusFilter.get(l),
ariaLabel: "Select view",
onChange: (e: Event) => activeStatusFilter.set((e.target as HTMLSelectElement).value),
style: { display: "none", "@media (max-width: 40em)": { display: "block" } },
$: [select()],
} as unknown as DomphyElement,
],
style: { flexShrink: "0" },
} as unknown as DomphyElement<"div">;
const customizeColumnsContent: DomphyElement<"div"> = {
div: (l: Listener) => {
domphyTable.version(l);
return domphyTable
.getAllLeafColumns()
.filter((column) => column.getCanHide())
.map((column) => ({
label: [
{
input: null,
type: "checkbox",
checked: column.getIsVisible(),
onChange: () => column.toggleVisibility(),
_doctorDisable: "missing-color",
$: [inputCheckbox({ color: "neutral" })],
} as unknown as DomphyElement,
{ span: String(column.columnDef.header ?? column.id) } as unknown as DomphyElement,
],
_key: column.id,
style: {
display: "flex",
alignItems: "center",
gap: themeSpacing(2),
paddingBlock: themeSpacing(1),
paddingInline: themeSpacing(2),
cursor: "pointer",
},
}));
},
style: { minWidth: themeSpacing(48) },
} as unknown as DomphyElement<"div">;
const toolbar: DomphyElement<"div"> = {
div: [
viewSelector,
{ div: null, style: { flex: "1" } } as unknown as DomphyElement,
{
button: [{ span: "Customize Columns" }, sidebarIcon(ICON_CHEVRON_DOWN)],
type: "button",
$: [buttonGhost({ color: "neutral" }), popover({ placement: "bottom-end", content: customizeColumnsContent })],
} as unknown as DomphyElement,
{
button: [sidebarIcon(ICON_PLUS), { span: "Add Section" }],
type: "button",
onClick: () => addSection(),
$: [button({ color: "primary" })],
} as unknown as DomphyElement,
],
style: { display: "flex", alignItems: "center", gap: themeSpacing(3), flexWrap: "wrap" },
_onMount: (node: ElementNode) => {
const release = activeStatusFilter.addListener((status) => {
domphyTable.table.getColumn("status")?.setFilterValue(status === "all" ? undefined : status);
});
node.addHook("Remove", release);
},
} as unknown as DomphyElement<"div">;
// ── Pagination footer ──
function rowsPerPageControl(): DomphyElement<"div"> {
return {
div: [
{ small: "Rows per page", $: [small({ color: "neutral" })] } as unknown as DomphyElement,
{
select: [5, 10, 20, 30].map((size) => ({ option: String(size), value: String(size), _key: size })),
value: String(domphyTable.table.getState().pagination.pageSize),
ariaLabel: "Rows per page",
onChange: (e: Event) => domphyTable.table.setPageSize(Number((e.target as HTMLSelectElement).value)),
$: [select()],
} as unknown as DomphyElement,
],
_key: "pageSize",
style: { display: "flex", alignItems: "center", gap: themeSpacing(2) },
} as unknown as DomphyElement<"div">;
}
function pageNavButtons(): DomphyElement<"div"> {
const canPrevious = domphyTable.getCanPreviousPage();
const canNext = domphyTable.getCanNextPage();
const navButton = (
label: string,
iconElement: DomphyElement<"span">,
onClick: () => void,
disabled: boolean,
key: string,
): DomphyElement<"button"> =>
({
button: [iconElement],
type: "button",
ariaLabel: label,
disabled,
onClick,
_key: key,
$: [buttonGhost({ color: "neutral" })],
}) as unknown as DomphyElement<"button">;
return {
div: [
navButton("First page", flippedIcon(ICON_CHEVRON_DOUBLE_RIGHT), () => domphyTable.table.firstPage(), !canPrevious, "first"),
navButton("Previous page", flippedIcon(ICON_CHEVRON_RIGHT), () => domphyTable.table.previousPage(), !canPrevious, "prev"),
navButton("Next page", sidebarIcon(ICON_CHEVRON_RIGHT), () => domphyTable.table.nextPage(), !canNext, "next"),
navButton("Last page", sidebarIcon(ICON_CHEVRON_DOUBLE_RIGHT), () => domphyTable.table.lastPage(), !canNext, "last"),
],
_key: "nav",
style: { display: "flex", alignItems: "center", gap: themeSpacing(1) },
} as unknown as DomphyElement<"div">;
}
const paginationFooter: DomphyElement<"footer"> = {
footer: (l: Listener) => {
domphyTable.version(l);
const pageIndex = domphyTable.table.getState().pagination.pageIndex;
const pageCount = Math.max(1, domphyTable.getPageCount());
const selectedCount = domphyTable.getSelectedRowModel().rows.length;
const totalCount = domphyTable.getRowModel().rows.length;
return [
{
small: `${selectedCount} of ${totalCount} row(s) selected.`,
_key: "selection",
$: [small({ color: "neutral" })],
} as unknown as DomphyElement,
{ div: null, _key: "spacer", style: { flex: "1" } } as unknown as DomphyElement,
rowsPerPageControl(),
{
small: `Page ${pageIndex + 1} of ${pageCount}`,
_key: "pageIndicator",
$: [small({ color: "neutral" })],
} as unknown as DomphyElement,
pageNavButtons(),
];
},
style: {
display: "flex",
alignItems: "center",
gap: themeSpacing(4),
flexWrap: "wrap",
paddingBlockStart: themeSpacing(4),
borderTop: (l: Listener) => `1px solid ${themeColor(l, "shift-3", "neutral")}`,
color: (l: Listener) => themeColor(l, "shift-10", "neutral"),
},
} as unknown as DomphyElement<"footer">;
// ── Row-editing drawer ──
function drawerBarRow(label: string, value: number, max: number, color: ThemeColor, key: string): DomphyElement<"div"> {
const percent = Math.round((value / max) * 100);
return {
div: [
{ small: `${label} — ${value}`, $: [small({ color: "neutral" })] } as unknown as DomphyElement,
{
div: [
{
div: null,
style: {
width: `${percent}%`,
height: "100%",
borderRadius: themeSpacing(2),
backgroundColor: (l: Listener) => themeColor(l, "shift-8", color),
},
} as unknown as DomphyElement,
],
style: {
width: "100%",
height: themeSpacing(3),
borderRadius: themeSpacing(2),
overflow: "hidden",
backgroundColor: (l: Listener) => themeColor(l, "shift-2", "neutral"),
},
} as unknown as DomphyElement,
],
_key: key,
style: { display: "flex", flexDirection: "column", gap: themeSpacing(1) },
} as unknown as DomphyElement<"div">;
}
function drawerChart(row: DashboardTableRow): DomphyElement<"div"> {
const max = Math.max(row.target, row.limit, 1);
return {
div: [
drawerBarRow("Target", row.target, max, "primary", "target-bar"),
drawerBarRow("Limit", row.limit, max, "secondary", "limit-bar"),
],
_key: "chart",
style: { display: "flex", flexDirection: "column", gap: themeSpacing(3) },
} as unknown as DomphyElement<"div">;
}
function drawerHeader(row: DashboardTableRow): DomphyElement<"header"> {
return {
header: [
{ h3: row.header, $: [heading()] } as unknown as DomphyElement,
{ p: `${row.sectionType} · ${row.status}`, $: [paragraph({ color: "neutral" })] } as unknown as DomphyElement,
],
_key: "header",
style: { display: "flex", flexDirection: "column", gap: themeSpacing(1) },
} as unknown as DomphyElement<"header">;
}
function drawerForm(row: DashboardTableRow): DomphyElement<"fieldset"> {
const fieldId = (name: string) => `dashboard01-drawer-${name}-${row.id}`;
return {
fieldset: [
{ legend: "Section details" },
{ label: "Title", for: fieldId("title"), $: [label()] },
{
input: null,
id: fieldId("title"),
value: row.header,
onChange: (e: Event) => {
row.header = (e.target as HTMLInputElement).value;
commitRows();
},
$: [inputText()],
} as unknown as DomphyElement,
{ label: "Reviewer", for: fieldId("reviewer"), $: [label()] },
{
select: REVIEWER_ROSTER.map((name) => ({ option: name, value: name, _key: name })),
id: fieldId("reviewer"),
value: row.reviewer,
onChange: (e: Event) => {
row.reviewer = (e.target as HTMLSelectElement).value;
commitRows();
},
$: [select()],
} as unknown as DomphyElement,
{ label: "Status", for: fieldId("status"), $: [label()] },
{
select: (Object.keys(STATUS_META) as DashboardTableStatus[]).map((status) => ({
option: status,
value: status,
_key: status,
})),
id: fieldId("status"),
value: row.status,
onChange: (e: Event) => {
row.status = (e.target as HTMLSelectElement).value as DashboardTableStatus;
commitRows();
},
$: [select()],
} as unknown as DomphyElement,
{ label: "Target", for: fieldId("target"), $: [label()] },
{
input: null,
type: "number",
id: fieldId("target"),
value: row.target,
onChange: (e: Event) => {
row.target = Number((e.target as HTMLInputElement).value) || 0;
commitRows();
},
$: [inputNumber()],
} as unknown as DomphyElement,
{ label: "Limit", for: fieldId("limit"), $: [label()] },
{
input: null,
type: "number",
id: fieldId("limit"),
value: row.limit,
onChange: (e: Event) => {
row.limit = Number((e.target as HTMLInputElement).value) || 0;
commitRows();
},
$: [inputNumber()],
} as unknown as DomphyElement,
],
_key: "form",
$: [formGroup({ layout: "vertical" })],
} as unknown as DomphyElement<"fieldset">;
}
function drawerFooter(): DomphyElement<"footer"> {
return {
footer: [
{
button: "Done",
type: "button",
onClick: () => drawerOpen.set(false),
$: [button({ color: "primary" })],
} as unknown as DomphyElement,
],
_key: "footer",
style: { display: "flex", justifyContent: "flex-end", marginBlockStart: themeSpacing(4) },
} as unknown as DomphyElement<"footer">;
}
const rowDrawer: DomphyElement<"dialog"> = {
dialog: (l: Listener) => {
const row = drawerRow.get(l);
if (!row) return [];
return [drawerHeader(row), drawerChart(row), drawerForm(row), drawerFooter()];
},
$: [drawer({ open: drawerOpen, placement: isNarrowViewport ? "bottom" : "end" })],
} as unknown as DomphyElement<"dialog">;
return {
div: [toolbar, tableScroll, paginationFooter, rowDrawer],
style: {
display: "flex",
flexDirection: "column",
gap: themeSpacing(4),
borderRadius: (l: Listener) => themeSpacing(themeDensity(l) * 2),
border: (l: Listener) => `1px solid ${themeColor(l, "shift-4", "neutral")}`,
backgroundColor: (l: Listener) => themeColor(l, "inherit", "neutral"),
color: (l: Listener) => themeColor(l, "shift-10", "neutral"),
padding: (l: Listener) => themeSpacing(themeDensity(l) * 4),
},
} as unknown as DomphyElement<"div">;
}
// ---------------------------------------------------------------------------
// Content-pane utility header row (external link ghost button)
// ---------------------------------------------------------------------------
function dashboardUtilityRow(): DomphyElement<"div"> {
return {
div: [
{
button: [{ span: "GitHub" }, sidebarIcon(ICON_EXTERNAL_LINK)],
type: "button",
ariaLabel: "Open project source",
$: [buttonGhost({ color: "neutral" })],
} as unknown as DomphyElement,
],
style: {
display: "flex",
justifyContent: "flex-end",
"@media (max-width: 48em)": { display: "none" },
},
} as unknown as DomphyElement<"div">;
}
// ---------------------------------------------------------------------------
// Public factory
// ---------------------------------------------------------------------------
interface Dashboard01Props {
pageTitle?: string;
user?: Sidebar07User;
navMain?: Sidebar07NavMainItem[];
documents?: Sidebar07Project[];
metricCards?: MetricCardData[];
chartData?: ChartBarDailyPoint[];
tableRows?: DashboardTableRow[];
}
/**
* shadcn/ui "dashboard-01" — a full admin dashboard shell: the icon-nav
* sidebar (reused from `sidebar07()`), a slim content header, four KPI
* cards, a range-switchable chart (reused from `chartBarStacked()`), and a
* drag-reorderable data table with its own toolbar and row-editing drawer.
* Call with no arguments for a fully working demo.
*/
function dashboard01(props: Dashboard01Props = {}): DomphyElement<"div"> {
const {
pageTitle = "Documents",
user = DEFAULT_USER,
navMain = DEFAULT_NAV_MAIN,
documents = DEFAULT_DOCUMENTS,
metricCards = DEFAULT_METRIC_CARDS,
chartData = generateChartBarDailyData(90, CHART_BAR_DAILY_END_DATE),
tableRows = DEFAULT_TABLE_ROWS,
} = props;
const content: DomphyElement[] = [
dashboardUtilityRow(),
metricCardGrid(metricCards),
chartRegion(chartData),
tableRegion(tableRows),
];
return sidebar07({
teams: [{ name: "Acme Inc.", plan: "Workspace" }],
navMain,
projects: documents,
user,
breadcrumbItems: [{ label: pageTitle }],
children: content,
});
}
export { dashboard01 };
export type { Dashboard01Props, DashboardTableRow, DashboardTableStatus, MetricCardData };