PettyUI/docs/superpowers/plans/2026-03-30-kitchen-sink-showcase.md
2026-03-30 12:08:51 +07:00

1902 lines
69 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kitchen Sink Showcase Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a single-page showcase app at `packages/showcase/` that displays every PettyUI component grouped by category with live interactive demos.
**Architecture:** Vite + SolidJS + Tailwind v4 app. Single scrollable page with sticky header + category anchors. Six section files (one per category), each rendering component demos. A shared `ComponentDemo` wrapper provides consistent title/description/demo-area layout.
**Tech Stack:** Vite, vite-plugin-solid, @tailwindcss/vite, SolidJS, PettyUI core (workspace import)
---
## File Structure
```
packages/showcase/
package.json — deps + scripts
tsconfig.json — extends base, paths for pettyui/*
vite.config.ts — solid + tailwind plugins, resolve aliases
index.html — entry HTML
src/
index.tsx — render mount
app.tsx — page shell: sticky header, TOC, section slots
app.css — tailwind import + global styles
component-demo.tsx — shared wrapper: name, description, demo area
sections/
layout-display.tsx — Avatar, Badge, Card, Image, Separator, Skeleton
inputs-basic.tsx — Button, TextField, NumberField, Checkbox, Switch, Toggle
inputs-selection.tsx — RadioGroup, ToggleGroup, Select, Combobox, Listbox, Slider
inputs-advanced.tsx — Form, DatePicker
navigation.tsx — Link, Breadcrumbs, Tabs, Accordion, Collapsible, Pagination, NavigationMenu, Wizard
overlays.tsx — Dialog, AlertDialog, Drawer, Popover, Tooltip, HoverCard, DropdownMenu, ContextMenu, CommandPalette
feedback-status.tsx — Alert, Toast, Progress, Meter
data.tsx — Calendar, DataTable, VirtualList
```
The inputs category is split into 3 files to stay under the 500-line file limit.
---
### Task 1: Scaffold the showcase package
**Files:**
- Create: `packages/showcase/package.json`
- Create: `packages/showcase/tsconfig.json`
- Create: `packages/showcase/vite.config.ts`
- Create: `packages/showcase/index.html`
- Create: `packages/showcase/src/app.css`
- Create: `packages/showcase/src/index.tsx`
- [ ] **Step 1: Create package.json**
```json
{
"name": "pettyui-showcase",
"version": "0.0.1",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"pettyui": "workspace:*",
"solid-js": "^1.9.12"
},
"devDependencies": {
"@tailwindcss/vite": "^4.1.3",
"tailwindcss": "^4.1.3",
"vite": "^8.0.3",
"vite-plugin-solid": "^2.11.11"
}
}
```
- [ ] **Step 2: Create tsconfig.json**
```json
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"baseUrl": ".",
"paths": {
"pettyui/*": ["../core/src/components/*/index.ts"]
}
},
"include": ["src"]
}
```
- [ ] **Step 3: Create vite.config.ts**
```ts
import { defineConfig } from "vite";
import solid from "vite-plugin-solid";
import tailwindcss from "@tailwindcss/vite";
import path from "node:path";
export default defineConfig({
plugins: [solid(), tailwindcss()],
resolve: {
conditions: ["solid", "browser", "module", "import"],
alias: {
"pettyui": path.resolve(__dirname, "../core/src/components"),
},
},
});
```
- [ ] **Step 4: Create index.html**
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PettyUI — Kitchen Sink</title>
</head>
<body>
<div id="root"></div>
<script src="/src/index.tsx" type="module"></script>
</body>
</html>
```
- [ ] **Step 5: Create src/app.css**
```css
@import "tailwindcss";
html {
scroll-behavior: smooth;
}
```
- [ ] **Step 6: Create src/index.tsx**
```tsx
import { render } from "solid-js/web";
import { App } from "./app";
import "./app.css";
const root = document.getElementById("root");
if (root) {
render(() => <App />, root);
}
```
- [ ] **Step 7: Install dependencies**
Run: `cd packages/showcase && pnpm install`
Expected: dependencies install, node_modules created
- [ ] **Step 8: Verify dev server starts**
Run: `cd packages/showcase && pnpm dev`
Expected: Vite dev server starts, blank page loads at localhost
- [ ] **Step 9: Commit**
```bash
git add packages/showcase/package.json packages/showcase/tsconfig.json packages/showcase/vite.config.ts packages/showcase/index.html packages/showcase/src/app.css packages/showcase/src/index.tsx
git commit -m "feat(showcase): scaffold Vite + SolidJS + Tailwind v4 app"
```
---
### Task 2: App shell with sticky header and TOC
**Files:**
- Create: `packages/showcase/src/app.tsx`
- Create: `packages/showcase/src/component-demo.tsx`
- [ ] **Step 1: Create component-demo.tsx**
Shared wrapper that renders a component name, description, and bordered demo area.
```tsx
import type { JSX } from "solid-js";
interface ComponentDemoProps {
name: string;
description: string;
children: JSX.Element;
}
/** Renders a titled component demo block with description and bordered demo area. */
export function ComponentDemo(props: ComponentDemoProps) {
return (
<div class="mb-12">
<h3 class="text-xl font-semibold text-gray-900 mb-1">{props.name}</h3>
<p class="text-sm text-gray-500 mb-4">{props.description}</p>
<div class="border border-gray-200 rounded-lg p-6 bg-white">
{props.children}
</div>
</div>
);
}
```
- [ ] **Step 2: Create app.tsx**
Main page layout with sticky header, category navigation, and placeholder section slots.
```tsx
import type { JSX } from "solid-js";
const categories = [
{ id: "layout-display", label: "Layout & Display" },
{ id: "inputs-basic", label: "Inputs: Basic" },
{ id: "inputs-selection", label: "Inputs: Selection" },
{ id: "inputs-advanced", label: "Inputs: Advanced" },
{ id: "navigation", label: "Navigation" },
{ id: "overlays", label: "Overlays" },
{ id: "feedback-status", label: "Feedback & Status" },
{ id: "data", label: "Data" },
] as const;
/** Section wrapper with category heading and anchor target. */
function Section(props: { id: string; title: string; children: JSX.Element }) {
return (
<section id={props.id} class="scroll-mt-20 mb-16">
<div class="border-b border-gray-300 pb-2 mb-8">
<h2 class="text-2xl font-bold text-gray-800">{props.title}</h2>
</div>
{props.children}
</section>
);
}
/** Kitchen sink showcase page with all PettyUI components. */
export function App() {
return (
<div class="min-h-screen bg-gray-50">
<header class="sticky top-0 z-50 bg-white border-b border-gray-200 shadow-sm">
<div class="max-w-5xl mx-auto px-6 py-3 flex items-center justify-between">
<h1 class="text-lg font-bold text-gray-900">
PettyUI <span class="font-normal text-gray-400">Kitchen Sink</span>
</h1>
<nav class="flex gap-3 text-sm">
{categories.map((cat) => (
<a href={`#${cat.id}`} class="text-gray-500 hover:text-gray-900 transition-colors">
{cat.label}
</a>
))}
</nav>
</div>
</header>
<main class="max-w-5xl mx-auto px-6 py-12">
<Section id="layout-display" title="Layout & Display">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="inputs-basic" title="Inputs: Basic">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="inputs-selection" title="Inputs: Selection">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="inputs-advanced" title="Inputs: Advanced">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="navigation" title="Navigation">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="overlays" title="Overlays">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="feedback-status" title="Feedback & Status">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
<Section id="data" title="Data">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
</main>
</div>
);
}
```
- [ ] **Step 3: Verify app shell renders**
Run: `cd packages/showcase && pnpm dev`
Expected: Page shows sticky header with "PettyUI Kitchen Sink", category nav links, and 8 placeholder sections. Clicking a nav link smooth-scrolls to the section.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/app.tsx packages/showcase/src/component-demo.tsx
git commit -m "feat(showcase): add app shell with sticky header and category sections"
```
---
### Task 3: Layout & Display section
**Files:**
- Create: `packages/showcase/src/sections/layout-display.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<LayoutDisplaySection />`
**Component exports used:**
- `Avatar` from `pettyui/avatar` — compound: `Avatar`, `Avatar.Image`, `Avatar.Fallback`
- `Badge` from `pettyui/badge` — simple: `Badge`
- `Card` from `pettyui/card` — compound: `Card`, `Card.Header`, `Card.Title`, `Card.Description`, `Card.Content`, `Card.Footer`
- `Image` from `pettyui/image` — compound: `Image`, `Image.Img`, `Image.Fallback`
- `Separator` from `pettyui/separator` — simple: `Separator`
- `Skeleton` from `pettyui/skeleton` — simple: `Skeleton`
- [ ] **Step 1: Create layout-display.tsx**
```tsx
import { Avatar } from "pettyui/avatar";
import { Badge } from "pettyui/badge";
import { Card } from "pettyui/card";
import { Image } from "pettyui/image";
import { Separator } from "pettyui/separator";
import { Skeleton } from "pettyui/skeleton";
import { ComponentDemo } from "../component-demo";
/** Avatar demo with image and fallback variants. */
function AvatarDemo() {
return (
<div class="flex items-center gap-4">
<Avatar class="w-10 h-10 rounded-full overflow-hidden">
<Avatar.Image
src="https://i.pravatar.cc/80?img=3"
alt="User avatar"
class="w-full h-full object-cover"
/>
<Avatar.Fallback class="w-full h-full bg-indigo-100 text-indigo-700 flex items-center justify-center font-medium">
MB
</Avatar.Fallback>
</Avatar>
<Avatar class="w-10 h-10 rounded-full overflow-hidden">
<Avatar.Image src="/broken-url.jpg" alt="Broken" class="w-full h-full object-cover" />
<Avatar.Fallback class="w-full h-full bg-emerald-100 text-emerald-700 flex items-center justify-center font-medium">
JD
</Avatar.Fallback>
</Avatar>
</div>
);
}
/** Badge demo with multiple variants. */
function BadgeDemo() {
return (
<div class="flex items-center gap-2">
<Badge class="px-2 py-0.5 text-xs font-medium rounded-full bg-gray-100 text-gray-700">Default</Badge>
<Badge class="px-2 py-0.5 text-xs font-medium rounded-full bg-blue-100 text-blue-700">Info</Badge>
<Badge class="px-2 py-0.5 text-xs font-medium rounded-full bg-green-100 text-green-700">Success</Badge>
<Badge class="px-2 py-0.5 text-xs font-medium rounded-full bg-yellow-100 text-yellow-700">Warning</Badge>
<Badge class="px-2 py-0.5 text-xs font-medium rounded-full bg-red-100 text-red-700">Error</Badge>
</div>
);
}
/** Card demo with header, content, and footer. */
function CardDemo() {
return (
<Card class="border border-gray-200 rounded-lg shadow-sm max-w-sm">
<Card.Header class="px-4 pt-4">
<Card.Title class="text-lg font-semibold">Card Title</Card.Title>
<Card.Description class="text-sm text-gray-500">A short description of this card.</Card.Description>
</Card.Header>
<Card.Content class="px-4 py-3">
<p class="text-sm text-gray-700">Card body content goes here. This could be anything.</p>
</Card.Content>
<Card.Footer class="px-4 pb-4 flex justify-end gap-2">
<button type="button" class="px-3 py-1.5 text-sm rounded border border-gray-300 hover:bg-gray-50">Cancel</button>
<button type="button" class="px-3 py-1.5 text-sm rounded bg-indigo-600 text-white hover:bg-indigo-700">Save</button>
</Card.Footer>
</Card>
);
}
/** Image demo with working image and fallback. */
function ImageDemo() {
return (
<div class="flex items-center gap-4">
<Image class="w-24 h-24 rounded-lg overflow-hidden">
<Image.Img
src="https://picsum.photos/seed/pettyui/200"
alt="Sample"
class="w-full h-full object-cover"
/>
<Image.Fallback class="w-full h-full bg-gray-200 flex items-center justify-center text-gray-400 text-xs">
Loading...
</Image.Fallback>
</Image>
<Image class="w-24 h-24 rounded-lg overflow-hidden">
<Image.Img src="/broken.jpg" alt="Broken" class="w-full h-full object-cover" />
<Image.Fallback class="w-full h-full bg-gray-200 flex items-center justify-center text-gray-400 text-xs">
No image
</Image.Fallback>
</Image>
</div>
);
}
/** Separator demo with horizontal and vertical. */
function SeparatorDemo() {
return (
<div>
<p class="text-sm text-gray-700 mb-2">Above the separator</p>
<Separator class="h-px bg-gray-200 my-3" />
<p class="text-sm text-gray-700">Below the separator</p>
</div>
);
}
/** Skeleton demo with different shapes. */
function SkeletonDemo() {
return (
<div class="flex items-center gap-4">
<Skeleton class="w-10 h-10 rounded-full bg-gray-200 animate-pulse" />
<div class="space-y-2">
<Skeleton class="h-4 w-32 rounded bg-gray-200 animate-pulse" />
<Skeleton class="h-3 w-48 rounded bg-gray-200 animate-pulse" />
</div>
</div>
);
}
/** Layout & Display section with all display components. */
export function LayoutDisplaySection() {
return (
<>
<ComponentDemo name="Avatar" description="User profile image with fallback to initials or icon when image fails to load">
<AvatarDemo />
</ComponentDemo>
<ComponentDemo name="Badge" description="Small status indicator label, typically used for counts, tags, or status">
<BadgeDemo />
</ComponentDemo>
<ComponentDemo name="Card" description="Grouped content container with header, body, and footer sections">
<CardDemo />
</ComponentDemo>
<ComponentDemo name="Image" description="Image element with fallback placeholder when the source fails to load">
<ImageDemo />
</ComponentDemo>
<ComponentDemo name="Separator" description="Visual divider between content sections or menu items">
<SeparatorDemo />
</ComponentDemo>
<ComponentDemo name="Skeleton" description="Placeholder loading indicator that mimics the shape of content being loaded">
<SkeletonDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Replace the layout-display placeholder in `app.tsx`:
Add import at top:
```tsx
import { LayoutDisplaySection } from "./sections/layout-display";
```
Replace:
```tsx
<Section id="layout-display" title="Layout & Display">
<p class="text-gray-400 italic">Coming soon...</p>
</Section>
```
With:
```tsx
<Section id="layout-display" title="Layout & Display">
<LayoutDisplaySection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Run: `cd packages/showcase && pnpm dev`
Expected: Layout & Display section shows 6 component demos with titles, descriptions, and interactive content.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/layout-display.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Layout & Display section demos"
```
---
### Task 4: Inputs Basic section
**Files:**
- Create: `packages/showcase/src/sections/inputs-basic.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<InputsBasicSection />`
**Component exports used:**
- `Button` from `pettyui/button` — simple: `Button`
- `TextField` from `pettyui/text-field` — compound: `TextField`, `TextField.Label`, `TextField.Input`, `TextField.Description`, `TextField.ErrorMessage`
- `NumberField` from `pettyui/number-field` — compound: `NumberField`, `NumberField.Label`, `NumberField.Input`, `NumberField.IncrementTrigger`, `NumberField.DecrementTrigger`
- `Checkbox` from `pettyui/checkbox` — simple: `Checkbox`
- `Switch` from `pettyui/switch` — simple: `Switch`
- `Toggle` from `pettyui/toggle` — simple: `Toggle`
- [ ] **Step 1: Create inputs-basic.tsx**
```tsx
import { createSignal } from "solid-js";
import { Button } from "pettyui/button";
import { TextField } from "pettyui/text-field";
import { NumberField } from "pettyui/number-field";
import { Checkbox } from "pettyui/checkbox";
import { Switch } from "pettyui/switch";
import { Toggle } from "pettyui/toggle";
import { ComponentDemo } from "../component-demo";
/** Button demo with multiple variants. */
function ButtonDemo() {
return (
<div class="flex items-center gap-3">
<Button class="px-4 py-2 text-sm font-medium rounded bg-indigo-600 text-white hover:bg-indigo-700">
Primary
</Button>
<Button class="px-4 py-2 text-sm font-medium rounded border border-gray-300 hover:bg-gray-50">
Secondary
</Button>
<Button class="px-4 py-2 text-sm font-medium rounded text-indigo-600 hover:bg-indigo-50">
Ghost
</Button>
<Button disabled class="px-4 py-2 text-sm font-medium rounded bg-gray-100 text-gray-400 cursor-not-allowed">
Disabled
</Button>
</div>
);
}
/** TextField demo with label, description, and error. */
function TextFieldDemo() {
return (
<div class="flex gap-6">
<TextField class="flex flex-col gap-1">
<TextField.Label class="text-sm font-medium text-gray-700">Name</TextField.Label>
<TextField.Input
placeholder="Enter your name"
class="border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<TextField.Description class="text-xs text-gray-400">Your full name</TextField.Description>
</TextField>
<TextField validationState="invalid" class="flex flex-col gap-1">
<TextField.Label class="text-sm font-medium text-red-600">Email</TextField.Label>
<TextField.Input
value="not-an-email"
class="border border-red-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-red-500"
/>
<TextField.ErrorMessage class="text-xs text-red-500">Invalid email address</TextField.ErrorMessage>
</TextField>
</div>
);
}
/** NumberField demo with increment/decrement. */
function NumberFieldDemo() {
return (
<NumberField defaultValue={5} min={0} max={100} class="flex flex-col gap-1">
<NumberField.Label class="text-sm font-medium text-gray-700">Quantity</NumberField.Label>
<div class="flex items-center gap-1">
<NumberField.DecrementTrigger class="w-8 h-8 flex items-center justify-center border border-gray-300 rounded hover:bg-gray-50 text-sm">
-
</NumberField.DecrementTrigger>
<NumberField.Input class="w-16 text-center border border-gray-300 rounded px-2 py-1 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
<NumberField.IncrementTrigger class="w-8 h-8 flex items-center justify-center border border-gray-300 rounded hover:bg-gray-50 text-sm">
+
</NumberField.IncrementTrigger>
</div>
</NumberField>
);
}
/** Checkbox demo with checked and unchecked states. */
function CheckboxDemo() {
return (
<div class="flex flex-col gap-3">
<Checkbox class="flex items-center gap-2 cursor-pointer">
{(state) => (
<>
<div class={`w-4 h-4 border rounded flex items-center justify-center text-xs ${state.checked() ? "bg-indigo-600 border-indigo-600 text-white" : "border-gray-300"}`}>
{state.checked() ? "✓" : ""}
</div>
<span class="text-sm text-gray-700">Accept terms and conditions</span>
</>
)}
</Checkbox>
<Checkbox defaultChecked class="flex items-center gap-2 cursor-pointer">
{(state) => (
<>
<div class={`w-4 h-4 border rounded flex items-center justify-center text-xs ${state.checked() ? "bg-indigo-600 border-indigo-600 text-white" : "border-gray-300"}`}>
{state.checked() ? "✓" : ""}
</div>
<span class="text-sm text-gray-700">Subscribe to newsletter</span>
</>
)}
</Checkbox>
</div>
);
}
/** Switch demo with on/off toggle. */
function SwitchDemo() {
const [checked, setChecked] = createSignal(false);
return (
<Switch checked={checked()} onChange={setChecked} class="flex items-center gap-3 cursor-pointer">
{(state) => (
<>
<div class={`w-10 h-6 rounded-full relative transition-colors ${state.checked() ? "bg-indigo-600" : "bg-gray-300"}`}>
<div class={`absolute top-0.5 w-5 h-5 rounded-full bg-white shadow transition-transform ${state.checked() ? "translate-x-4.5" : "translate-x-0.5"}`} />
</div>
<span class="text-sm text-gray-700">{state.checked() ? "On" : "Off"}</span>
</>
)}
</Switch>
);
}
/** Toggle demo with pressed/unpressed. */
function ToggleDemo() {
return (
<Toggle class="px-3 py-1.5 text-sm border rounded transition-colors data-[pressed]:bg-indigo-100 data-[pressed]:text-indigo-700 data-[pressed]:border-indigo-300 border-gray-300 hover:bg-gray-50">
Bold
</Toggle>
);
}
/** Inputs: Basic section with fundamental input components. */
export function InputsBasicSection() {
return (
<>
<ComponentDemo name="Button" description="Clickable element that triggers an action">
<ButtonDemo />
</ComponentDemo>
<ComponentDemo name="TextField" description="Text input field with label, description, and error message support">
<TextFieldDemo />
</ComponentDemo>
<ComponentDemo name="NumberField" description="Numeric input with increment/decrement buttons and keyboard support">
<NumberFieldDemo />
</ComponentDemo>
<ComponentDemo name="Checkbox" description="Toggle control for boolean input, supports indeterminate state">
<CheckboxDemo />
</ComponentDemo>
<ComponentDemo name="Switch" description="Toggle control for on/off states, visually distinct from checkbox">
<SwitchDemo />
</ComponentDemo>
<ComponentDemo name="Toggle" description="Two-state button that can be toggled on or off">
<ToggleDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { InputsBasicSection } from "./sections/inputs-basic";
```
Replace inputs-basic placeholder:
```tsx
<Section id="inputs-basic" title="Inputs: Basic">
<InputsBasicSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: 6 input component demos render with working interactivity (checkbox toggles, switch slides, toggle presses).
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/inputs-basic.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Inputs Basic section demos"
```
---
### Task 5: Inputs Selection section
**Files:**
- Create: `packages/showcase/src/sections/inputs-selection.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<InputsSelectionSection />`
**Component exports used:**
- `RadioGroup` from `pettyui/radio-group` — compound: `RadioGroup`, `RadioGroup.Item`
- `ToggleGroup` from `pettyui/toggle-group` — compound: `ToggleGroup`, `ToggleGroup.Item`
- `Select, SelectRoot, SelectTrigger, SelectValue, SelectContent, SelectItem` from `pettyui/select` — named exports
- `Combobox, ComboboxRoot, ComboboxInput, ComboboxTrigger, ComboboxContent, ComboboxItem` from `pettyui/combobox` — named exports
- `Listbox` from `pettyui/listbox` — compound: `Listbox`, `Listbox.Item`
- `Slider` from `pettyui/slider` — compound: `Slider`, `Slider.Track`, `Slider.Range`, `Slider.Thumb`
- [ ] **Step 1: Create inputs-selection.tsx**
```tsx
import { createSignal, For } from "solid-js";
import { RadioGroup } from "pettyui/radio-group";
import { ToggleGroup } from "pettyui/toggle-group";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "pettyui/select";
import { Combobox, ComboboxInput, ComboboxTrigger, ComboboxContent, ComboboxItem } from "pettyui/combobox";
import { Listbox } from "pettyui/listbox";
import { Slider } from "pettyui/slider";
import { ComponentDemo } from "../component-demo";
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"];
/** RadioGroup demo with 3 options. */
function RadioGroupDemo() {
return (
<RadioGroup defaultValue="option-b" class="flex flex-col gap-2">
{(state) => (
<For each={["option-a", "option-b", "option-c"]}>
{(value) => (
<RadioGroup.Item value={value} class="flex items-center gap-2 cursor-pointer">
{(itemState) => (
<>
<div class={`w-4 h-4 rounded-full border-2 flex items-center justify-center ${itemState.checked() ? "border-indigo-600" : "border-gray-300"}`}>
{itemState.checked() && <div class="w-2 h-2 rounded-full bg-indigo-600" />}
</div>
<span class="text-sm text-gray-700">Option {value.split("-")[1]?.toUpperCase()}</span>
</>
)}
</RadioGroup.Item>
)}
</For>
)}
</RadioGroup>
);
}
/** ToggleGroup demo with single selection. */
function ToggleGroupDemo() {
return (
<ToggleGroup defaultValue="center" class="flex gap-1">
<ToggleGroup.Item value="left" class="px-3 py-1.5 text-sm border rounded-l transition-colors data-[pressed]:bg-indigo-100 data-[pressed]:text-indigo-700 data-[pressed]:border-indigo-300 border-gray-300">
Left
</ToggleGroup.Item>
<ToggleGroup.Item value="center" class="px-3 py-1.5 text-sm border-y transition-colors data-[pressed]:bg-indigo-100 data-[pressed]:text-indigo-700 data-[pressed]:border-indigo-300 border-gray-300">
Center
</ToggleGroup.Item>
<ToggleGroup.Item value="right" class="px-3 py-1.5 text-sm border rounded-r transition-colors data-[pressed]:bg-indigo-100 data-[pressed]:text-indigo-700 data-[pressed]:border-indigo-300 border-gray-300">
Right
</ToggleGroup.Item>
</ToggleGroup>
);
}
/** Select demo with fruit options. */
function SelectDemo() {
return (
<Select options={fruits} placeholder="Pick a fruit" class="relative w-48">
<SelectTrigger class="w-full flex items-center justify-between border border-gray-300 rounded px-3 py-1.5 text-sm hover:bg-gray-50">
<SelectValue class="text-gray-700" placeholder="Pick a fruit" />
<span class="text-gray-400"></span>
</SelectTrigger>
<SelectContent class="absolute top-full mt-1 w-full border border-gray-200 rounded bg-white shadow-lg z-10">
<For each={fruits}>
{(fruit) => (
<SelectItem value={fruit} class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer data-[highlighted]:bg-indigo-50">
{fruit}
</SelectItem>
)}
</For>
</SelectContent>
</Select>
);
}
/** Combobox demo with searchable fruit list. */
function ComboboxDemo() {
return (
<Combobox options={fruits} placeholder="Search fruits..." class="relative w-48">
<div class="flex items-center border border-gray-300 rounded">
<ComboboxInput class="w-full px-3 py-1.5 text-sm rounded focus:outline-none" placeholder="Search fruits..." />
<ComboboxTrigger class="px-2 text-gray-400"></ComboboxTrigger>
</div>
<ComboboxContent class="absolute top-full mt-1 w-full border border-gray-200 rounded bg-white shadow-lg z-10">
<For each={fruits}>
{(fruit) => (
<ComboboxItem value={fruit} class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer data-[highlighted]:bg-indigo-50">
{fruit}
</ComboboxItem>
)}
</For>
</ComboboxContent>
</Combobox>
);
}
/** Listbox demo with inline selectable list. */
function ListboxDemo() {
return (
<Listbox class="w-48 border border-gray-200 rounded overflow-hidden">
<For each={fruits}>
{(fruit) => (
<Listbox.Item value={fruit} class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer data-[highlighted]:bg-indigo-50 data-[selected]:bg-indigo-100 data-[selected]:text-indigo-700">
{fruit}
</Listbox.Item>
)}
</For>
</Listbox>
);
}
/** Slider demo with value display. */
function SliderDemo() {
const [value, setValue] = createSignal(40);
return (
<div class="w-64">
<div class="flex justify-between text-sm text-gray-500 mb-2">
<span>Volume</span>
<span>{value()}%</span>
</div>
<Slider value={value()} onChange={setValue} min={0} max={100} class="relative h-5 flex items-center">
<Slider.Track class="h-1.5 w-full bg-gray-200 rounded-full relative">
<Slider.Range class="absolute h-full bg-indigo-600 rounded-full" />
</Slider.Track>
<Slider.Thumb class="absolute w-4 h-4 bg-white border-2 border-indigo-600 rounded-full shadow -translate-x-1/2 cursor-pointer" />
</Slider>
</div>
);
}
/** Inputs: Selection section with choice/range components. */
export function InputsSelectionSection() {
return (
<>
<ComponentDemo name="RadioGroup" description="Group of mutually exclusive options where only one can be selected">
<RadioGroupDemo />
</ComponentDemo>
<ComponentDemo name="ToggleGroup" description="Group of toggle buttons where one or multiple can be selected">
<ToggleGroupDemo />
</ComponentDemo>
<ComponentDemo name="Select" description="Dropdown for selecting a single option from a list, with keyboard navigation and typeahead">
<SelectDemo />
</ComponentDemo>
<ComponentDemo name="Combobox" description="Searchable select that filters options as the user types, with keyboard navigation">
<ComboboxDemo />
</ComponentDemo>
<ComponentDemo name="Listbox" description="Inline list of selectable options with keyboard navigation">
<ListboxDemo />
</ComponentDemo>
<ComponentDemo name="Slider" description="Range input for selecting numeric values by dragging a thumb along a track">
<SliderDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { InputsSelectionSection } from "./sections/inputs-selection";
```
Replace inputs-selection placeholder:
```tsx
<Section id="inputs-selection" title="Inputs: Selection">
<InputsSelectionSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: 6 selection components render. Radio buttons toggle, select opens a dropdown, combobox filters on type, slider drags.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/inputs-selection.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Inputs Selection section demos"
```
---
### Task 6: Inputs Advanced section
**Files:**
- Create: `packages/showcase/src/sections/inputs-advanced.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<InputsAdvancedSection />`
**Component exports used:**
- `Form` from `pettyui/form` — compound: `Form`, `Form.Field`, `Form.Label`, `Form.Control`, `Form.Description`, `Form.ErrorMessage`, `Form.Submit`
- `DatePicker` from `pettyui/date-picker` — compound: `DatePicker`, `DatePicker.Input`, `DatePicker.Trigger`, `DatePicker.Content`
- [ ] **Step 1: Create inputs-advanced.tsx**
```tsx
import { Form } from "pettyui/form";
import { DatePicker } from "pettyui/date-picker";
import { ComponentDemo } from "../component-demo";
/** Form demo with validation and error display. */
function FormDemo() {
return (
<Form
onSubmit={(data) => {
alert(`Submitted: ${JSON.stringify(data)}`);
}}
class="flex flex-col gap-4 max-w-sm"
>
<Form.Field name="name" class="flex flex-col gap-1">
<Form.Label class="text-sm font-medium text-gray-700">Name</Form.Label>
<Form.Control
type="text"
placeholder="Your name"
class="border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<Form.Description class="text-xs text-gray-400">Required field</Form.Description>
<Form.ErrorMessage class="text-xs text-red-500" />
</Form.Field>
<Form.Field name="email" class="flex flex-col gap-1">
<Form.Label class="text-sm font-medium text-gray-700">Email</Form.Label>
<Form.Control
type="email"
placeholder="you@example.com"
class="border border-gray-300 rounded px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<Form.ErrorMessage class="text-xs text-red-500" />
</Form.Field>
<Form.Submit class="px-4 py-2 text-sm font-medium rounded bg-indigo-600 text-white hover:bg-indigo-700 self-start">
Submit
</Form.Submit>
</Form>
);
}
/** DatePicker demo with calendar dropdown. */
function DatePickerDemo() {
return (
<DatePicker class="relative w-48">
<div class="flex items-center border border-gray-300 rounded">
<DatePicker.Input
placeholder="Pick a date"
class="w-full px-3 py-1.5 text-sm rounded focus:outline-none"
/>
<DatePicker.Trigger class="px-2 text-gray-400 hover:text-gray-600">
📅
</DatePicker.Trigger>
</div>
<DatePicker.Content class="absolute top-full mt-1 border border-gray-200 rounded bg-white shadow-lg z-10 p-2" />
</DatePicker>
);
}
/** Inputs: Advanced section with complex input components. */
export function InputsAdvancedSection() {
return (
<>
<ComponentDemo name="Form" description="Form with Zod v4 schema validation, field-level error display, and accessible aria linking">
<FormDemo />
</ComponentDemo>
<ComponentDemo name="DatePicker" description="Date input with dropdown calendar for selecting dates, with locale formatting and constraints">
<DatePickerDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { InputsAdvancedSection } from "./sections/inputs-advanced";
```
Replace inputs-advanced placeholder:
```tsx
<Section id="inputs-advanced" title="Inputs: Advanced">
<InputsAdvancedSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: Form renders with name/email fields and submit button. DatePicker shows input with calendar trigger.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/inputs-advanced.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Inputs Advanced section demos"
```
---
### Task 7: Navigation section
**Files:**
- Create: `packages/showcase/src/sections/navigation.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<NavigationSection />`
**Component exports used:**
- `Link` from `pettyui/link` — simple: `Link`
- `Breadcrumbs` from `pettyui/breadcrumbs` — compound: `Breadcrumbs`, `Breadcrumbs.Item`, `Breadcrumbs.Link`, `Breadcrumbs.Separator`
- `Tabs` from `pettyui/tabs` — compound: `Tabs`, `Tabs.List`, `Tabs.Tab`, `Tabs.Panel`
- `Accordion` from `pettyui/accordion` — compound: `Accordion`, `Accordion.Item`, `Accordion.Header`, `Accordion.Trigger`, `Accordion.Content`
- `Collapsible` from `pettyui/collapsible` — compound: `Collapsible`, `Collapsible.Trigger`, `Collapsible.Content`
- `Pagination` from `pettyui/pagination` — simple: `Pagination`
- `NavigationMenu` from `pettyui/navigation-menu` — compound: `NavigationMenu`, `NavigationMenu.List`, `NavigationMenu.Item`, `NavigationMenu.Trigger`, `NavigationMenu.Content`, `NavigationMenu.Link`
- `Wizard` from `pettyui/wizard` — compound: `Wizard`, `Wizard.StepList`, `Wizard.Step`, `Wizard.StepTrigger`, `Wizard.StepContent`, `Wizard.Prev`, `Wizard.Next`
- [ ] **Step 1: Create navigation.tsx**
```tsx
import { For } from "solid-js";
import { Link } from "pettyui/link";
import { Breadcrumbs } from "pettyui/breadcrumbs";
import { Tabs } from "pettyui/tabs";
import { Accordion } from "pettyui/accordion";
import { Collapsible } from "pettyui/collapsible";
import { Pagination } from "pettyui/pagination";
import { NavigationMenu } from "pettyui/navigation-menu";
import { Wizard } from "pettyui/wizard";
import { ComponentDemo } from "../component-demo";
/** Link demo with variants. */
function LinkDemo() {
return (
<div class="flex items-center gap-4">
<Link href="#" class="text-sm text-indigo-600 hover:underline">Internal link</Link>
<Link href="https://example.com" external class="text-sm text-indigo-600 hover:underline">
External link
</Link>
<Link href="#" disabled class="text-sm text-gray-400 cursor-not-allowed">Disabled link</Link>
</div>
);
}
/** Breadcrumbs demo with 3 levels. */
function BreadcrumbsDemo() {
return (
<Breadcrumbs class="flex items-center gap-1 text-sm">
<Breadcrumbs.Item>
<Breadcrumbs.Link href="#" class="text-gray-500 hover:text-gray-700">Home</Breadcrumbs.Link>
<Breadcrumbs.Separator class="text-gray-300 mx-1">/</Breadcrumbs.Separator>
</Breadcrumbs.Item>
<Breadcrumbs.Item>
<Breadcrumbs.Link href="#" class="text-gray-500 hover:text-gray-700">Components</Breadcrumbs.Link>
<Breadcrumbs.Separator class="text-gray-300 mx-1">/</Breadcrumbs.Separator>
</Breadcrumbs.Item>
<Breadcrumbs.Item>
<span class="text-gray-900 font-medium">Breadcrumbs</span>
</Breadcrumbs.Item>
</Breadcrumbs>
);
}
/** Tabs demo with 3 panels. */
function TabsDemo() {
return (
<Tabs defaultValue="tab-1" class="w-full">
<Tabs.List class="flex border-b border-gray-200">
<Tabs.Tab value="tab-1" class="px-4 py-2 text-sm border-b-2 -mb-px transition-colors data-[selected]:border-indigo-600 data-[selected]:text-indigo-600 border-transparent text-gray-500 hover:text-gray-700">
Account
</Tabs.Tab>
<Tabs.Tab value="tab-2" class="px-4 py-2 text-sm border-b-2 -mb-px transition-colors data-[selected]:border-indigo-600 data-[selected]:text-indigo-600 border-transparent text-gray-500 hover:text-gray-700">
Password
</Tabs.Tab>
<Tabs.Tab value="tab-3" class="px-4 py-2 text-sm border-b-2 -mb-px transition-colors data-[selected]:border-indigo-600 data-[selected]:text-indigo-600 border-transparent text-gray-500 hover:text-gray-700">
Settings
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="tab-1" class="p-4 text-sm text-gray-700">Account settings content</Tabs.Panel>
<Tabs.Panel value="tab-2" class="p-4 text-sm text-gray-700">Password settings content</Tabs.Panel>
<Tabs.Panel value="tab-3" class="p-4 text-sm text-gray-700">General settings content</Tabs.Panel>
</Tabs>
);
}
/** Accordion demo with 3 sections. */
function AccordionDemo() {
const items = [
{ value: "item-1", title: "What is PettyUI?", content: "A headless SolidJS component library." },
{ value: "item-2", title: "Is it accessible?", content: "Yes, all components follow WAI-ARIA patterns." },
{ value: "item-3", title: "Can I style it?", content: "Absolutely. It's headless — bring your own styles." },
];
return (
<Accordion class="w-full max-w-md border border-gray-200 rounded divide-y divide-gray-200">
<For each={items}>
{(item) => (
<Accordion.Item value={item.value}>
<Accordion.Header>
<Accordion.Trigger class="w-full flex items-center justify-between px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50">
{item.title}
<span class="text-gray-400 data-[expanded]:rotate-180 transition-transform"></span>
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content class="px-4 pb-3 text-sm text-gray-500">
{item.content}
</Accordion.Content>
</Accordion.Item>
)}
</For>
</Accordion>
);
}
/** Collapsible demo with expand/collapse. */
function CollapsibleDemo() {
return (
<Collapsible class="w-full max-w-md">
<Collapsible.Trigger class="flex items-center gap-2 text-sm font-medium text-gray-700 hover:text-gray-900">
<span class="data-[expanded]:rotate-90 transition-transform"></span>
Show more details
</Collapsible.Trigger>
<Collapsible.Content class="mt-2 p-3 bg-gray-50 rounded text-sm text-gray-600">
Here are the additional details that were hidden. Click again to collapse.
</Collapsible.Content>
</Collapsible>
);
}
/** Pagination demo. */
function PaginationDemo() {
return (
<Pagination
count={50}
itemsPerPage={10}
class="flex items-center gap-1"
/>
);
}
/** NavigationMenu demo with dropdown. */
function NavigationMenuDemo() {
return (
<NavigationMenu class="relative">
<NavigationMenu.List class="flex items-center gap-1">
<NavigationMenu.Item>
<NavigationMenu.Link href="#" class="px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
Home
</NavigationMenu.Link>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Trigger class="px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
Products
</NavigationMenu.Trigger>
<NavigationMenu.Content class="absolute top-full left-0 mt-1 p-2 border border-gray-200 rounded bg-white shadow-lg z-10 min-w-48">
<NavigationMenu.Link href="#" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-indigo-50 rounded">
Widget A
</NavigationMenu.Link>
<NavigationMenu.Link href="#" class="block px-3 py-1.5 text-sm text-gray-700 hover:bg-indigo-50 rounded">
Widget B
</NavigationMenu.Link>
</NavigationMenu.Content>
</NavigationMenu.Item>
<NavigationMenu.Item>
<NavigationMenu.Link href="#" class="px-3 py-2 text-sm text-gray-700 hover:bg-gray-100 rounded">
About
</NavigationMenu.Link>
</NavigationMenu.Item>
</NavigationMenu.List>
</NavigationMenu>
);
}
/** Wizard demo with 3 steps. */
function WizardDemo() {
return (
<Wizard class="w-full max-w-md">
<Wizard.StepList class="flex items-center gap-2 mb-4">
<For each={["Details", "Review", "Confirm"]}>
{(label, i) => (
<Wizard.Step index={i()} class="flex items-center gap-2">
<Wizard.StepTrigger class="w-7 h-7 rounded-full border-2 flex items-center justify-center text-xs font-medium data-[active]:border-indigo-600 data-[active]:text-indigo-600 data-[completed]:bg-indigo-600 data-[completed]:text-white data-[completed]:border-indigo-600 border-gray-300 text-gray-400">
{i() + 1}
</Wizard.StepTrigger>
<span class="text-sm text-gray-600">{label}</span>
</Wizard.Step>
)}
</For>
</Wizard.StepList>
<Wizard.StepContent index={0} class="p-4 border border-gray-200 rounded mb-4 text-sm text-gray-700">
Step 1: Enter your details here.
</Wizard.StepContent>
<Wizard.StepContent index={1} class="p-4 border border-gray-200 rounded mb-4 text-sm text-gray-700">
Step 2: Review your information.
</Wizard.StepContent>
<Wizard.StepContent index={2} class="p-4 border border-gray-200 rounded mb-4 text-sm text-gray-700">
Step 3: Confirm and submit.
</Wizard.StepContent>
<div class="flex gap-2">
<Wizard.Prev class="px-3 py-1.5 text-sm border border-gray-300 rounded hover:bg-gray-50 disabled:opacity-40">
Previous
</Wizard.Prev>
<Wizard.Next class="px-3 py-1.5 text-sm bg-indigo-600 text-white rounded hover:bg-indigo-700 disabled:opacity-40">
Next
</Wizard.Next>
</div>
</Wizard>
);
}
/** Navigation section with all navigation components. */
export function NavigationSection() {
return (
<>
<ComponentDemo name="Link" description="Navigation anchor element with external link and disabled support">
<LinkDemo />
</ComponentDemo>
<ComponentDemo name="Breadcrumbs" description="Navigation trail showing the current page location within a hierarchy">
<BreadcrumbsDemo />
</ComponentDemo>
<ComponentDemo name="Tabs" description="Tabbed interface for switching between different views or sections of content">
<TabsDemo />
</ComponentDemo>
<ComponentDemo name="Accordion" description="Vertically stacked sections that expand/collapse to show content">
<AccordionDemo />
</ComponentDemo>
<ComponentDemo name="Collapsible" description="Content section that can be expanded or collapsed with a trigger">
<CollapsibleDemo />
</ComponentDemo>
<ComponentDemo name="Pagination" description="Navigation for paginated content with page numbers, previous/next controls">
<PaginationDemo />
</ComponentDemo>
<ComponentDemo name="NavigationMenu" description="Horizontal navigation bar with dropdown submenus, hover intent, and keyboard support">
<NavigationMenuDemo />
</ComponentDemo>
<ComponentDemo name="Wizard" description="Multi-step flow with step indicators, navigation, and linear/non-linear progression">
<WizardDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { NavigationSection } from "./sections/navigation";
```
Replace navigation placeholder:
```tsx
<Section id="navigation" title="Navigation">
<NavigationSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: 8 navigation component demos. Tabs switch panels, accordion expands/collapses, wizard steps through.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/navigation.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Navigation section demos"
```
---
### Task 8: Overlays section
**Files:**
- Create: `packages/showcase/src/sections/overlays.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<OverlaysSection />`
**Component exports used:**
- `Dialog` from `pettyui/dialog` — compound
- `AlertDialog` from `pettyui/alert-dialog` — compound
- `Drawer` from `pettyui/drawer` — compound
- `Popover` from `pettyui/popover` — compound
- `Tooltip, TooltipRoot, TooltipTrigger, TooltipContent` from `pettyui/tooltip` — named exports
- `HoverCard, HoverCardRoot, HoverCardTrigger, HoverCardContent` from `pettyui/hover-card` — named exports
- `DropdownMenu, DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator` from `pettyui/dropdown-menu` — named exports
- `ContextMenu, ContextMenuRoot, ContextMenuTrigger, ContextMenuContent, ContextMenuItem` from `pettyui/context-menu` — named exports
- `CommandPalette` from `pettyui/command-palette` — compound
- [ ] **Step 1: Create overlays.tsx**
```tsx
import { createSignal } from "solid-js";
import { Dialog } from "pettyui/dialog";
import { AlertDialog } from "pettyui/alert-dialog";
import { Drawer } from "pettyui/drawer";
import { Popover } from "pettyui/popover";
import { TooltipRoot, TooltipTrigger, TooltipContent } from "pettyui/tooltip";
import { HoverCardRoot, HoverCardTrigger, HoverCardContent } from "pettyui/hover-card";
import { DropdownMenuRoot, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from "pettyui/dropdown-menu";
import { ContextMenuRoot, ContextMenuTrigger, ContextMenuContent, ContextMenuItem } from "pettyui/context-menu";
import { CommandPalette } from "pettyui/command-palette";
import { ComponentDemo } from "../component-demo";
const triggerBtn = "px-3 py-1.5 text-sm font-medium rounded border border-gray-300 hover:bg-gray-50";
const overlayBg = "fixed inset-0 bg-black/40 z-40";
const panelBase = "bg-white rounded-lg shadow-xl p-6 z-50";
/** Dialog demo with trigger. */
function DialogDemo() {
return (
<Dialog>
<Dialog.Trigger class={triggerBtn}>Open Dialog</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class={overlayBg} />
<Dialog.Content class={`fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 ${panelBase}`}>
<Dialog.Title class="text-lg font-semibold mb-1">Dialog Title</Dialog.Title>
<Dialog.Description class="text-sm text-gray-500 mb-4">
This is a modal dialog. Press Escape or click Close to dismiss.
</Dialog.Description>
<Dialog.Close class="px-3 py-1.5 text-sm bg-indigo-600 text-white rounded hover:bg-indigo-700">
Close
</Dialog.Close>
</Dialog.Content>
</Dialog.Portal>
</Dialog>
);
}
/** AlertDialog demo with confirm/cancel. */
function AlertDialogDemo() {
return (
<AlertDialog>
<AlertDialog.Trigger class={triggerBtn}>Delete Item</AlertDialog.Trigger>
<AlertDialog.Portal>
<AlertDialog.Overlay class={overlayBg} />
<AlertDialog.Content class={`fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96 ${panelBase}`}>
<AlertDialog.Title class="text-lg font-semibold mb-1">Are you sure?</AlertDialog.Title>
<AlertDialog.Description class="text-sm text-gray-500 mb-4">
This action cannot be undone.
</AlertDialog.Description>
<div class="flex justify-end gap-2">
<AlertDialog.Cancel class="px-3 py-1.5 text-sm border border-gray-300 rounded hover:bg-gray-50">
Cancel
</AlertDialog.Cancel>
<AlertDialog.Action class="px-3 py-1.5 text-sm bg-red-600 text-white rounded hover:bg-red-700">
Delete
</AlertDialog.Action>
</div>
</AlertDialog.Content>
</AlertDialog.Portal>
</AlertDialog>
);
}
/** Drawer demo sliding from right. */
function DrawerDemo() {
return (
<Drawer>
<Drawer.Trigger class={triggerBtn}>Open Drawer</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay class={overlayBg} />
<Drawer.Content class="fixed top-0 right-0 h-full w-80 bg-white shadow-xl z-50 p-6">
<Drawer.Title class="text-lg font-semibold mb-1">Drawer Panel</Drawer.Title>
<Drawer.Description class="text-sm text-gray-500 mb-4">
This drawer slides in from the right.
</Drawer.Description>
<Drawer.Close class="px-3 py-1.5 text-sm border border-gray-300 rounded hover:bg-gray-50">
Close
</Drawer.Close>
</Drawer.Content>
</Drawer.Portal>
</Drawer>
);
}
/** Popover demo with floating content. */
function PopoverDemo() {
return (
<Popover>
<Popover.Trigger class={triggerBtn}>Toggle Popover</Popover.Trigger>
<Popover.Portal>
<Popover.Content class="bg-white border border-gray-200 rounded-lg shadow-lg p-4 z-50 w-64">
<p class="text-sm text-gray-700 mb-2">This is a popover with interactive content.</p>
<Popover.Close class="text-xs text-indigo-600 hover:underline">Dismiss</Popover.Close>
</Popover.Content>
</Popover.Portal>
</Popover>
);
}
/** Tooltip demo on hover. */
function TooltipDemo() {
return (
<TooltipRoot>
<TooltipTrigger class={triggerBtn}>Hover me</TooltipTrigger>
<TooltipContent class="bg-gray-900 text-white text-xs rounded px-2 py-1 z-50 shadow">
This is a tooltip
</TooltipContent>
</TooltipRoot>
);
}
/** HoverCard demo with rich preview. */
function HoverCardDemo() {
return (
<HoverCardRoot>
<HoverCardTrigger class="text-sm text-indigo-600 underline cursor-pointer">
@pettyui
</HoverCardTrigger>
<HoverCardContent class="bg-white border border-gray-200 rounded-lg shadow-lg p-4 z-50 w-64">
<p class="font-medium text-sm">PettyUI</p>
<p class="text-xs text-gray-500 mt-1">AI-native headless UI for SolidJS. 44 components.</p>
</HoverCardContent>
</HoverCardRoot>
);
}
/** DropdownMenu demo with grouped actions. */
function DropdownMenuDemo() {
return (
<DropdownMenuRoot>
<DropdownMenuTrigger class={triggerBtn}>Actions </DropdownMenuTrigger>
<DropdownMenuContent class="border border-gray-200 rounded bg-white shadow-lg z-50 min-w-40 py-1">
<DropdownMenuItem class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer">Edit</DropdownMenuItem>
<DropdownMenuItem class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer">Duplicate</DropdownMenuItem>
<DropdownMenuSeparator class="h-px bg-gray-200 my-1" />
<DropdownMenuItem class="px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 cursor-pointer">Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenuRoot>
);
}
/** ContextMenu demo with right-click area. */
function ContextMenuDemo() {
return (
<ContextMenuRoot>
<ContextMenuTrigger class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center text-sm text-gray-500">
Right-click here
</ContextMenuTrigger>
<ContextMenuContent class="border border-gray-200 rounded bg-white shadow-lg z-50 min-w-40 py-1">
<ContextMenuItem class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer">Copy</ContextMenuItem>
<ContextMenuItem class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer">Paste</ContextMenuItem>
<ContextMenuItem class="px-3 py-1.5 text-sm hover:bg-indigo-50 cursor-pointer">Inspect</ContextMenuItem>
</ContextMenuContent>
</ContextMenuRoot>
);
}
/** CommandPalette demo with searchable commands. */
function CommandPaletteDemo() {
const [open, setOpen] = createSignal(false);
return (
<div>
<button type="button" class={triggerBtn} onClick={() => setOpen(true)}>
Open Command Palette
</button>
<CommandPalette open={open()} onOpenChange={setOpen} class="fixed top-1/4 left-1/2 -translate-x-1/2 w-96 bg-white border border-gray-200 rounded-lg shadow-xl z-50">
<CommandPalette.Input placeholder="Type a command..." class="w-full px-4 py-3 text-sm border-b border-gray-200 focus:outline-none" />
<CommandPalette.List class="max-h-60 overflow-y-auto p-2">
<CommandPalette.Group>
<CommandPalette.Item value="new-file" class="px-3 py-1.5 text-sm rounded hover:bg-indigo-50 cursor-pointer">
New File
</CommandPalette.Item>
<CommandPalette.Item value="open-file" class="px-3 py-1.5 text-sm rounded hover:bg-indigo-50 cursor-pointer">
Open File
</CommandPalette.Item>
<CommandPalette.Item value="save" class="px-3 py-1.5 text-sm rounded hover:bg-indigo-50 cursor-pointer">
Save
</CommandPalette.Item>
</CommandPalette.Group>
<CommandPalette.Empty class="px-3 py-4 text-sm text-gray-400 text-center">
No results found
</CommandPalette.Empty>
</CommandPalette.List>
</CommandPalette>
</div>
);
}
/** Overlays section with all overlay/modal components. */
export function OverlaysSection() {
return (
<>
<ComponentDemo name="Dialog" description="Modal overlay that interrupts the user with important content requiring acknowledgment">
<DialogDemo />
</ComponentDemo>
<ComponentDemo name="AlertDialog" description="Modal dialog for critical confirmations that requires explicit user action to dismiss">
<AlertDialogDemo />
</ComponentDemo>
<ComponentDemo name="Drawer" description="Panel that slides in from the edge of the screen">
<DrawerDemo />
</ComponentDemo>
<ComponentDemo name="Popover" description="Floating content panel anchored to a trigger element">
<PopoverDemo />
</ComponentDemo>
<ComponentDemo name="Tooltip" description="Floating label that appears on hover/focus to describe an element">
<TooltipDemo />
</ComponentDemo>
<ComponentDemo name="HoverCard" description="Card that appears on hover to preview linked content">
<HoverCardDemo />
</ComponentDemo>
<ComponentDemo name="DropdownMenu" description="Menu of actions triggered by a button, with keyboard navigation and grouping">
<DropdownMenuDemo />
</ComponentDemo>
<ComponentDemo name="ContextMenu" description="Right-click context menu with keyboard navigation">
<ContextMenuDemo />
</ComponentDemo>
<ComponentDemo name="CommandPalette" description="Search-driven command menu for finding and executing actions">
<CommandPaletteDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { OverlaysSection } from "./sections/overlays";
```
Replace overlays placeholder:
```tsx
<Section id="overlays" title="Overlays">
<OverlaysSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: 9 overlay component demos. Dialog opens/closes, alert dialog shows confirm prompt, drawer slides in, popover floats, tooltip shows on hover, dropdown menu opens, right-click shows context menu, command palette opens.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/overlays.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Overlays section demos"
```
---
### Task 9: Feedback & Status section
**Files:**
- Create: `packages/showcase/src/sections/feedback-status.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<FeedbackStatusSection />`
**Component exports used:**
- `Alert` from `pettyui/alert` — simple: `Alert` (with `.Title`, `.Description` accessed via dot syntax from the Alert export)
- `Toast, toast` from `pettyui/toast``Toast.Region` compound, `toast()` imperative API
- `Progress` from `pettyui/progress` — simple: `Progress`
- `Meter` from `pettyui/meter` — simple: `Meter`
- [ ] **Step 1: Check Alert export pattern**
Read `packages/core/src/components/alert/alert.tsx` to verify if Alert has compound sub-components or is a flat component.
- [ ] **Step 2: Create feedback-status.tsx**
```tsx
import { Alert } from "pettyui/alert";
import { Toast, toast } from "pettyui/toast";
import { Progress } from "pettyui/progress";
import { Meter } from "pettyui/meter";
import { ComponentDemo } from "../component-demo";
/** Alert demo with multiple variants. */
function AlertDemo() {
return (
<div class="flex flex-col gap-3">
<Alert class="border border-blue-200 bg-blue-50 rounded-lg px-4 py-3">
<Alert.Title class="text-sm font-medium text-blue-800">Info</Alert.Title>
<Alert.Description class="text-sm text-blue-700">This is an informational alert.</Alert.Description>
</Alert>
<Alert class="border border-green-200 bg-green-50 rounded-lg px-4 py-3">
<Alert.Title class="text-sm font-medium text-green-800">Success</Alert.Title>
<Alert.Description class="text-sm text-green-700">Operation completed successfully.</Alert.Description>
</Alert>
<Alert class="border border-red-200 bg-red-50 rounded-lg px-4 py-3">
<Alert.Title class="text-sm font-medium text-red-800">Error</Alert.Title>
<Alert.Description class="text-sm text-red-700">Something went wrong.</Alert.Description>
</Alert>
</div>
);
}
/** Toast demo with trigger button. */
function ToastDemo() {
return (
<div>
<div class="flex gap-2">
<button
type="button"
class="px-3 py-1.5 text-sm font-medium rounded border border-gray-300 hover:bg-gray-50"
onClick={() => toast("Default toast notification")}
>
Default Toast
</button>
<button
type="button"
class="px-3 py-1.5 text-sm font-medium rounded bg-green-600 text-white hover:bg-green-700"
onClick={() => toast.success("Saved successfully!")}
>
Success Toast
</button>
<button
type="button"
class="px-3 py-1.5 text-sm font-medium rounded bg-red-600 text-white hover:bg-red-700"
onClick={() => toast.error("Something failed")}
>
Error Toast
</button>
</div>
<Toast.Region class="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
{(toastData) => (
<div class="bg-white border border-gray-200 rounded-lg shadow-lg px-4 py-3 min-w-64 text-sm">
<p class="font-medium text-gray-900">{toastData.title}</p>
{toastData.description && <p class="text-gray-500 text-xs mt-1">{toastData.description}</p>}
</div>
)}
</Toast.Region>
</div>
);
}
/** Progress demo at 60%. */
function ProgressDemo() {
return (
<div class="w-64">
<div class="flex justify-between text-sm text-gray-500 mb-1">
<span>Uploading...</span>
<span>60%</span>
</div>
<Progress value={60} max={100} class="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
{(state) => (
<div
class="h-full bg-indigo-600 rounded-full transition-all"
style={{ width: `${state.percentage()}%` }}
/>
)}
</Progress>
</div>
);
}
/** Meter demo with value gauge. */
function MeterDemo() {
return (
<div class="w-64">
<div class="flex justify-between text-sm text-gray-500 mb-1">
<span>Disk Usage</span>
<span>75%</span>
</div>
<Meter value={75} min={0} max={100} class="w-full h-2 bg-gray-200 rounded-full overflow-hidden">
{(state) => (
<div
class="h-full bg-amber-500 rounded-full transition-all"
style={{ width: `${state.percentage()}%` }}
/>
)}
</Meter>
</div>
);
}
/** Feedback & Status section with notification and indicator components. */
export function FeedbackStatusSection() {
return (
<>
<ComponentDemo name="Alert" description="Inline status message for important information, warnings, errors, or success states">
<AlertDemo />
</ComponentDemo>
<ComponentDemo name="Toast" description="Temporary notification that auto-dismisses, with imperative toast() API">
<ToastDemo />
</ComponentDemo>
<ComponentDemo name="Progress" description="Visual indicator showing completion progress of a task or operation">
<ProgressDemo />
</ComponentDemo>
<ComponentDemo name="Meter" description="Value gauge indicator for quantities like disk usage or battery">
<MeterDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 3: Wire into app.tsx**
Add import:
```tsx
import { FeedbackStatusSection } from "./sections/feedback-status";
```
Replace feedback-status placeholder:
```tsx
<Section id="feedback-status" title="Feedback & Status">
<FeedbackStatusSection />
</Section>
```
- [ ] **Step 4: Verify in browser**
Expected: Alert variants display, toast buttons trigger temporary notifications, progress bar at 60%, meter at 75%.
- [ ] **Step 5: Commit**
```bash
git add packages/showcase/src/sections/feedback-status.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Feedback & Status section demos"
```
---
### Task 10: Data section
**Files:**
- Create: `packages/showcase/src/sections/data.tsx`
- Modify: `packages/showcase/src/app.tsx` — replace placeholder with `<DataSection />`
**Component exports used:**
- `Calendar` from `pettyui/calendar` — compound: `Calendar`, `Calendar.Header`, `Calendar.Heading`, `Calendar.Nav`, `Calendar.PrevButton`, `Calendar.NextButton`, `Calendar.Grid`, `Calendar.GridHead`, `Calendar.GridBody`, `Calendar.Cell`
- `DataTable, DataTableRoot` from `pettyui/data-table` — named export
- `VirtualList` from `pettyui/virtual-list` — compound with empty sub-object
- [ ] **Step 1: Create data.tsx**
```tsx
import { For } from "solid-js";
import { Calendar } from "pettyui/calendar";
import { DataTable } from "pettyui/data-table";
import { VirtualList } from "pettyui/virtual-list";
import { ComponentDemo } from "../component-demo";
/** Calendar demo with month grid. */
function CalendarDemo() {
return (
<Calendar class="border border-gray-200 rounded-lg p-4 w-72">
<Calendar.Header class="flex items-center justify-between mb-3">
<Calendar.PrevButton class="w-7 h-7 flex items-center justify-center rounded hover:bg-gray-100 text-gray-600">
</Calendar.PrevButton>
<Calendar.Heading class="text-sm font-medium text-gray-700" />
<Calendar.NextButton class="w-7 h-7 flex items-center justify-center rounded hover:bg-gray-100 text-gray-600">
</Calendar.NextButton>
</Calendar.Header>
<Calendar.Grid class="w-full">
<Calendar.GridHead class="text-xs text-gray-400" />
<Calendar.GridBody class="text-sm">
{(day) => (
<Calendar.Cell
day={day}
class="w-8 h-8 flex items-center justify-center rounded hover:bg-indigo-50 cursor-pointer data-[selected]:bg-indigo-600 data-[selected]:text-white data-[today]:font-bold data-[outside]:text-gray-300"
/>
)}
</Calendar.GridBody>
</Calendar.Grid>
</Calendar>
);
}
const sampleData = [
{ id: 1, name: "Alice Johnson", role: "Engineer", status: "Active" },
{ id: 2, name: "Bob Smith", role: "Designer", status: "Active" },
{ id: 3, name: "Carol Williams", role: "PM", status: "Inactive" },
{ id: 4, name: "David Brown", role: "Engineer", status: "Active" },
{ id: 5, name: "Eve Davis", role: "QA", status: "Active" },
];
/** DataTable demo with sortable columns. */
function DataTableDemo() {
return (
<DataTable
data={sampleData}
columns={[
{ accessorKey: "name", header: "Name" },
{ accessorKey: "role", header: "Role" },
{ accessorKey: "status", header: "Status" },
]}
class="w-full text-sm"
>
{(table) => (
<table class="w-full border-collapse">
<thead>
<tr class="border-b border-gray-200">
<For each={table.headers()}>
{(header) => (
<th class="text-left px-3 py-2 text-gray-600 font-medium">{header.label}</th>
)}
</For>
</tr>
</thead>
<tbody>
<For each={table.rows()}>
{(row) => (
<tr class="border-b border-gray-100 hover:bg-gray-50">
<For each={row.cells()}>
{(cell) => (
<td class="px-3 py-2 text-gray-700">{cell.value}</td>
)}
</For>
</tr>
)}
</For>
</tbody>
</table>
)}
</DataTable>
);
}
const virtualItems = Array.from({ length: 10000 }, (_, i) => `Item ${i + 1}`);
/** VirtualList demo with 10k items. */
function VirtualListDemo() {
return (
<VirtualList
items={virtualItems}
itemHeight={32}
overscan={5}
class="h-48 overflow-auto border border-gray-200 rounded"
>
{(item) => (
<div class="px-3 py-1 text-sm text-gray-700 border-b border-gray-100">
{item}
</div>
)}
</VirtualList>
);
}
/** Data section with data display components. */
export function DataSection() {
return (
<>
<ComponentDemo name="Calendar" description="Month grid for date selection with keyboard navigation and locale support">
<CalendarDemo />
</ComponentDemo>
<ComponentDemo name="DataTable" description="Feature-rich data table with sorting, filtering, and pagination">
<DataTableDemo />
</ComponentDemo>
<ComponentDemo name="VirtualList" description="Virtualized scrollable list that only renders visible items, for large datasets">
<VirtualListDemo />
</ComponentDemo>
</>
);
}
```
- [ ] **Step 2: Wire into app.tsx**
Add import:
```tsx
import { DataSection } from "./sections/data";
```
Replace data placeholder:
```tsx
<Section id="data" title="Data">
<DataSection />
</Section>
```
- [ ] **Step 3: Verify in browser**
Expected: Calendar shows month grid with selectable dates, data table shows 5 rows with headers, virtual list scrolls smoothly through 10k items.
- [ ] **Step 4: Commit**
```bash
git add packages/showcase/src/sections/data.tsx packages/showcase/src/app.tsx
git commit -m "feat(showcase): add Data section demos"
```
---
### Task 11: Final integration and polish
**Files:**
- Modify: `packages/showcase/src/app.tsx` — verify all imports are wired
- No new files
- [ ] **Step 1: Verify all 8 sections render**
Run: `cd packages/showcase && pnpm dev`
Scroll through the entire page and verify:
- Sticky header stays at top with working category nav links
- Smooth scroll to each section on nav click
- All 44 components have visible, interactive demos
- No console errors
- [ ] **Step 2: Fix any component API mismatches**
If any component demos don't render due to API mismatches (wrong prop names, different compound component structure), read the specific component source file and fix the demo code to match the actual API.
- [ ] **Step 3: Final commit**
```bash
git add -A packages/showcase/
git commit -m "feat(showcase): complete kitchen sink with all 44 components"
```