sidebar08
A Sidebar block/component from shadcn/ui — clean-room reimplemented for Domphy (see methodology). Call sidebar08() with no arguments for a working demo, or edit the code below live.
Implementation notes
Reuses sidebar07's team-switcher/nav-main/projects/user-footer rendering (via sidebar05-08-shared.ts) and adds a de-emphasized secondary-nav block (Support/Feedback, no active-state styling, same collapse+tooltip pattern) plus a rounded/shadowed 'inset' main-content card (own dataTone='shift-0' lighter surface vs the root's dataTone='shift-2' muted backdrop) whose margin shrinks in step with the sidebar's collapse transition. Secondary-nav text intentionally stays at the same shift-9 accessibility floor as primary nav (doctor's low-contrast rule) — de-emphasis is instead conveyed via the small() patch's smaller type scale rather than a dimmer color, a deliberate deviation from a literal 'quieter color' reading of the spec in favor of WCAG legibility. Same mobile-overlay-via-CSS-transform caveat as sidebar07. Doctor self-check: 0 issues.
Status: partial · Reference: shadcn/ui original
// shadcn/ui "sidebar-08" — clean-room reimplementation from the public behavior
// description only (no upstream source viewed). Same full-featured sidebar as
// sidebar07 (team switcher, nested nav-main, projects) plus a de-emphasized
// secondary nav block near the bottom, and a main content area rendered as a
// rounded, shadowed "inset" card offset from the sidebar and viewport edges —
// the inset gap shrinks in step with the sidebar's own collapse transition.
import type { DomphyElement, ElementNode, Listener, ReadableState } from "@domphy/core";
import { toState } from "@domphy/core";
import { small, tooltip } from "@domphy/ui";
import { themeColor, themeDensity, themeSpacing } from "@domphy/theme";
import {
ICON_BAR_CHART,
ICON_FOLDER,
ICON_GRID,
ICON_INBOX,
ICON_LIFEBUOY,
ICON_MESSAGE,
renderExpandableNavRow,
renderPlainNavRow,
renderProjectRow,
renderTeamSwitcher,
renderUserFooter,
sidebarBackdrop,
sidebarIcon,
sidebarMainContent,
sidebarStickyHeader,
type SidebarBreadcrumbItem,
type SidebarNavMainItem,
type SidebarProject,
type SidebarTeam,
type SidebarUser,
} from "./sidebar05-08-shared.js";
/** A quiet utility link (support/feedback) — no active-state styling, always
* visible (collapses to just its icon like the other rows). */
type Sidebar08SecondaryNavItem = { title: string; href?: string; icon?: string };
type Sidebar08Props = {
teams?: SidebarTeam[];
navMain?: SidebarNavMainItem[];
projects?: SidebarProject[];
secondaryNav?: Sidebar08SecondaryNavItem[];
user?: SidebarUser;
breadcrumbItems?: SidebarBreadcrumbItem[];
children?: DomphyElement | DomphyElement[];
};
const DEFAULT_TEAMS: SidebarTeam[] = [
{ name: "Acme Inc", plan: "Enterprise" },
{ name: "Acme Corp", plan: "Startup" },
];
const DEFAULT_NAV_MAIN: SidebarNavMainItem[] = [
{
title: "Playground",
icon: ICON_GRID,
items: [
{ title: "History" },
{ title: "Starred", active: true },
{ title: "Settings" },
],
},
{
title: "Models",
icon: ICON_INBOX,
items: [{ title: "Genesis" }, { title: "Explorer" }, { title: "Quantum" }],
},
{ title: "Documentation", icon: ICON_BAR_CHART, href: "#" },
];
const DEFAULT_PROJECTS: SidebarProject[] = [
{ title: "Design Engineering", icon: ICON_FOLDER, href: "#" },
{ title: "Sales & Marketing", icon: ICON_FOLDER, href: "#" },
{ title: "Travel", icon: ICON_FOLDER, href: "#" },
];
const DEFAULT_SECONDARY_NAV: Sidebar08SecondaryNavItem[] = [
{ title: "Support", icon: ICON_LIFEBUOY, href: "#" },
{ title: "Feedback", icon: ICON_MESSAGE, href: "#" },
];
const DEFAULT_USER: SidebarUser = {
name: "Shad Cn",
email: "shadcn@example.com",
};
/** A de-emphasized secondary-nav row: quieter color than nav-main, no active
* state, same expanded/collapsed dual-row + collapsed-only-tooltip pattern. */
function renderSecondaryNavRow(
item: Sidebar08SecondaryNavItem,
collapsed: ReadableState<boolean>,
): DomphyElement<"li"> {
// Contrast stays at the same shift-9 floor as every other row (WCAG
// legibility is never traded away); de-emphasis instead comes from a
// smaller type scale (small() patch) rather than a dimmer color.
const rowStyle = {
display: "flex",
alignItems: "center",
width: "100%",
gap: (l: Listener) => themeSpacing(themeDensity(l) * 2),
paddingBlock: (l: Listener) => themeSpacing(themeDensity(l) * 1.5),
paddingInline: (l: Listener) => themeSpacing(themeDensity(l) * 3),
borderRadius: (l: Listener) => themeSpacing(themeDensity(l) * 1),
textDecoration: () => "none",
overflow: "hidden",
whiteSpace: "nowrap",
color: (l: Listener) => themeColor(l, "shift-9", "neutral"),
backgroundColor: (l: Listener) => themeColor(l, "inherit", "neutral"),
"&:hover": { backgroundColor: (l: Listener) => themeColor(l, "shift-2", "neutral") },
};
return {
li: [
{
a: [
...(item.icon ? [sidebarIcon(item.icon)] : []),
{
small: item.title,
style: { flex: "1", textAlign: "left" },
$: [small({ color: "neutral" })],
} as unknown as DomphyElement,
],
href: item.href ?? "#",
style: { ...rowStyle, display: (l: Listener) => (collapsed.get(l) ? "none" : "flex") },
} as unknown as DomphyElement,
{
a: [item.icon ? sidebarIcon(item.icon) : { span: item.title[0] }],
href: item.href ?? "#",
ariaLabel: item.title,
style: {
...rowStyle,
justifyContent: "center",
display: (l: Listener) => (collapsed.get(l) ? "flex" : "none"),
},
$: [tooltip({ content: item.title, placement: "right" })],
} as unknown as DomphyElement,
],
_key: item.title,
} as DomphyElement<"li">;
}
/**
* shadcn/ui "sidebar-08" — sidebar07's full feature set plus a de-emphasized
* secondary nav block and a rounded/shadowed "inset" main content card whose
* offset from the sidebar tracks the collapse transition. Call with no
* arguments for a fully working demo.
*/
function sidebar08(props: Sidebar08Props = {}): DomphyElement<"div"> {
const {
teams = DEFAULT_TEAMS,
navMain = DEFAULT_NAV_MAIN,
projects = DEFAULT_PROJECTS,
secondaryNav = DEFAULT_SECONDARY_NAV,
user = DEFAULT_USER,
breadcrumbItems = [{ label: "Models" }, { label: "Genesis" }],
children,
} = props;
const sidebarOpen = toState(true);
const collapsed = toState(false);
const navMainRows = navMain.map((item) =>
item.items && item.items.length > 0
? renderExpandableNavRow(item, collapsed)
: renderPlainNavRow(item, collapsed),
);
const asideElement: DomphyElement<"aside"> = {
aside: [
renderTeamSwitcher(teams),
{
nav: [
{
ul: navMainRows,
style: {
listStyle: "none",
margin: "0",
padding: "0",
display: "flex",
flexDirection: "column",
gap: themeSpacing(0.5),
},
} as unknown as DomphyElement,
{
div: [
{
small: "Projects",
style: {
display: (l: Listener) => (collapsed.get(l) ? "none" : "block"),
paddingInline: themeSpacing(3),
},
$: [small({ color: "neutral" })],
} as unknown as DomphyElement,
{
ul: projects.map((project) => renderProjectRow(project, collapsed)),
style: {
listStyle: "none",
margin: "0",
padding: "0",
display: "flex",
flexDirection: "column",
gap: themeSpacing(0.5),
},
} as unknown as DomphyElement,
],
style: { display: "flex", flexDirection: "column", gap: themeSpacing(1), marginTop: themeSpacing(4) },
} as unknown as DomphyElement,
],
style: {
flex: "1",
minHeight: "0",
overflowY: "auto",
overflowX: "hidden",
paddingInline: (l: Listener) => themeSpacing(themeDensity(l) * 3),
},
} as unknown as DomphyElement,
// Secondary nav — quiet utility links, no accordion/dropdown, no
// active-state highlighting, always visible above the user footer.
{
ul: secondaryNav.map((item) => renderSecondaryNavRow(item, collapsed)),
style: {
listStyle: "none",
margin: "0",
padding: (l: Listener) => `0 ${themeSpacing(themeDensity(l) * 3)}`,
display: "flex",
flexDirection: "column",
gap: themeSpacing(0.5),
flexShrink: "0",
},
} as unknown as DomphyElement,
renderUserFooter(user),
// Thin invisible edge rail — also acts as a click target to toggle collapse.
{
div: null,
ariaHidden: "true",
onClick: () => collapsed.set(!collapsed.get()),
style: {
position: "absolute",
insetBlock: "0",
insetInlineEnd: "0",
width: themeSpacing(1),
cursor: "col-resize",
},
} as unknown as DomphyElement,
],
_onMount: (node: ElementNode) => {
const onKeyDown = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "b") {
event.preventDefault();
collapsed.set(!collapsed.get());
}
};
window.addEventListener("keydown", onKeyDown);
node.addHook("Remove", () => window.removeEventListener("keydown", onKeyDown));
},
style: {
position: "relative",
display: "flex",
flexDirection: "column",
flexShrink: "0",
width: (l: Listener) => (collapsed.get(l) ? themeSpacing(12) : themeSpacing(64)),
overflow: "hidden",
transition: "width 0.2s linear",
color: (l: Listener) => themeColor(l, "shift-9", "neutral"),
backgroundColor: (l: Listener) => themeColor(l, "inherit", "neutral"),
"@media (max-width: 768px)": {
position: "fixed",
insetBlock: "0",
insetInlineStart: "0",
zIndex: "15",
width: themeSpacing(72),
transform: (l: Listener) => (sidebarOpen.get(l) ? "translateX(0)" : "translateX(-100%)"),
transition: "transform 0.2s ease",
boxShadow: (l: Listener) => `0 0 ${themeSpacing(6)} ${themeColor(l, "shift-4", "neutral")}`,
},
},
} as unknown as DomphyElement<"aside">;
// The "inset" look: the main panel is its own rounded, shadowed card
// (lighter dataTone than the muted root backdrop) with a margin that
// shrinks — growing the card into the freed space — in step with the
// sidebar's own collapse transition, using the same timing/easing.
const mainElement: DomphyElement<"main"> = {
main: [
sidebarStickyHeader({
onToggle: () => {
sidebarOpen.set(!sidebarOpen.get());
collapsed.set(!collapsed.get());
},
breadcrumbItems,
}),
sidebarMainContent(children),
],
dataTone: "shift-0",
style: {
display: "flex",
flexDirection: "column",
flex: "1",
minWidth: "0",
minHeight: "0",
overflow: "hidden",
margin: (l: Listener) => (collapsed.get(l) ? themeSpacing(2) : themeSpacing(3)),
borderRadius: (l: Listener) => themeSpacing(themeDensity(l) * 3),
boxShadow: (l: Listener) => `0 ${themeSpacing(1)} ${themeSpacing(6)} ${themeColor(l, "shift-4", "neutral")}`,
backgroundColor: (l: Listener) => themeColor(l, "inherit", "neutral"),
color: (l: Listener) => themeColor(l, "shift-9", "neutral"),
transition: "margin 0.2s linear",
},
} as unknown as DomphyElement<"main">;
return {
div: [asideElement, mainElement, sidebarBackdrop(sidebarOpen, () => sidebarOpen.set(false))],
dataTone: "shift-2",
style: {
display: "flex",
height: "100dvh",
overflow: "hidden",
position: "relative",
backgroundColor: (l: Listener) => themeColor(l, "inherit", "neutral"),
color: (l: Listener) => themeColor(l, "shift-9", "neutral"),
},
} as unknown as DomphyElement<"div">;
}
export { sidebar08 };
export type { Sidebar08Props, Sidebar08SecondaryNavItem };
export type {
SidebarNavChild as Sidebar08NavChild,
SidebarNavMainItem as Sidebar08NavMainItem,
SidebarProject as Sidebar08Project,
SidebarTeam as Sidebar08Team,
SidebarUser as Sidebar08User,
} from "./sidebar05-08-shared.js";