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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
QA_TIMEOUT_SCALE: 2
run: |
for group in \
actions calendar data-display docs feedback input \
actions calendar coverage data-display docs feedback input \
layout navigation showcase surfaces theming unlisted
do
ps-qa --app tests/ps-qa/ps-qa.ron \
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ The kitchen sink demo and documentation for our UI components.
## Native QA

Build the site, then run every declared outcome with the font-enabled chuzz host
and ps-qa 0.7.1 or newer:
and ps-qa 0.7.2 or newer:

```sh
bun run build
Expand All @@ -14,8 +14,9 @@ ps-qa --app tests/ps-qa/ps-qa.ron qa-hosted \
--checks tests/ps-qa/checks
```

The UI 3.2 registry build passes all 330 native checks across 12 groups,
including Calendar selection and month navigation. CI includes every group.
The UI 3.2 registry build passes all 600 native checks across 13 groups,
including Calendar selection, month navigation, and keyboard and pointer-driven
Slider and Color Picker outcomes. CI includes every group.
The Layouts route uses its own document marker, and decorative cards are
articles rather than controls that promise an action.

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"@iconify-json/lucide": "^1.2.127",
"@iconify-json/mdi": "^1.2.3",
"@iconify/tailwind4": "^1.0.6",
"@pathscale/ui": "^3.1.0",
"@pathscale/ui": "^3.2.1",
"@rsbuild/core": "^1.3.20",
"@rsbuild/plugin-babel": "^1.0.5",
"@solidjs/router": "2.0.0-next.19",
Expand Down
11 changes: 9 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { createRouter } from "@solidjs/router";
import { ParentComponent } from "solid-js";
import { createRouter, useLocation } from "@solidjs/router";
import { ParentComponent, createEffect } from "solid-js";
import { routes } from "./routes";

import { BaseLayout } from "./layouts/BaseLayout";
import { MarketingHeader } from "./components/layout/Header/MarketingHeader";

const Layout: ParentComponent = (props) => {
const location = useLocation();

createEffect(
() => location.pathname,
() => window.scrollTo(0, 0),
);

return (
<BaseLayout
header={MarketingHeader}
Expand Down
12 changes: 8 additions & 4 deletions src/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Icon } from "@pathscale/ui";
import { Button, Icon } from "@pathscale/ui";
import { setTheme, theme } from "./lib/theme";

export default function ThemeToggle() {
Expand All @@ -8,12 +8,16 @@ export default function ThemeToggle() {
};

return (
<button
<Button
onClick={toggleTheme}
class="p-2 rounded-md bg-primary text-primary-content"
variant="solid"
flavor="primary"
size="sm"
width="square"
radius="md"
aria-label="Switch theme"
>
{theme() === "dark" ? <Icon src="icon-[lucide--sun]" width={20} height={20} /> : <Icon src="icon-[lucide--moon]" width={20} height={20} />}
</button>
</Button>
);
}
31 changes: 20 additions & 11 deletions src/components/AlertShowcase.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Component } from "solid-js";
import { Component, createSignal } from "solid-js";
import ShowcaseLayout from "./ShowcaseLayout";
import { Alert, Button, Flex } from "@pathscale/ui";
import { PropsTable } from "./showcase/PropsTable";
import { CodeBlock } from "./showcase/CodeBlock";
import { ShowcaseSection } from "./showcase/ShowcaseSection";
import { ActionStatus, createActionStatus } from "./showcase/ActionStatus";

const AlertShowcase: Component = () => {
const [cookieChoice, setCookieChoice] = createSignal<"accept" | "deny" | null>(null);
const actions = createActionStatus();
const sections = [
{ id: "contents", title: "Contents" },
{ id: "default", title: "Default" },
Expand Down Expand Up @@ -154,15 +157,20 @@ const AlertShowcase: Component = () => {
justify="start"
gap="md"
>
<Alert class="shadow-lg">
we use cookies for no reason.
<div class="space-x-1">
<Button size="sm">Deny</Button>
<Button size="sm" flavor="primary">
Accept
</Button>
</div>
</Alert>
<div>
<Alert class="shadow-lg">
we use cookies for no reason.
<div class="space-x-1">
<Button size="sm" onClick={() => setCookieChoice("deny")}>Deny</Button>
<Button size="sm" flavor="primary" onClick={() => setCookieChoice("accept")}>
Accept
</Button>
</div>
</Alert>
<p role="status" aria-live="polite" class="mt-3 text-sm text-base-content/70">
{cookieChoice() ? `Cookie preference saved: ${cookieChoice()}.` : "Choose a cookie preference."}
</p>
</div>
</Flex>
<CodeBlock
code={`<Alert>
Expand All @@ -182,8 +190,9 @@ const AlertShowcase: Component = () => {
>
<Alert class="shadow-lg">
You have 1 unread message
<Button size="sm">See</Button>
<Button size="sm" onClick={actions.handler("Unread message opened")}>See</Button>
</Alert>
<ActionStatus message={actions.message()} />
</Flex>
<CodeBlock
code={`<Alert>
Expand Down
27 changes: 21 additions & 6 deletions src/components/BadgeShowcase.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Badge, Button, Chip, Flex, Icon } from "@pathscale/ui";
import { createSignal } from "solid-js";
import ShowcaseLayout from "./ShowcaseLayout";
import { CodeBlock } from "./showcase/CodeBlock";
import { PropsTable } from "./showcase/PropsTable";
import { ShowcaseSection } from "./showcase/ShowcaseSection";
import { ActionStatus, createActionStatus } from "./showcase/ActionStatus";

const badgeProps = [
{ name: "size", type: '"sm" | "md" | "lg"', default: '"md"', description: "Indicator size." },
Expand All @@ -13,19 +15,21 @@ const badgeProps = [
];

export default function BadgeShowcase() {
const [selectedPlacement, setSelectedPlacement] = createSignal<"top-right" | "top-left" | "bottom-right" | "bottom-left">("top-right");
const actions = createActionStatus();
return (
<ShowcaseLayout>
<Flex direction="col" gap="xl">
<ShowcaseSection id="anchor" title="Anchored badge">
<Flex gap="xl" align="center" wrap="wrap">
<Badge.Anchor>
<Button variant="outline" width="square" aria-label="Inbox">
<Button variant="outline" width="square" aria-label="Inbox" onClick={actions.handler("Inbox opened")}>
<Icon src="mdi--inbox" width={20} height={20} />
</Button>
<Badge flavor="destructive">3</Badge>
</Badge.Anchor>
<Badge.Anchor>
<Button variant="outline">Messages</Button>
<Button variant="outline" onClick={actions.handler("Messages opened")}>Messages</Button>
<Badge flavor="accent" placement="bottom-right">12</Badge>
</Badge.Anchor>
</Flex>
Expand All @@ -38,12 +42,23 @@ export default function BadgeShowcase() {
<ShowcaseSection id="placements" title="Placements">
<Flex gap="xl" align="center" wrap="wrap">
{(["top-left", "top-right", "bottom-left", "bottom-right"] as const).map((placement) => (
<Badge.Anchor>
<Button variant="outline">{placement}</Button>
<Badge placement={placement} flavor="accent" />
</Badge.Anchor>
<Button
variant="outline"
aria-pressed={selectedPlacement() === placement ? "true" : "false"}
onClick={() => {
setSelectedPlacement(placement);
actions.announce(`Badge placement set to ${placement}`);
}}
>
{placement}
</Button>
))}
<Badge.Anchor>
<span class="inline-flex min-w-28 justify-center">Preview</span>
<Badge placement={selectedPlacement()} flavor="accent" />
</Badge.Anchor>
</Flex>
<ActionStatus message={actions.message()} />
</ShowcaseSection>

<ShowcaseSection id="labels" title="Standalone labels use Tag">
Expand Down
43 changes: 28 additions & 15 deletions src/components/ButtonShowcase.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Button, Flex, Icon } from "@pathscale/ui";
import { createSignal } from "solid-js";
import ShowcaseLayout from "./ShowcaseLayout";
import { CodeBlock } from "./showcase/CodeBlock";
import { PropsTable } from "./showcase/PropsTable";
import { ShowcaseSection } from "./showcase/ShowcaseSection";
import { ActionStatus, createActionStatus } from "./showcase/ActionStatus";

const buttonProps = [
{ name: "variant", type: '"primary" | "secondary" | "tertiary" | "outline" | "ghost" | "danger" | "danger-soft"', default: '"primary"', description: "Semantic visual treatment." },
Expand All @@ -16,6 +18,8 @@ const buttonProps = [
];

export default function ButtonShowcase() {
const actions = createActionStatus();
const [pressed, setPressed] = createSignal(true);
const startIcon = <Icon src="mdi--arrow-left" width={18} height={18} />;
const endIcon = <Icon src="mdi--arrow-right" width={18} height={18} />;

Expand All @@ -24,13 +28,13 @@ export default function ButtonShowcase() {
<Flex direction="col" gap="xl">
<ShowcaseSection id="variants" title="Variants">
<Flex gap="md" wrap="wrap">
<Button flavor="primary">Primary</Button>
<Button flavor="secondary">Secondary</Button>
<Button flavor="accent">Accent</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button flavor="destructive">Danger</Button>
<Button flavor="destructive" variant="soft">Danger soft</Button>
<Button flavor="primary" onClick={actions.handler("Primary button activated")}>Primary</Button>
<Button flavor="secondary" onClick={actions.handler("Secondary button activated")}>Secondary</Button>
<Button flavor="accent" onClick={actions.handler("Accent button activated")}>Accent</Button>
<Button variant="outline" onClick={actions.handler("Outline button activated")}>Outline</Button>
<Button variant="ghost" onClick={actions.handler("Ghost button activated")}>Ghost</Button>
<Button flavor="destructive" onClick={actions.handler("Danger button activated")}>Danger</Button>
<Button flavor="destructive" variant="soft" onClick={actions.handler("Danger soft button activated")}>Danger soft</Button>
</Flex>
<CodeBlock code={`<Button flavor="primary">Primary</Button>
<Button variant="outline">Outline</Button>
Expand All @@ -39,9 +43,9 @@ export default function ButtonShowcase() {

<ShowcaseSection id="sizes" title="Sizes">
<Flex gap="md" align="center" wrap="wrap">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="sm" onClick={actions.handler("Small button activated")}>Small</Button>
<Button size="md" onClick={actions.handler("Medium button activated")}>Medium</Button>
<Button size="lg" onClick={actions.handler("Large button activated")}>Large</Button>
</Flex>
<CodeBlock code={`<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
Expand All @@ -50,9 +54,9 @@ export default function ButtonShowcase() {

<ShowcaseSection id="icons" title="Icons">
<Flex gap="md" align="center" wrap="wrap">
<Button variant="outline" startIcon={startIcon}>Previous</Button>
<Button flavor="primary" endIcon={endIcon}>Continue</Button>
<Button variant="ghost" width="square" aria-label="Settings">
<Button variant="outline" startIcon={startIcon} onClick={actions.handler("Moved to the previous step")}>Previous</Button>
<Button flavor="primary" endIcon={endIcon} onClick={actions.handler("Continued to the next step")}>Continue</Button>
<Button variant="ghost" width="square" aria-label="Settings" onClick={actions.handler("Settings opened")}>
<Icon src="mdi--cog" width={20} height={20} />
</Button>
</Flex>
Expand All @@ -65,18 +69,27 @@ export default function ButtonShowcase() {
<Flex gap="md" align="center" wrap="wrap">
<Button state="loading">Saving</Button>
<Button state="disabled">Unavailable</Button>
<Button variant="outline" aria-pressed="true">Pressed</Button>
<Button
variant="outline"
aria-pressed={pressed() ? "true" : "false"}
onClick={() => {
setPressed((value) => !value);
actions.announce(`Pressed state ${pressed() ? "on" : "off"}`);
}}
>Pressed</Button>
</Flex>
<CodeBlock code={`<Button state="loading">Saving</Button>
<Button state="disabled">Unavailable</Button>
<Button variant="outline" aria-pressed={selected() ? "true" : "false"}>Selected</Button>`} />
</ShowcaseSection>

<ShowcaseSection id="width" title="Full width">
<Button width="full" flavor="primary">Continue</Button>
<Button width="full" flavor="primary" aria-label="Continue full width" onClick={actions.handler("Full-width action continued")}>Continue</Button>
<CodeBlock code={`<Button width="full" flavor="primary">Continue</Button>`} />
</ShowcaseSection>

<ActionStatus message={actions.message()} />

<ShowcaseSection id="native-link" title="Navigation styled as an action">
<p class="mb-4 text-base-content/70">
Button deliberately renders a button. Use a native anchor or the Link component for
Expand Down
14 changes: 8 additions & 6 deletions src/components/CalendarShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,8 @@ export default function CalendarShowcase() {
setRangePreview(undefined);
};

const today = new Date();
const minDate = new Date(today.getFullYear(), today.getMonth(), 1);
const maxDate = new Date(today.getFullYear(), today.getMonth() + 2, 0);
const minDate = new Date(2025, 5, 1);
const maxDate = new Date(2025, 6, 31);

return (
<ShowcaseLayout>
Expand All @@ -164,14 +163,15 @@ export default function CalendarShowcase() {

<ShowcaseSection id="default" title="Default">
<Flex direction="col" gap="md">
<Calendar />
<Calendar id="default-calendar" defaultValue={new Date(2025, 5, 15)} />
<CodeBlock code={`<Calendar />`} />
</Flex>
</ShowcaseSection>

<ShowcaseSection id="controlled" title="Controlled Value">
<Flex direction="col" gap="md">
<Calendar
id="controlled-calendar"
value={selectedDate()}
onChange={(value) => setSelectedDate(value)}
/>
Expand All @@ -191,6 +191,8 @@ export default function CalendarShowcase() {
<ShowcaseSection id="range" title="Range Selection">
<Flex direction="col" gap="md">
<Calendar
id="range-calendar"
defaultValue={new Date(2025, 5, 15)}
selectionMode="range"
rangeStart={rangeStart()}
rangeEnd={rangeEnd()}
Expand Down Expand Up @@ -221,14 +223,14 @@ export default function CalendarShowcase() {

<ShowcaseSection id="disabled" title="Disabled">
<Flex direction="col" gap="md">
<Calendar state="disabled" />
<Calendar id="disabled-calendar" state="disabled" />
<CodeBlock code={`<Calendar state="disabled" />`} />
</Flex>
</ShowcaseSection>

<ShowcaseSection id="min-max" title="Min / Max">
<Flex direction="col" gap="md">
<Calendar minValue={minDate} maxValue={maxDate} />
<Calendar id="bounded-calendar" minValue={minDate} maxValue={maxDate} />
<CodeBlock
code={`const today = new Date();
const minDate = new Date(today.getFullYear(), today.getMonth(), 1);
Expand Down
Loading
Loading