feat: apply new playground in Popover documentation

This commit is contained in:
p-sw 2024-06-30 22:57:10 +09:00
parent cf331b93e2
commit fb69f288a1
5 changed files with 404 additions and 25 deletions

View File

@ -1,6 +1,6 @@
import { Button } from "@pswui/Button";
import { useToast } from "@pswui/Toast";
import { forwardRef, useEffect, useState } from "react";
import { forwardRef, useEffect, useMemo, useState } from "react";
import SyntaxHighlighter from "react-syntax-highlighter";
import { gruvboxDark } from "react-syntax-highlighter/dist/cjs/styles/hljs";
import { twMerge } from "tailwind-merge";
@ -16,12 +16,16 @@ export const GITHUB_COMP_PREVIEW = (componentName: string) =>
export const GITHUB_STORY = (componentName: string, storyName: string) =>
`${GITHUB_DOCS}/src/docs/components/${componentName}Blocks/Examples/${storyName}.tsx`;
export type TEMPLATE = Record<string, Record<string, string | boolean>>;
export const LoadedCode = ({
from,
className,
template,
}: {
from: string;
className?: string;
template?: TEMPLATE;
}) => {
const [state, setState] = useState<string | undefined | null>();
const { toast } = useToast();
@ -34,6 +38,33 @@ export const LoadedCode = ({
})();
}, [from]);
const postProcessedCode = useMemo(() => {
if (!state) return "";
if (!template) return state;
let templatedCode = state;
for (const [componentName, componentTemplateProps] of Object.entries(
template,
)) {
for (const [propName, propValue] of Object.entries(
componentTemplateProps,
)) {
const regex = new RegExp(
`(<${componentName}\s[^]*)\s${propName}=(\{(true|false|"[^"\n]*"|'[^'\n]*'|\`[^\`\n]*\`)\}|"[^"\n]*"|'[^'\n]*')`,
);
templatedCode = templatedCode.replace(
regex,
typeof propValue === "string"
? `\$1 ${propName}="${propValue}"`
: `$1 ${propName}={${propValue}}`,
);
}
}
return templatedCode;
}, [state, template]);
return (
<div className={twMerge("relative", className)}>
<Button
@ -76,7 +107,7 @@ export const LoadedCode = ({
className={`w-full h-64 rounded-lg ${!state ? "animate-pulse" : ""} scrollbar-none`}
customStyle={{ padding: "1rem" }}
>
{state ?? ""}
{postProcessedCode}
</SyntaxHighlighter>
</div>
);

View File

@ -1,26 +1,15 @@
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_STORY, GITHUB_COMP, GITHUB_COMP_PREVIEW } from "@/components/LoadedCode";
import { PopoverDemo } from "./PopoverBlocks/Preview";
import { LoadedCode, GITHUB_STORY, GITHUB_COMP } from "@/components/LoadedCode";
import Examples from "./PopoverBlocks/Examples";
import PopoverPlayground from "./PopoverBlocks/Playground";
# Popover
Displays rich content in a portal, triggered by a button.
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<PopoverDemo />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Popover")} />
</TabContent>
</TabProvider>
## Playground
<PopoverPlayground />
## Installation
@ -60,10 +49,12 @@ import { Popover, PopoverTrigger, PopoverContent } from "@components/popover"
#### Special
| Prop | Type | Default | Description |
|:----------|:----------|:--------|:------------------------------------------------------------------|
| `opened` | `boolean` | `false` | Initial open state |
| `asChild` | `boolean` | `false` | Whether the root of popover is rendered as a child of a component |
| Prop | Type | Default | Description |
|:----------|:------------------------|:------------|:------------------------------------------------------------------|
| `opened` | `boolean \| undefined` | `undefined` | Opened state |
| `asChild` | `boolean` | `false` | Whether the root of popover is rendered as a child of a component |
Note that giving `opened` prop not undefined will also sets the internal state `controlled` which disables internal popover open/close handling, like close on outside click.
### PopoverContent

View File

@ -0,0 +1,328 @@
import { GITHUB_COMP_PREVIEW, LoadedCode } from "@/components/LoadedCode.tsx";
import { Story } from "@/components/Story";
import useMutable from "@/utils/useMutable.ts";
import { Button } from "@pswui/Button.tsx";
import { Checkbox } from "@pswui/Checkbox.tsx";
import { Label } from "@pswui/Label.tsx";
import { Popover, PopoverContent, PopoverTrigger } from "@pswui/Popover.tsx";
import { TabContent, TabList, TabProvider, TabTrigger } from "@pswui/Tabs";
import { type ControlledPopoverDemoProps, PopoverDemo } from "./Preview.tsx";
export default function PopoverPlayground() {
const [props, mutate] = useMutable<ControlledPopoverDemoProps>({
PopoverProps: {
opened: false,
},
PopoverContentProps: {
direction: "col",
position: "end",
anchor: "middle",
align: "middle",
offset: "md",
className: "",
},
});
return (
<>
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<PopoverDemo {...props} />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Popover")} />
</TabContent>
</TabProvider>
<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-2"
}
>
<Label direction={"horizontal"}>
<span>opened </span>
<Checkbox
checked={props.PopoverProps.opened}
onChange={(e) => {
const v = e.currentTarget.checked;
mutate((p) => {
p.PopoverProps.opened = v;
});
}}
/>
</Label>
<Label direction={"horizontal"}>
<span>direction = </span>
<Popover>
<PopoverTrigger>
<Button className={"gap-2"}>
{props.PopoverContentProps.direction}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<title>Expand</title>
<path
fill="currentColor"
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
/>
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"start"}
align={"start"}
>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.direction = "col";
})
}
>
Column
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.direction = "row";
})
}
>
Row
</Button>
</PopoverContent>
</Popover>
</Label>
<Label direction={"horizontal"}>
<span>position = </span>
<Popover>
<PopoverTrigger>
<Button className={"gap-2"}>
{props.PopoverContentProps.position}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<title>Expand</title>
<path
fill="currentColor"
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
/>
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"start"}
align={"start"}
>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.position = "start";
})
}
>
Start
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.position = "end";
})
}
>
End
</Button>
</PopoverContent>
</Popover>
</Label>
<Label direction={"horizontal"}>
<span>anchor = </span>
<Popover>
<PopoverTrigger>
<Button className={"gap-2"}>
{props.PopoverContentProps.anchor}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<title>Expand</title>
<path
fill="currentColor"
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
/>
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"start"}
align={"start"}
>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.anchor = "start";
})
}
>
Start
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.anchor = "middle";
})
}
>
Middle
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.anchor = "end";
})
}
>
End
</Button>
</PopoverContent>
</Popover>
</Label>
<Label direction={"horizontal"}>
<span>align = </span>
<Popover>
<PopoverTrigger>
<Button className={"gap-2"}>
{props.PopoverContentProps.align}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<title>Expand</title>
<path
fill="currentColor"
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
/>
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"start"}
align={"start"}
>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.align = "start";
})
}
>
Start
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.align = "middle";
})
}
>
Middle
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.align = "end";
})
}
>
End
</Button>
</PopoverContent>
</Popover>
</Label>
<Label direction={"horizontal"}>
<span>offset = </span>
<Popover>
<PopoverTrigger>
<Button className={"gap-2"}>
{props.PopoverContentProps.offset}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
>
<title>Expand</title>
<path
fill="currentColor"
d="M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6z"
/>
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"start"}
align={"start"}
>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.offset = "sm";
})
}
>
Small
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.offset = "md";
})
}
>
Middle
</Button>
<Button
preset={"ghost"}
onClick={() =>
mutate((p) => {
p.PopoverContentProps.offset = "lg";
})
}
>
Large
</Button>
</PopoverContent>
</Popover>
</Label>
</div>
</>
);
}

View File

@ -1,9 +1,26 @@
import { Button } from "@pswui/Button";
import { Popover, PopoverContent, PopoverTrigger } from "@pswui/Popover";
export function PopoverDemo() {
export interface ControlledPopoverDemoProps {
PopoverProps: {
opened: boolean;
};
PopoverContentProps: {
direction: "row" | "col";
position: "start" | "end";
anchor: "start" | "middle" | "end";
align: "start" | "middle" | "end";
offset: "sm" | "md" | "lg";
className?: string;
};
}
export function PopoverDemo({
PopoverProps,
PopoverContentProps,
}: ControlledPopoverDemoProps) {
return (
<Popover>
<Popover {...PopoverProps}>
<PopoverTrigger>
<Button size="icon">
<svg
@ -20,7 +37,7 @@ export function PopoverDemo() {
</svg>
</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverContent {...PopoverContentProps}>
<Button
preset="ghost"
className="gap-2"

12
src/utils/useMutable.ts Normal file
View File

@ -0,0 +1,12 @@
import { useReducer } from "react";
function reduce<T>(state: T, action: (state: T) => void) {
const mutableState: T = { ...state };
action(mutableState);
return mutableState;
}
export default function useMutable<T>(initialData: T) {
const [state, dispatch] = useReducer(reduce<T>, initialData);
return [state, dispatch] as const;
}