diff --git a/.changeset/bright-tools-arrive.md b/.changeset/bright-tools-arrive.md new file mode 100644 index 0000000..358d235 --- /dev/null +++ b/.changeset/bright-tools-arrive.md @@ -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. diff --git a/README.md b/README.md index 72fd535..36803c6 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/apps/docs/package.json b/apps/docs/package.json index 6a7008b..15e1cea 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -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" }, diff --git a/apps/docs/public/dev-icon.svg b/apps/docs/public/dev-icon.svg new file mode 100644 index 0000000..c3c8315 --- /dev/null +++ b/apps/docs/public/dev-icon.svg @@ -0,0 +1,14 @@ + + dowel local development + + + + diff --git a/apps/docs/src/components/code-block.tsx b/apps/docs/src/components/code-block.tsx index 3312f01..5a92629 100644 --- a/apps/docs/src/components/code-block.tsx +++ b/apps/docs/src/components/code-block.tsx @@ -37,7 +37,7 @@ function CopyButton({ value }: { value: string }) { size="sm" onClick={copy} > - {copied ? : } + {copied ? : } } /> diff --git a/apps/docs/src/components/component-gallery.tsx b/apps/docs/src/components/component-gallery.tsx index 8d34837..89eb7f9 100644 --- a/apps/docs/src/components/component-gallery.tsx +++ b/apps/docs/src/components/component-gallery.tsx @@ -2,6 +2,7 @@ import { Link } from "@tanstack/react-router"; import type { LinkProps } from "@tanstack/react-router"; import { AlertDialog, + Avatar, Badge, Button, Callout, @@ -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, @@ -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; @@ -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(); const tableColumns = tableColumn.columns([ @@ -80,7 +96,7 @@ function GalleryItem({
  • {title} - +
    {children}
  • @@ -116,6 +132,12 @@ export function ComponentGallery() { + + + + + + @@ -138,6 +160,12 @@ export function ComponentGallery() { Draft + + Passed + Degraded + Failed + + Project name @@ -278,6 +306,23 @@ export function ComponentGallery() { + + + Repository access} /> + + + + + Repository access + + Visible to organization members. + + + + + + + @@ -312,6 +357,42 @@ export function ComponentGallery() { + + Checks passed + + Updated two minutes ago + + + + + + + + + + + + + + + + } + title="No matching repositories" + description="Try a different name or clear the active filters." + actions={} + /> + + diff --git a/apps/docs/src/components/docs-page.tsx b/apps/docs/src/components/docs-page.tsx index 43e8d71..9005cfd 100644 --- a/apps/docs/src/components/docs-page.tsx +++ b/apps/docs/src/components/docs-page.tsx @@ -174,7 +174,7 @@ export function ComponentGrid() { {item.title} - + {item.summary} diff --git a/apps/docs/src/components/docs-shell.tsx b/apps/docs/src/components/docs-shell.tsx new file mode 100644 index 0000000..c100247 --- /dev/null +++ b/apps/docs/src/components/docs-shell.tsx @@ -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 ( + + } + > + + + } + /> + + + GitHub + + + + ); +} + +function WorkspaceTitle() { + const pathname = useRouterState({ + select: (state) => state.location.pathname, + }); + const current = nav + .flatMap((section) => section.items) + .find((item) => item.to === pathname); + + return ( + {current?.title ?? "Dowel"} + ); +} + +export function DocsShell({ children }: { children: ReactNode }) { + const { navOpen, openSearch, setNavOpen, theme, toggleTheme } = useDocs(); + + return ( +
    + + + + + + + + + + + + + + + + +
    + setNavOpen(!navOpen)} + > + {navOpen ? : } + + + + + + +
    + + + + +
    +
    + +
    + setNavOpen(false)} /> +
    + +
    + +
    + +
    +
    + +
    +
    {children}
    +
    + +
    + + Made by{" "} + + karn + {" "} + and a few AI agents + +
    + + + + WCAG 2.2 AA + + {versionLabel} +
    +
    +
    +
    +
    + ); +} diff --git a/apps/docs/src/components/icons.tsx b/apps/docs/src/components/icons.tsx index 9cb8569..2629dfc 100644 --- a/apps/docs/src/components/icons.tsx +++ b/apps/docs/src/components/icons.tsx @@ -1,95 +1,37 @@ -/** - * The docs' own icon set. dowel ships components, not icons, and the docs - * deliberately take no icon dependency — so these are hand-authored at a - * single 16px grid with one stroke weight, which is what keeps them looking - * like one set rather than a pile of clip art. - * - * All of them inherit `currentColor` and carry `aria-hidden`: every icon here - * sits inside a control that already has a text label or an IconButton - * `label`, so none of them is ever the accessible name. - */ - -type IconProps = { size?: number }; - -const stroke = { - fill: "none", - stroke: "currentColor", - strokeWidth: 1.25, - strokeLinecap: "round", - strokeLinejoin: "round", -} as const; - -function Svg({ - size = 16, - children, -}: IconProps & { children: React.ReactNode }) { - return ( - - ); +import { + ArrowRightIcon as HeroArrowRightIcon, + Bars3Icon, + CheckIcon as HeroCheckIcon, + MagnifyingGlassIcon, + MoonIcon as HeroMoonIcon, + Square2StackIcon, + SunIcon as HeroSunIcon, + XMarkIcon, +} from "@heroicons/react/16/solid"; +import type { ComponentType, SVGProps } from "react"; + +type HeroIcon = ComponentType>; + +/** Keep every general UI glyph on Heroicons' native 16px Micro grid. */ +function docsIcon(Icon: HeroIcon) { + return function DocsIcon() { + return ( +