pswui-docs/src/components/Playground.tsx

184 lines
5.7 KiB
TypeScript

import { Button } from "@pswui/Button";
import { Checkbox } from "@pswui/Checkbox";
import { Input } from "@pswui/Input";
import { Label } from "@pswui/Label";
import { Popover, PopoverContent, PopoverTrigger } from "@pswui/Popover";
import { Switch } from "@pswui/Switch";
import { TabContent, TabList, TabProvider, TabTrigger } from "@pswui/Tabs";
import type { ReactNode } from "react";
import { GITHUB_COMP_PREVIEW, LoadedCode, type TEMPLATE } from "./LoadedCode";
import { Story } from "./Story";
export type Template = Record<
string,
Record<
string,
| { type: "boolean"; value: boolean; disabled?: boolean }
| { type: "select"; options: string[]; value: string; disabled?: boolean }
| { type: "string"; value: string; disabled?: boolean }
| { type: "number"; value: number; disabled?: boolean }
>
>;
export type ControlTemplate = Record<
string,
Record<
string,
| {
type: "boolean";
value: boolean;
disabled?: boolean;
onChange: (value: boolean) => void;
onToggle: (v: boolean) => void;
}
| {
type: "select";
options: string[];
value: string;
disabled?: boolean;
onChange: (value: string) => void;
onToggle: (v: boolean) => void;
}
| {
type: "string";
value: string;
disabled?: boolean;
onChange: (value: string) => void;
onToggle: (v: boolean) => void;
}
| {
type: "number";
value: number;
disabled?: boolean;
onChange: (value: number) => void;
onToggle: (v: boolean) => void;
}
>
>;
export function PlaygroundControl<T extends ControlTemplate>(props: {
props: T;
}): ReactNode {
return (
<>
<h3>Controls</h3>
<div
className={
"rounded-lg p-4 border border-neutral-300 dark:border-neutral-700 flex flex-col justify-center items-start gap-12"
}
>
{Object.entries(props.props).map(([componentName, propEntries]) => (
<div
key={componentName}
className="w-full flex flex-col justify-center items-start gap-4"
>
<span className="font-thin opacity-50 w-full border-b border-b-current pb-2">
&lt;{componentName.slice(0, componentName.length - 5)}&gt;
</span>
{Object.entries(propEntries).map(([propName, propMeta]) => (
<div
key={componentName + propName}
className={`flex gap-2 justify-between w-full ${propMeta.type === "boolean" ? "flex-row items-center" : "flex-col md:flex-row md:items-center"}`}
>
<Label
direction="horizontal"
className="flex flex-row items-center gap-2"
>
<Switch
checked={!propMeta.disabled}
onChange={(e) => {
propMeta.onToggle(!e.currentTarget.checked);
}}
/>
{propMeta.disabled ? (
<s className="opacity-50">{propName}</s>
) : (
<span>{propName}</span>
)}
</Label>
{propMeta.type === "boolean" ? (
<Checkbox
checked={propMeta.value}
onChange={(e) => propMeta.onChange(e.currentTarget.checked)}
/>
) : propMeta.type === "string" ? (
<Input
type="text"
value={propMeta.value}
onChange={(e) => propMeta.onChange(e.currentTarget.value)}
className="w-full md:w-fit"
/>
) : propMeta.type === "select" ? (
<Popover>
<PopoverTrigger>
<Button
preset="default"
className="w-full md:w-fit"
>
{propMeta.value}
</Button>
</PopoverTrigger>
<PopoverContent className="min-w-36">
{propMeta.options.map((value) => (
<Button
preset="ghost"
key={value}
onClick={() => propMeta.onChange(value)}
>
{value}
</Button>
))}
</PopoverContent>
</Popover>
) : propMeta.type === "number" ? (
<Input
type="number"
value={propMeta.value}
onChange={(e) =>
propMeta.onChange(e.currentTarget.valueAsNumber)
}
className="w-full md:w-fit"
/>
) : null}
</div>
))}
</div>
))}
</div>
</>
);
}
export function PlaygroundLayout<T extends ControlTemplate>({
children,
compName,
props,
control,
}: {
children: ReactNode;
compName: string;
props: TEMPLATE;
control: T;
}) {
return (
<>
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">{children}</Story>
</TabContent>
<TabContent name="code">
<LoadedCode
from={GITHUB_COMP_PREVIEW(compName)}
template={props}
/>
</TabContent>
</TabProvider>
<PlaygroundControl props={control} />
</>
);
}