All 44 component demos
Layout & Display, Inputs Basic, Inputs Selection, Inputs Advanced, Navigation, Overlays, Feedback & Status, and Data sections with live interactive demos for every PettyUI component.
This commit is contained in:
parent
3d2257ae3c
commit
8761d317ae
@ -1,4 +1,12 @@
|
|||||||
import type { JSX } from "solid-js";
|
import type { JSX } from "solid-js";
|
||||||
|
import { LayoutDisplaySection } from "./sections/layout-display";
|
||||||
|
import { InputsBasicSection } from "./sections/inputs-basic";
|
||||||
|
import { InputsSelectionSection } from "./sections/inputs-selection";
|
||||||
|
import { InputsAdvancedSection } from "./sections/inputs-advanced";
|
||||||
|
import { NavigationSection } from "./sections/navigation";
|
||||||
|
import { OverlaysSection } from "./sections/overlays";
|
||||||
|
import { FeedbackStatusSection } from "./sections/feedback-status";
|
||||||
|
import { DataSection } from "./sections/data";
|
||||||
|
|
||||||
const categories = [
|
const categories = [
|
||||||
{ id: "layout-display", label: "Layout & Display" },
|
{ id: "layout-display", label: "Layout & Display" },
|
||||||
@ -55,28 +63,28 @@ export function App() {
|
|||||||
|
|
||||||
<main class="max-w-5xl mx-auto px-6 py-12">
|
<main class="max-w-5xl mx-auto px-6 py-12">
|
||||||
<Section id="layout-display" title="Layout & Display">
|
<Section id="layout-display" title="Layout & Display">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<LayoutDisplaySection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="inputs-basic" title="Inputs: Basic">
|
<Section id="inputs-basic" title="Inputs: Basic">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<InputsBasicSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="inputs-selection" title="Inputs: Selection">
|
<Section id="inputs-selection" title="Inputs: Selection">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<InputsSelectionSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="inputs-advanced" title="Inputs: Advanced">
|
<Section id="inputs-advanced" title="Inputs: Advanced">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<InputsAdvancedSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="navigation" title="Navigation">
|
<Section id="navigation" title="Navigation">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<NavigationSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="overlays" title="Overlays">
|
<Section id="overlays" title="Overlays">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<OverlaysSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="feedback-status" title="Feedback & Status">
|
<Section id="feedback-status" title="Feedback & Status">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<FeedbackStatusSection />
|
||||||
</Section>
|
</Section>
|
||||||
<Section id="data" title="Data">
|
<Section id="data" title="Data">
|
||||||
<p class="text-gray-400 italic">Coming soon...</p>
|
<DataSection />
|
||||||
</Section>
|
</Section>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
112
packages/showcase/src/sections/data.tsx
Normal file
112
packages/showcase/src/sections/data.tsx
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
const cellCls = "px-3 py-2 text-sm text-gray-700 border-b border-gray-100";
|
||||||
|
const headCls = "px-3 py-2 text-xs font-semibold text-gray-500 uppercase tracking-wide border-b border-gray-200 bg-gray-50 text-left";
|
||||||
|
|
||||||
|
type Person = { id: number; name: string; role: string; status: string };
|
||||||
|
|
||||||
|
const people: Person[] = [
|
||||||
|
{ id: 1, name: "Alice Chen", role: "Engineer", status: "Active" },
|
||||||
|
{ id: 2, name: "Bob Kim", role: "Designer", status: "Active" },
|
||||||
|
{ id: 3, name: "Carol Diaz", role: "Manager", status: "Away" },
|
||||||
|
{ id: 4, name: "Dan Park", role: "Engineer", status: "Active" },
|
||||||
|
{ id: 5, name: "Eva Müller", role: "Product", status: "Inactive" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const tableColumns = [
|
||||||
|
{ id: "name", header: "Name", sortable: true, accessor: (r: Person) => r.name },
|
||||||
|
{ id: "role", header: "Role", sortable: true, accessor: (r: Person) => r.role },
|
||||||
|
{ id: "status", header: "Status", sortable: false, accessor: (r: Person) => r.status },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Calendar demo showing a navigable month grid. */
|
||||||
|
function CalendarDemo() {
|
||||||
|
const navBtn = "w-7 h-7 flex items-center justify-center rounded hover:bg-gray-100 text-gray-600 text-sm";
|
||||||
|
const content = (
|
||||||
|
<Calendar class="w-72 border border-gray-200 rounded-lg p-3 bg-white shadow-sm">
|
||||||
|
<Calendar.Header class="flex items-center justify-between mb-2">
|
||||||
|
<Calendar.Nav class="flex items-center gap-1">
|
||||||
|
<Calendar.PrevButton class={navBtn}>‹</Calendar.PrevButton>
|
||||||
|
<Calendar.NextButton class={navBtn}>›</Calendar.NextButton>
|
||||||
|
</Calendar.Nav>
|
||||||
|
<Calendar.Heading class="text-sm font-semibold text-gray-800" />
|
||||||
|
</Calendar.Header>
|
||||||
|
<Calendar.Grid class="w-full border-collapse">
|
||||||
|
<Calendar.GridHead class="[&_th]:text-xs [&_th]:text-gray-400 [&_th]:font-medium [&_th]:pb-1 [&_th]:text-center" />
|
||||||
|
<Calendar.GridBody class="[&_td]:text-center [&_td]:p-0.5 [&_td]:text-sm [&_td]:rounded [&_td]:cursor-pointer [&_td:hover]:bg-indigo-50 [&_td[data-selected]]:bg-indigo-600 [&_td[data-selected]]:text-white [&_td[data-today]]:font-bold [&_td[data-outside-month]]:text-gray-300" />
|
||||||
|
</Calendar.Grid>
|
||||||
|
</Calendar>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DataTable demo with sortable columns and 5 sample rows. */
|
||||||
|
function DataTableDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="w-full overflow-x-auto rounded-lg border border-gray-200">
|
||||||
|
<DataTable data={people} columns={tableColumns} pageSize={5}>
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<For each={tableColumns}>
|
||||||
|
{(col) => <th class={headCls}>{col.header}</th>}
|
||||||
|
</For>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<DataTable.Body />
|
||||||
|
</table>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ITEM_COUNT = 10000;
|
||||||
|
const ITEM_HEIGHT = 36;
|
||||||
|
|
||||||
|
/** VirtualList demo rendering 10 000 items with windowed scroll. */
|
||||||
|
function VirtualListDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="w-full max-w-sm border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
<div class="px-3 py-2 bg-gray-50 border-b border-gray-200 text-xs text-gray-500">
|
||||||
|
{ITEM_COUNT.toLocaleString()} items — only visible rows are rendered
|
||||||
|
</div>
|
||||||
|
<VirtualList
|
||||||
|
count={ITEM_COUNT}
|
||||||
|
estimateSize={ITEM_HEIGHT}
|
||||||
|
style={{ height: "240px" }}
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
{(item) => (
|
||||||
|
<div class="flex items-center px-3 h-9 text-sm border-b border-gray-100 last:border-0">
|
||||||
|
<span class="text-gray-400 w-16 text-xs">#{item.index + 1}</span>
|
||||||
|
<span class="text-gray-700">Item {item.index + 1}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</VirtualList>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Data section with calendar, table, and virtual list components. */
|
||||||
|
export function DataSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<ComponentDemo name="Calendar" description="Navigable month grid for selecting dates with keyboard and locale support">
|
||||||
|
<CalendarDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="DataTable" description="Sortable, filterable table with pagination and row selection">
|
||||||
|
<DataTableDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="VirtualList" description="Windowed scroll container that renders only visible items for large datasets">
|
||||||
|
<VirtualListDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
116
packages/showcase/src/sections/feedback-status.tsx
Normal file
116
packages/showcase/src/sections/feedback-status.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
const btnBase = "px-3 py-1.5 text-sm font-medium rounded border";
|
||||||
|
|
||||||
|
/** Alert demo showing info, success, and error variants. */
|
||||||
|
function AlertDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="flex flex-col gap-2 w-full max-w-md">
|
||||||
|
<Alert class="flex items-start gap-2 p-3 rounded border border-blue-200 bg-blue-50 text-blue-800 text-sm">
|
||||||
|
<span class="font-semibold">Info:</span> Your session will expire in 5 minutes.
|
||||||
|
</Alert>
|
||||||
|
<Alert class="flex items-start gap-2 p-3 rounded border border-green-200 bg-green-50 text-green-800 text-sm">
|
||||||
|
<span class="font-semibold">Success:</span> Your changes have been saved.
|
||||||
|
</Alert>
|
||||||
|
<Alert class="flex items-start gap-2 p-3 rounded border border-red-200 bg-red-50 text-red-800 text-sm">
|
||||||
|
<span class="font-semibold">Error:</span> Failed to connect to the server.
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toast demo with imperative API triggering different toast types. */
|
||||||
|
function ToastDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
<button type="button" class={`${btnBase} border-gray-300 hover:bg-gray-50`} onClick={() => toast("Action completed")}>Default</button>
|
||||||
|
<button type="button" class={`${btnBase} border-green-300 text-green-700 hover:bg-green-50`} onClick={() => toast.success("Saved successfully!")}>Success</button>
|
||||||
|
<button type="button" class={`${btnBase} border-red-300 text-red-700 hover:bg-red-50`} onClick={() => toast.error("Something went wrong")}>Error</button>
|
||||||
|
<button type="button" class={`${btnBase} border-blue-300 text-blue-700 hover:bg-blue-50`} onClick={() => toast.loading("Loading...")}>Loading</button>
|
||||||
|
</div>
|
||||||
|
<Toast.Region class="fixed bottom-4 right-4 flex flex-col gap-2 z-50 w-72" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Progress demo showing a determinate bar at 60%. */
|
||||||
|
function ProgressDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="w-full max-w-md space-y-3">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||||
|
<span>Uploading file...</span>
|
||||||
|
<span>60%</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<Progress value={60} class="h-full bg-indigo-600 rounded-full" style={{ width: "60%" }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||||
|
<span>Processing...</span>
|
||||||
|
<span>indeterminate</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<Progress class="h-full bg-indigo-400 rounded-full w-1/3 animate-pulse" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Meter demo showing a scalar measurement at 75%. */
|
||||||
|
function MeterDemo() {
|
||||||
|
const content = (
|
||||||
|
<div class="w-full max-w-md space-y-3">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||||
|
<span>Disk usage</span>
|
||||||
|
<span>75 / 100 GB</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-3 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<Meter value={75} class="h-full bg-amber-500 rounded-full" style={{ width: "75%" }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||||
|
<span>Password strength</span>
|
||||||
|
<span>Strong</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-3 bg-gray-200 rounded-full overflow-hidden">
|
||||||
|
<Meter value={90} class="h-full bg-green-500 rounded-full" style={{ width: "90%" }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Feedback and Status section with notification and indicator components. */
|
||||||
|
export function FeedbackStatusSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<ComponentDemo name="Alert" description="Announces important messages to assistive technology via role=alert">
|
||||||
|
<AlertDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="Toast" description="Ephemeral notifications triggered imperatively with toast() API">
|
||||||
|
<ToastDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="Progress" description="Tracks completion of a task with determinate or indeterminate states">
|
||||||
|
<ProgressDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="Meter" description="Displays a scalar measurement within a known range such as disk usage">
|
||||||
|
<MeterDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
56
packages/showcase/src/sections/inputs-advanced.tsx
Normal file
56
packages/showcase/src/sections/inputs-advanced.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
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() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DatePicker demo with calendar dropdown. */
|
||||||
|
function DatePickerDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inputs Advanced section with complex input components. */
|
||||||
|
export function InputsAdvancedSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<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">
|
||||||
|
<DatePickerDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
116
packages/showcase/src/sections/inputs-basic.tsx
Normal file
116
packages/showcase/src/sections/inputs-basic.tsx
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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 primary, secondary, ghost, disabled variants. */
|
||||||
|
const ButtonDemo = () => (
|
||||||
|
<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 state. */
|
||||||
|
const TextFieldDemo = () => (
|
||||||
|
<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 and decrement buttons. */
|
||||||
|
const NumberFieldDemo = () => (
|
||||||
|
<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. */
|
||||||
|
const CheckboxDemo = () => (
|
||||||
|
<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. */
|
||||||
|
const SwitchDemo = () => (
|
||||||
|
<Switch 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" : "translate-x-0.5"}`} />
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-gray-700">{state.checked() ? "On" : "Off"}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Toggle demo with pressed/unpressed states. */
|
||||||
|
const ToggleDemo = () => (
|
||||||
|
<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 const InputsBasicSection = () => (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
146
packages/showcase/src/sections/inputs-selection.tsx
Normal file
146
packages/showcase/src/sections/inputs-selection.tsx
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
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 mutually exclusive options. */
|
||||||
|
function RadioGroupDemo() {
|
||||||
|
const content = (
|
||||||
|
<RadioGroup defaultValue="option-b" class="flex flex-col gap-2">
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ToggleGroup demo with single selection alignment. */
|
||||||
|
function ToggleGroupDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Select demo with fruit dropdown. */
|
||||||
|
function SelectDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Combobox demo with searchable fruit list. */
|
||||||
|
function ComboboxDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listbox demo with inline selectable items. */
|
||||||
|
function ListboxDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Slider demo with value display. */
|
||||||
|
function SliderDemo() {
|
||||||
|
const [value, setValue] = createSignal(40);
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Inputs Selection section with choice and range components. */
|
||||||
|
export function InputsSelectionSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<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">
|
||||||
|
<SelectDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="Combobox" description="Searchable select that filters options as the user types">
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
107
packages/showcase/src/sections/layout-display.tsx
Normal file
107
packages/showcase/src/sections/layout-display.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
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. */
|
||||||
|
const AvatarDemo = () => (
|
||||||
|
<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. */
|
||||||
|
const BadgeDemo = () => (
|
||||||
|
<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. */
|
||||||
|
const CardDemo = () => (
|
||||||
|
<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.</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 and broken sources. */
|
||||||
|
const ImageDemo = () => (
|
||||||
|
<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 divider. */
|
||||||
|
const SeparatorDemo = () => (
|
||||||
|
<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. */
|
||||||
|
const SkeletonDemo = () => (
|
||||||
|
<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 and Display section with all display components. */
|
||||||
|
export const LayoutDisplaySection = () => (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
201
packages/showcase/src/sections/navigation.tsx
Normal file
201
packages/showcase/src/sections/navigation.tsx
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
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 internal, external, and disabled variants. */
|
||||||
|
function LinkDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Breadcrumbs demo with 3-level trail. */
|
||||||
|
function BreadcrumbsDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tabs demo with 3 content panels. */
|
||||||
|
function TabsDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accordionItems = [
|
||||||
|
{ 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." },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Accordion demo with 3 collapsible sections. */
|
||||||
|
function AccordionDemo() {
|
||||||
|
const content = (
|
||||||
|
<Accordion class="w-full max-w-md border border-gray-200 rounded divide-y divide-gray-200">
|
||||||
|
<For each={accordionItems}>
|
||||||
|
{(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">▾</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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collapsible demo with expand/collapse toggle. */
|
||||||
|
function CollapsibleDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>▶</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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Pagination demo with page navigation. */
|
||||||
|
function PaginationDemo() {
|
||||||
|
const content = <Pagination count={50} itemsPerPage={10} class="flex items-center gap-1" />;
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** NavigationMenu demo with dropdown submenu. */
|
||||||
|
function NavigationMenuDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wizard step indicators helper for the wizard demo. */
|
||||||
|
function WizardSteps() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wizard demo with 3-step flow. */
|
||||||
|
function WizardDemo() {
|
||||||
|
const content = (
|
||||||
|
<Wizard class="w-full max-w-md">
|
||||||
|
<WizardSteps />
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Navigation section with all navigation components. */
|
||||||
|
export function NavigationSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<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 and controls">
|
||||||
|
<PaginationDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="NavigationMenu" description="Horizontal navigation bar with dropdown submenus">
|
||||||
|
<NavigationMenuDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
<ComponentDemo name="Wizard" description="Multi-step flow with step indicators and navigation">
|
||||||
|
<WizardDemo />
|
||||||
|
</ComponentDemo>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
209
packages/showcase/src/sections/overlays.tsx
Normal file
209
packages/showcase/src/sections/overlays.tsx
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
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";
|
||||||
|
const modalPos = "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-96";
|
||||||
|
|
||||||
|
/** Dialog demo with modal overlay. */
|
||||||
|
function DialogDemo() {
|
||||||
|
const content = (
|
||||||
|
<Dialog>
|
||||||
|
<Dialog.Trigger class={triggerBtn}>Open Dialog</Dialog.Trigger>
|
||||||
|
<Dialog.Portal>
|
||||||
|
<Dialog.Overlay class={overlayBg} />
|
||||||
|
<Dialog.Content class={`${modalPos} ${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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** AlertDialog demo with confirm/cancel actions. */
|
||||||
|
function AlertDialogDemo() {
|
||||||
|
const content = (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialog.Trigger class={triggerBtn}>Delete Item</AlertDialog.Trigger>
|
||||||
|
<AlertDialog.Portal>
|
||||||
|
<AlertDialog.Overlay class={overlayBg} />
|
||||||
|
<AlertDialog.Content class={`${modalPos} ${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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Drawer demo sliding in from the right edge. */
|
||||||
|
function DrawerDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Popover demo with floating content panel. */
|
||||||
|
function PopoverDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tooltip demo that appears on hover. */
|
||||||
|
function TooltipDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HoverCard demo with rich preview on hover. */
|
||||||
|
function HoverCardDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DropdownMenu demo with action items. */
|
||||||
|
function DropdownMenuDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ContextMenu demo with right-click trigger area. */
|
||||||
|
function ContextMenuDemo() {
|
||||||
|
const content = (
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** CommandPalette demo with searchable commands. */
|
||||||
|
function CommandPaletteDemo() {
|
||||||
|
const [open, setOpen] = createSignal(false);
|
||||||
|
const content = (
|
||||||
|
<div>
|
||||||
|
<button type="button" class={triggerBtn} onClick={() => setOpen(true)}>Open Command Palette</button>
|
||||||
|
<CommandPalette open={open()} onOpenChange={setOpen} class={`${modalPos} 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>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Overlays section showcasing all overlay and modal components. */
|
||||||
|
export function OverlaysSection() {
|
||||||
|
const content = (
|
||||||
|
<>
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
return content;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user