Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-tools-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@karnstack/dowel": minor
---

Add Popover, Separator, Avatar, Status, Spinner, Skeleton, and EmptyState with responsive documentation, light and dark themes, reduced-motion handling, and accessibility coverage. Standardize library-owned functional icons on Heroicons Micro and add tone-aware default icons to Callout.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The system theme is the default. Pass `light` or `dark` to pin a theme.

## Components

Alert Dialog · Badge · Button · Callout · Checkbox · Composer · Data Table · Dialog · Icon Button · Input · Kbd · Menu · Native Select · Property Picker · Radio Group · Search Field · Select · Sidebar · Switch · Tabs · Tooltip
Alert Dialog · Avatar · Badge · Button · Callout · Checkbox · Composer · Data Table · Dialog · Empty State · Icon Button · Input · Kbd · Menu · Native Select · Popover · Property Picker · Radio Group · Search Field · Select · Separator · Sidebar · Skeleton · Spinner · Status · Switch · Tabs · Tooltip

Every component is keyboard tested and axe checked. The rendered catalog is contrast tested against WCAG 2.2 AA in light and dark. Visual controls own their appearance. Layout primitives accept application shell hooks.

Expand Down
3 changes: 2 additions & 1 deletion apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
},
"dependencies": {
"@fontsource-variable/host-grotesk": "5.3.0",
"@heroicons/react": "2.2.0",
"@karnstack/dowel": "workspace:*",
"@tanstack/react-hotkeys": "0.10.0",
"@tanstack/react-router": "^1.170.23",
"@tanstack/react-start": "^1.168.40",
"@karnstack/dowel": "workspace:*",
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
Expand Down
14 changes: 14 additions & 0 deletions apps/docs/public/dev-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/docs/src/components/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function CopyButton({ value }: { value: string }) {
size="sm"
onClick={copy}
>
{copied ? <CheckIcon size={14} /> : <CopyIcon size={14} />}
{copied ? <CheckIcon /> : <CopyIcon />}
</IconButton>
}
/>
Expand Down
85 changes: 83 additions & 2 deletions apps/docs/src/components/component-gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Link } from "@tanstack/react-router";
import type { LinkProps } from "@tanstack/react-router";
import {
AlertDialog,
Avatar,
Badge,
Button,
Callout,
Expand All @@ -10,18 +11,24 @@ import {
Composer,
DataTable,
Dialog,
EmptyState,
Field,
IconButton,
Input,
Kbd,
Menu,
NativeSelect,
Popover,
PropertyPicker,
PropertyPill,
RadioGroup,
SearchField,
Separator,
Select,
Sidebar,
Skeleton,
Spinner,
Status,
Switch,
Tabs,
Textarea,
Expand All @@ -30,7 +37,13 @@ import {
} from "@karnstack/dowel";
import type { ReactNode } from "react";

import { ArrowRightIcon, CheckIcon, CopyIcon, MenuIcon } from "./icons";
import {
ArrowRightIcon,
CheckIcon,
CopyIcon,
MenuIcon,
SearchIcon,
} from "./icons";

type GalleryItemProps = {
title: string;
Expand All @@ -45,6 +58,9 @@ const statusOptions = [
{ value: "done", label: "Done", group: "Closed" },
];

const avatarPhoto =
"https://images.unsplash.com/photo-1573497019236-17f8177b81e8?auto=format&crop=faces&fit=crop&h=96&q=80&w=96";

type GalleryIssue = { id: string; title: string; updated: string };
const tableColumn = createDataTableColumnHelper<GalleryIssue>();
const tableColumns = tableColumn.columns([
Expand Down Expand Up @@ -80,7 +96,7 @@ function GalleryItem({
<li className="docs-gallery-item" data-layout={layout}>
<Link className="docs-gallery-link" to={to}>
{title}
<ArrowRightIcon size={14} />
<ArrowRightIcon />
</Link>
<div className="docs-gallery-preview">{children}</div>
</li>
Expand Down Expand Up @@ -116,6 +132,12 @@ export function ComponentGallery() {
</AlertDialog.Root>
</GalleryItem>

<GalleryItem title="Avatar" to="/components/avatar">
<Avatar name="Maya Chen" src={avatarPhoto} status="online" />
<Avatar name="Grace Hopper" status="away" />
<Avatar name="Lin Chen" shape="square" />
</GalleryItem>

<GalleryItem title="Button" to="/components/button">
<Button variant="primary">Create</Button>
<Button>Cancel</Button>
Expand All @@ -138,6 +160,12 @@ export function ComponentGallery() {
<Badge dot>Draft</Badge>
</GalleryItem>

<GalleryItem title="Status" to="/components/status">
<Status tone="success">Passed</Status>
<Status tone="warning">Degraded</Status>
<Status tone="danger">Failed</Status>
</GalleryItem>

<GalleryItem title="Input" to="/components/input" layout="stack">
<Field.Root>
<Field.Label>Project name</Field.Label>
Expand Down Expand Up @@ -278,6 +306,23 @@ export function ComponentGallery() {
</Dialog.Root>
</GalleryItem>

<GalleryItem title="Popover" to="/components/popover">
<Popover.Root>
<Popover.Trigger render={<Button>Repository access</Button>} />
<Popover.Portal>
<Popover.Positioner>
<Popover.Popup>
<Popover.Arrow />
<Popover.Title>Repository access</Popover.Title>
<Popover.Description>
Visible to organization members.
</Popover.Description>
</Popover.Popup>
</Popover.Positioner>
</Popover.Portal>
</Popover.Root>
</GalleryItem>

<GalleryItem title="Icon Button" to="/components/icon-button">
<IconButton label="Copy">
<CopyIcon />
Expand Down Expand Up @@ -312,6 +357,42 @@ export function ComponentGallery() {
<Kbd keys={["Shift", "P"]} />
</GalleryItem>

<GalleryItem
title="Separator"
to="/components/separator"
layout="stack"
>
<Status tone="success">Checks passed</Status>
<Separator />
<Status>Updated two minutes ago</Status>
</GalleryItem>

<GalleryItem title="Spinner" to="/components/spinner">
<Spinner size="sm" />
<Spinner />
<Spinner size="lg" />
</GalleryItem>

<GalleryItem title="Skeleton" to="/components/skeleton" layout="stack">
<Skeleton />
<Skeleton size="sm" />
<Skeleton variant="block" size="sm" />
</GalleryItem>

<GalleryItem
title="Empty State"
to="/components/empty-state"
layout="wide"
>
<EmptyState
size="compact"
icon={<SearchIcon />}
title="No matching repositories"
description="Try a different name or clear the active filters."
actions={<Button size="sm">Clear filters</Button>}
/>
</GalleryItem>

<GalleryItem title="Tabs" to="/components/tabs" layout="stack">
<Tabs.Root defaultValue="assigned">
<Tabs.List aria-label="Issue views">
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/src/components/docs-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export function ComponentGrid() {
<Link className="docs-card" to={item.to}>
<span className="docs-card-title">
{item.title}
<ArrowRightIcon size={14} />
<ArrowRightIcon />
</span>
<span className="docs-card-summary">{item.summary}</span>
</Link>
Expand Down
144 changes: 144 additions & 0 deletions apps/docs/src/components/docs-shell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { Link, useRouterState } from "@tanstack/react-router";
import { IconButton, Sidebar, Tooltip } from "@karnstack/dowel";
import type { ReactNode } from "react";

import { SearchTrigger, ThemeToggle, Wordmark } from "./docs-chrome";
import { useDocs } from "./docs-context";
import { CloseIcon, GitHubIcon, MenuIcon, SearchIcon } from "./icons";
import { SidebarNav } from "./sidebar-nav";
import { nav } from "../lib/nav";
import { versionLabel } from "../lib/version";

const REPO = "https://github.com/karnstack/dowel";

function RepositoryLink() {
return (
<Tooltip.Root>
<Tooltip.Trigger
render={
<IconButton
label="dowel on GitHub"
nativeButton={false}
size="sm"
render={<a href={REPO} target="_blank" rel="noreferrer noopener" />}
>
<GitHubIcon />
</IconButton>
}
/>
<Tooltip.Portal>
<Tooltip.Positioner>
<Tooltip.Popup>GitHub</Tooltip.Popup>
</Tooltip.Positioner>
</Tooltip.Portal>
</Tooltip.Root>
);
}

function WorkspaceTitle() {
const pathname = useRouterState({
select: (state) => state.location.pathname,
});
const current = nav
.flatMap((section) => section.items)
.find((item) => item.to === pathname);

return (
<span className="docs-workspace-title">{current?.title ?? "Dowel"}</span>
);
}

export function DocsShell({ children }: { children: ReactNode }) {
const { navOpen, openSearch, setNavOpen, theme, toggleTheme } = useDocs();

return (
<div className="docs-shell">
<Sidebar.Root defaultWidth={232} minWidth={208} maxWidth={304}>
<Sidebar.Panel aria-label="Documentation navigation">
<Sidebar.Header>
<Link to="/" aria-label="Homepage" className="docs-brand">
<Wordmark />
</Link>
</Sidebar.Header>

<Sidebar.Body>
<SidebarNav />
</Sidebar.Body>
</Sidebar.Panel>

<Sidebar.ResizeHandle aria-label="Resize documentation navigation" />

<Sidebar.Content
style={{
margin: 0,
borderColor: "transparent",
borderRadius: 0,
background: "var(--dowel-bg-1)",
}}
>
<div className="docs-mobile-bar">
<IconButton
label={navOpen ? "Close navigation" : "Open navigation"}
aria-expanded={navOpen}
aria-controls="docs-mobile-nav"
onClick={() => setNavOpen(!navOpen)}
>
{navOpen ? <CloseIcon /> : <MenuIcon />}
</IconButton>

<Link to="/" aria-label="Homepage" className="docs-brand">
<Wordmark />
</Link>

<div className="docs-mobile-actions">
<IconButton label="Search documentation" onClick={openSearch}>
<SearchIcon />
</IconButton>
<ThemeToggle theme={theme} onToggle={toggleTheme} />
</div>
</div>

<div className="docs-mobile-nav" id="docs-mobile-nav">
<SidebarNav onNavigate={() => setNavOpen(false)} />
</div>

<div className="docs-workspace-bar">
<WorkspaceTitle />
<div className="docs-workspace-search">
<SearchTrigger size="sm" onClick={openSearch} />
</div>
</div>

<div className="docs-shell-scroll">
<div className="docs-shell-content">{children}</div>
</div>

<footer className="docs-workspace-footer">
<span className="docs-workspace-credit">
Made by{" "}
<a
href="https://x.com/gyankarn"
target="_blank"
rel="noreferrer noopener"
>
karn
</a>{" "}
and a few AI agents
</span>
<div className="docs-workspace-actions">
<RepositoryLink />
<ThemeToggle theme={theme} size="sm" onToggle={toggleTheme} />
<abbr
className="docs-workspace-a11y"
title="WCAG 2.2 Level AA color contrast"
>
WCAG 2.2 AA
</abbr>
<span className="docs-workspace-version">{versionLabel}</span>
</div>
</footer>
</Sidebar.Content>
</Sidebar.Root>
</div>
);
}
Loading
Loading