Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
759fbd8a5e
chore(deps): bump ws from 8.17.0 to 8.17.1
Bumps [ws](https://github.com/websockets/ws) from 8.17.0 to 8.17.1.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.17.0...8.17.1)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-30 13:58:32 +00:00
54 changed files with 3108 additions and 3632 deletions

View File

@ -7,6 +7,7 @@
"author": "Shinwoo PARK <devpysweb@gmail.com>",
"license": "MIT",
"private": true,
"packageManager": "yarn@4.3.0+sha512.1606bef7c84bc7d83b8576063de2fd08f7d69f9939015bed800f9585a002390268ecc777e9feeba7e26e9556aef6beaad4806968db2182ab5dd3e955ab3b9a0b",
"type": "module",
"scripts": {
"postinstall": "lefthook install",
@ -18,16 +19,14 @@
"dependencies": {
"@mdx-js/react": "^3.0.1",
"@stefanprobst/rehype-extract-toc": "^2.2.0",
"@tailwindcss/vite": "^4.0.12",
"highlight.js": "^11.9.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.23.1",
"react-syntax-highlighter": "^15.6.1",
"react-syntax-highlighter": "^15.5.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0",
"tailwind-merge": "^2.3.0",
"typescript": "^5.4.5"
"tailwind-merge": "^2.3.0"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
@ -43,11 +42,13 @@
"@typescript-eslint/eslint-plugin": "^7.13.0",
"@typescript-eslint/parser": "^7.13.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"lefthook": "^1.6.18",
"tailwind-scrollbar": "^4.0.1",
"tailwindcss": "^4.0.12",
"postcss": "^8.4.38",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.4.4",
"typescript": "^5.4.5",
"vite": "^5.3.0",
"vite-plugin-dynamic-import": "^1.5.0"
},
"packageManager": "yarn@4.4.0+sha512.91d93b445d9284e7ed52931369bc89a663414e5582d00eea45c67ddc459a2582919eece27c412d6ffd1bd0793ff35399381cb229326b961798ce4f4cc60ddfdb"
}
}

View File

@ -9,8 +9,19 @@ import {
import DocsLayout from "./DocsLayout";
import DynamicLayout from "./DynamicLayout";
import ErrorBoundary from "./ErrorHandler";
import Home from "./Home";
import MainLayout from "./MainLayout";
import DocsConfiguration, {
tableOfContents as docsConfigurationToc,
} from "./docs/configuration.mdx";
import DocsInstallation, {
tableOfContents as docsInstallationToc,
} from "./docs/installation.mdx";
import DocsIntroduction, {
tableOfContents as docsIntroductionToc,
} from "./docs/introduction.mdx";
import { Tooltip, TooltipContent } from "@pswui/Tooltip";
import React, {
type ForwardedRef,
@ -214,20 +225,12 @@ const router = createBrowserRouter(
>
<Route
index
lazy={async () => {
const { default: Component } = await import("./Home");
return {
Component,
loader() {
return REDIRECTED_404.test(window.location.search)
? redirect(
REDIRECTED_404.exec(window.location.search)?.[1] ?? "/",
)
: true;
},
};
}}
loader={() =>
REDIRECTED_404.test(window.location.search)
? redirect(REDIRECTED_404.exec(window.location.search)?.[1] ?? "/")
: true
}
element={<Home />}
/>
<Route
path="docs"
@ -239,50 +242,27 @@ const router = createBrowserRouter(
/>
<Route
path="introduction"
lazy={async () => {
const { default: DocsIntroduction, tableOfContents } = await import(
"./docs/introduction.mdx"
);
return {
Component: () => (
<DynamicLayout toc={tableOfContents}>
<DocsIntroduction components={overrideComponents} />
</DynamicLayout>
),
};
}}
element={
<DynamicLayout toc={docsIntroductionToc}>
<DocsIntroduction components={overrideComponents} />
</DynamicLayout>
}
/>
<Route
path="installation"
lazy={async () => {
const { default: DocsInstallation, tableOfContents } = await import(
"./docs/installation.mdx"
);
return {
Component: () => (
<DynamicLayout toc={tableOfContents}>
<DocsInstallation components={overrideComponents} />
</DynamicLayout>
),
};
}}
element={
<DynamicLayout toc={docsInstallationToc}>
<DocsInstallation components={overrideComponents} />
</DynamicLayout>
}
/>
<Route
path="configuration"
lazy={async () => {
const { default: DocsConfiguration, tableOfContents } =
await import("./docs/configuration.mdx");
return {
Component: () => (
<DynamicLayout toc={tableOfContents}>
<DocsConfiguration components={overrideComponents} />
</DynamicLayout>
),
};
}}
element={
<DynamicLayout toc={docsConfigurationToc}>
<DocsConfiguration components={overrideComponents} />
</DynamicLayout>
}
/>
<Route path="components">
<Route

View File

@ -33,7 +33,7 @@ function SideNav() {
function DocsLayout() {
return (
<div className="grow grid grid-cols-1 md:grid-cols-[12rem_1fr] lg:grid-cols-[12rem_1fr_10rem] w-full max-w-5xl mx-auto">
<div className="flex-grow grid grid-cols-1 md:grid-cols-[12rem_1fr] lg:grid-cols-[12rem_1fr_10rem] w-full max-w-5xl mx-auto">
<SideNav />
<Outlet />
</div>

View File

@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
function Home() {
return (
<main className="grow h-full flex flex-col p-4 justify-center items-center">
<main className="flex-grow h-full flex flex-col p-4 justify-center items-center">
<section className="h-full flex flex-col justify-center items-center text-center gap-8">
<header className="flex flex-col justify-center items-center gap-2">
<h1 className="text-4xl font-bold">

View File

@ -62,8 +62,7 @@ function ThemeButton() {
</Button>
</PopoverTrigger>
<PopoverContent
anchor={"end"}
align={"end"}
anchor="bottomLeft"
className="w-32"
>
<Button
@ -150,7 +149,7 @@ function TopNav() {
</svg>
</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerOverlay className="z-[99]">
<DrawerContent className="w-[300px] overflow-auto">
<DrawerClose className="absolute top-4 right-4">
<Button

View File

@ -1,39 +1,10 @@
import { Button } from "@pswui/Button";
import { useToast } from "@pswui/Toast";
import {
type Component,
forwardRef,
useEffect,
useMemo,
useState,
} from "react";
import {
PrismLight,
type SyntaxHighlighterProps,
} from "react-syntax-highlighter";
import { duotoneSpace } from "react-syntax-highlighter/dist/cjs/styles/prism";
import css from "react-syntax-highlighter/dist/esm/languages/prism/css";
import js from "react-syntax-highlighter/dist/esm/languages/prism/javascript";
import jsx from "react-syntax-highlighter/dist/esm/languages/prism/jsx";
import markup from "react-syntax-highlighter/dist/esm/languages/prism/markup";
import tsx from "react-syntax-highlighter/dist/esm/languages/prism/tsx";
import ts from "react-syntax-highlighter/dist/esm/languages/prism/typescript";
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";
const SyntaxHighlighter =
PrismLight as unknown as typeof Component<SyntaxHighlighterProps> & {
registerLanguage<T>(name: string, func: T): void;
alias(name: string, alias: string | string[]): void;
alias(aliases: Record<string, string | string[]>): void;
};
SyntaxHighlighter.registerLanguage("javascript", js);
SyntaxHighlighter.registerLanguage("typescript", ts);
SyntaxHighlighter.registerLanguage("tsx", tsx);
SyntaxHighlighter.registerLanguage("jsx", jsx);
SyntaxHighlighter.registerLanguage("markup", markup);
SyntaxHighlighter.registerLanguage("css", css);
export const GITHUB_UI = "https://raw.githubusercontent.com/pswui/ui/main";
export const GITHUB_DOCS = "https://raw.githubusercontent.com/pswui/docs/main";
export const GITHUB_COMP = (componentName: string) =>
@ -45,19 +16,17 @@ 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 | number | undefined>
>;
export type TEMPLATE = Record<string, Record<string, string | boolean>>;
const TEMPLATE_REMOVE_REGEX = /\/\*\s*remove\s*\*\/(.|\n)*?\/\*\s*end\s*\*\//g;
const TEMPLATE_REPLACE_REGEX =
/\/\*\s*replace\s*\*\/(.|\n)*?\/\*\s*with\s*\n((.|\n)+)\n\s*\*\//g;
export const LoadedCode = forwardRef<
HTMLDivElement,
{ from: string; className?: string; template?: TEMPLATE }
>(({ from, className, template }, ref) => {
export const LoadedCode = ({
from,
className,
template,
}: {
from: string;
className?: string;
template?: TEMPLATE;
}) => {
const [state, setState] = useState<string | undefined | null>();
const { toast } = useToast();
@ -75,10 +44,6 @@ export const LoadedCode = forwardRef<
let templatedCode = state;
templatedCode = templatedCode
.replaceAll(TEMPLATE_REMOVE_REGEX, "")
.replaceAll(TEMPLATE_REPLACE_REGEX, "$2");
for (const [componentName, componentTemplateProps] of Object.entries(
template,
)) {
@ -86,15 +51,13 @@ export const LoadedCode = forwardRef<
componentTemplateProps,
)) {
const regex = new RegExp(
`(<${componentName.slice(0, componentName.length - 5)}\\b[^>]*?)(\n?\\s+)${propName}={${componentName}.${propName}}`,
`(<${componentName}\s[^]*)\s${propName}=(\{(true|false|"[^"\n]*"|'[^'\n]*'|\`[^\`\n]*\`)\}|"[^"\n]*"|'[^'\n]*')`,
);
templatedCode = templatedCode.replace(
regex,
typeof propValue === "undefined"
? "$1"
: typeof propValue === "string"
? `\$1$2 ${propName}="${propValue}"`
: `$1$2 ${propName}={${propValue}}`,
typeof propValue === "string"
? `\$1 ${propName}="${propValue}"`
: `$1 ${propName}={${propValue}}`,
);
}
}
@ -103,10 +66,7 @@ export const LoadedCode = forwardRef<
}, [state, template]);
return (
<div
className={twMerge("relative", className)}
ref={ref}
>
<div className={twMerge("relative", className)}>
<Button
preset="default"
size="icon"
@ -143,16 +103,15 @@ export const LoadedCode = forwardRef<
</Button>
<SyntaxHighlighter
language="typescript"
style={duotoneSpace}
className={`w-full h-64 rounded-lg ${!state ? "animate-pulse" : ""} scrollbar-none resize-y`}
style={gruvboxDark}
className={`w-full h-64 rounded-lg ${!state ? "animate-pulse" : ""} scrollbar-none`}
customStyle={{ padding: "1rem" }}
>
{postProcessedCode}
</SyntaxHighlighter>
</div>
);
});
LoadedCode.displayName = "LoadedCode";
};
export const Code = forwardRef<
HTMLDivElement,
@ -193,7 +152,7 @@ export const Code = forwardRef<
</Button>
<SyntaxHighlighter
language={language}
style={duotoneSpace}
style={gruvboxDark}
className={"w-full h-auto max-h-64 rounded-lg scrollbar-none"}
customStyle={{ padding: "1rem" }}
>

View File

@ -1,44 +0,0 @@
import useMutable from "@/utils/useMutable";
import { useMemo } from "react";
import type { TEMPLATE } from "./LoadedCode";
import type { ControlTemplate, Template } from "./Playground";
export function usePgProps<T extends TEMPLATE>(
t: Template,
): [T, ControlTemplate] {
const [props, mutate] = useMutable(t);
return useMemo(() => {
const rawProps: TEMPLATE = {};
const controlTemplate: ControlTemplate = {};
for (const [componentName, prop] of Object.entries(props)) {
const pre: ControlTemplate[string] = {};
const vals: TEMPLATE[string] = {};
for (const [propKey, propMeta] of Object.entries(prop)) {
pre[propKey] = {
...propMeta,
onChange(value: string | number | boolean) {
console.log(`mutating ${componentName}/${propKey}`);
mutate((state) => {
state[componentName][propKey].value = value;
});
},
onToggle(v: boolean) {
console.log(`toggling ${componentName}/${propKey}`);
mutate((state) => {
state[componentName][propKey].disabled = v;
});
},
};
vals[propKey] = propMeta.disabled ? undefined : propMeta.value;
}
controlTemplate[componentName] = pre;
rawProps[componentName] = vals;
}
return [rawProps as T, controlTemplate];
}, [props, mutate]);
}

View File

@ -1,183 +0,0 @@
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} />
</>
);
}

View File

@ -1,152 +1,162 @@
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_COMP, GITHUB_STORY } from "@/components/LoadedCode";
import Examples from "./ButtonBlocks/Examples";
import ButtonPlayground from "./ButtonBlocks/Playground";
# Button
Displays a button or a component that looks like a button.
## Playground
<ButtonPlayground />
## Installation
1. Create a new file `Button.tsx` in your component folder.
2. Copy and paste the following code into the file.
<LoadedCode from={GITHUB_COMP("Button")} />
## Usage
```tsx
import { Button } from "@components/Button";
```
```html
<button>Button</button>
```
## Props
### Variants
| Prop | Type | Default | Description |
| :----------- | :---------------------------------------------------------------------------- | :---------- | :-------------------------------------- |
| `size` | `"link" \| "sm" \| "md" \| "lg" \| "icon"` | `"md"` | The size of the button |
| `border` | `"none" \| "solid" \| "success" \| "warning" \| "danger"` | `"solid"` | The border color of the button |
| `background` | `"default" \| "ghost" \| "success" \| "warning" \| "danger" \| "transparent"` | `"default"` | The background color of the button |
| `decoration` | `"none" \| "link"` | `"none"` | The inner text decoration of the button |
| `presets` | `"default" \| "ghost" \| "link" \| "success" \| "warning" \| "danger"` | `"default"` | The preset of the variant props |
### Special
| Prop | Type | Default | Description |
| :-------- | :-------- | :------ | :------------------------------------------------------- |
| `asChild` | `boolean` | `false` | Whether the button is rendered as a child of a component |
## Examples
### Default
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Default />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Default")} />
</TabContent>
</TabProvider>
### Ghost
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Ghost />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Ghost")} />
</TabContent>
</TabProvider>
### Link
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Link />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Link")} />
</TabContent>
</TabProvider>
### Success
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Success />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Success")} />
</TabContent>
</TabProvider>
### Warning
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Warning />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Warning")} />
</TabContent>
</TabProvider>
### Danger
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Danger />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Danger")} />
</TabContent>
</TabProvider>
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_COMP, GITHUB_COMP_PREVIEW, GITHUB_STORY } from "@/components/LoadedCode";
import { ButtonDemo } from "./ButtonBlocks/Preview";
import Examples from "./ButtonBlocks/Examples";
# Button
Displays a button or a component that looks like a button.
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<ButtonDemo />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Button")} />
</TabContent>
</TabProvider>
## Installation
1. Create a new file `Button.tsx` in your component folder.
2. Copy and paste the following code into the file.
<LoadedCode from={GITHUB_COMP("Button")} />
## Usage
```tsx
import { Button } from "@components/Button";
```
```html
<Button>Button</Button>
```
## Props
### Variants
| Prop | Type | Default | Description |
|:-------------|:------------------------------------------------------------------------------|:------------|:----------------------------------------|
| `size` | `"link" \| "sm" \| "md" \| "lg" \| "icon"` | `"md"` | The size of the button |
| `border` | `"none" \| "solid" \| "success" \| "warning" \| "danger"` | `"solid"` | The border color of the button |
| `background` | `"default" \| "ghost" \| "success" \| "warning" \| "danger" \| "transparent"` | `"default"` | The background color of the button |
| `decoration` | `"none" \| "link"` | `"none"` | The inner text decoration of the button |
| `presets` | `"default" \| "ghost" \| "link" \| "success" \| "warning" \| "danger"` | `"default"` | The preset of the variant props |
### Special
| Prop | Type | Default | Description |
|:----------|:----------|:--------|:---------------------------------------------------------|
| `asChild` | `boolean` | `false` | Whether the button is rendered as a child of a component |
## Examples
### Default
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Default />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Default")} />
</TabContent>
</TabProvider>
### Ghost
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Ghost />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Ghost")} />
</TabContent>
</TabProvider>
### Link
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Link />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Link")} />
</TabContent>
</TabProvider>
### Success
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Success />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Success")} />
</TabContent>
</TabProvider>
### Warning
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Warning />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Warning")} />
</TabContent>
</TabProvider>
### Danger
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Danger />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Button", "Danger")} />
</TabContent>
</TabProvider>

View File

@ -1,64 +0,0 @@
import type { TEMPLATE } from "@/components/LoadedCode";
import { usePgProps } from "@/components/PgHooks";
import { PlaygroundLayout } from "@/components/Playground";
import { ButtonDemo, type ControlledButtonDemoProps } from "./Preview";
interface TemplateProps extends TEMPLATE, ControlledButtonDemoProps {}
export default function ButtonPlayground() {
const [props, control] = usePgProps<TemplateProps>({
ButtonProps: {
preset: {
type: "select",
options: ["default", "ghost", "link", "success", "warning", "danger"],
value: "default",
},
size: {
type: "select",
options: ["link", "sm", "md", "lg", "icon"],
value: "md",
disabled: true,
},
border: {
type: "select",
options: ["none", "solid", "success", "warning", "danger"],
value: "solid",
disabled: true,
},
background: {
type: "select",
options: [
"default",
"ghost",
"success",
"warning",
"danger",
"transparent",
],
value: "default",
disabled: true,
},
decoration: {
type: "select",
options: ["none", "link"],
value: "none",
disabled: true,
},
disabled: {
type: "boolean",
value: false,
disabled: true,
},
},
});
return (
<PlaygroundLayout
compName="Button"
props={props}
control={control}
>
<ButtonDemo {...props} />
</PlaygroundLayout>
);
}

View File

@ -1,37 +1,5 @@
import { Button } from "@pswui/Button";
/* remove */
export interface ControlledButtonDemoProps {
ButtonProps: {
preset?: "default" | "ghost" | "link" | "success" | "warning" | "danger";
size?: "link" | "sm" | "md" | "lg" | "icon";
border?: "none" | "solid" | "success" | "warning" | "danger";
background?:
| "default"
| "ghost"
| "success"
| "warning"
| "danger"
| "transparent";
decoration?: "none" | "link";
disabled?: boolean;
};
}
/* end */
/* replace */
export function ButtonDemo({ ButtonProps }: ControlledButtonDemoProps) {
/* with
export function ButtonDemo() {
*/
return (
<Button
preset={ButtonProps.preset}
size={ButtonProps.size}
border={ButtonProps.border}
background={ButtonProps.background}
decoration={ButtonProps.decoration}
disabled={ButtonProps.disabled}
>
Button
</Button>
);
return <Button>Button</Button>;
}

View File

@ -1,79 +1,84 @@
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import {
LoadedCode,
GITHUB_COMP,
GITHUB_COMP_PREVIEW,
GITHUB_STORY,
} from "@/components/LoadedCode";
import Examples from "./CheckboxBlocks/Examples";
import Playground from "./CheckboxBlocks/Playground";
# Checkbox
A control that allows the user to toggle between checked and not checked.
## Playground
<Playground />
## Installation
1. Create a new file `Checkbox.mdx` in your component folder.
2. Copy and paste the following code into the file.
<LoadedCode from={GITHUB_COMP("Checkbox")} />
## Usage
```tsx
import { Checkbox } from "@components/Checkbox";
```
```html
<Checkbox />
```
## Props
### Variants
| Prop | Type | Default | Description |
| :----- | :----------------------- | :------ | :----------------------- |
| `size` | `"base" \| "md" \| "lg"` | `"md"` | The size of the checkbox |
## Examples
### Text
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Text />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Checkbox", "Text")} />
</TabContent>
</TabProvider>
### Disabled
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Disabled />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Checkbox", "Disabled")} />
</TabContent>
</TabProvider>
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_COMP, GITHUB_COMP_PREVIEW, GITHUB_STORY } from "@/components/LoadedCode";
import { CheckboxDemo } from "./CheckboxBlocks/Preview";
import Examples from "./CheckboxBlocks/Examples";
# Checkbox
A control that allows the user to toggle between checked and not checked.
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<CheckboxDemo />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Checkbox")} />
</TabContent>
</TabProvider>
## Installation
1. Create a new file `Checkbox.mdx` in your component folder.
2. Copy and paste the following code into the file.
<LoadedCode from={GITHUB_COMP("Checkbox")} />
## Usage
```tsx
import { Checkbox } from "@components/Checkbox";
```
```html
<Checkbox />
```
## Props
### Variants
| Prop | Type | Default | Description |
|:-------|:-------------------------|:--------|:-------------------------|
| `size` | `"base" \| "md" \| "lg"` | `"md"` | The size of the checkbox |
## Examples
### Text
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Text />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Checkbox", "Text")} />
</TabContent>
</TabProvider>
### Disabled
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.Disabled />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Checkbox", "Disabled")} />
</TabContent>
</TabProvider>

View File

@ -1,28 +0,0 @@
import type { TEMPLATE } from "@/components/LoadedCode";
import { usePgProps } from "@/components/PgHooks";
import { PlaygroundLayout } from "@/components/Playground";
import { CheckboxDemo, type CheckboxDemoPlaygroundProps } from "./Preview";
interface TemplateProps extends TEMPLATE, CheckboxDemoPlaygroundProps {}
export default function CheckboxPlayground() {
const [props, control] = usePgProps<TemplateProps>({
CheckboxProps: {
size: {
type: "select",
options: ["base", "md", "lg"],
value: "md",
},
},
});
return (
<PlaygroundLayout
compName="Checkbox"
props={props}
control={control}
>
<CheckboxDemo {...props} />
</PlaygroundLayout>
);
}

View File

@ -1,20 +1,10 @@
import { Checkbox } from "@pswui/Checkbox";
import { Label } from "@pswui/Label";
/* remove */
export interface CheckboxDemoPlaygroundProps {
CheckboxProps: {
size: "base" | "md" | "lg";
};
}
/* replace */
export function CheckboxDemo({ CheckboxProps }: CheckboxDemoPlaygroundProps) {
/* with
export function CheckboxDemo() {
*/
return (
<Label direction="horizontal">
<Checkbox size={CheckboxProps.size} />
<Checkbox />
<span>Checkbox</span>
</Label>
);

View File

@ -1,121 +1,181 @@
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import {
LoadedCode,
GITHUB_DIR_COMP,
GITHUB_COMP_PREVIEW,
GITHUB_STORY,
} from "@/components/LoadedCode";
import { DialogDemo } from "./DialogBlocks/Preview";
import Examples from "./DialogBlocks/Examples";
import Playground from "./DialogBlocks/Playground";
# Dialog
A modal window that prompts the user to take an action or provides critical information.
<Playground />
## Installation
1. Create a new directory named `Dialog` in your component folder.
2. Create following files in the folder, and paste the code into the file.
- `index.ts`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "index.ts")} />
- `Context.ts`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "Context.ts")} />
- `Component.tsx`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "Component.tsx")} />
## Usage
```tsx
import {
DialogRoot,
DialogTrigger,
DialogOverlay,
DialogContent,
DialogHeader,
DialogTitle,
DialogSubtitle,
DialogFooter,
DialogClose,
} from "@components/Dialog";
```
```html
<DialogRoot>
<DialogTrigger>
<button>Open Dialog</button>
</DialogTrigger>
<DialogOverlay>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogSubtitle>Dialog Subtitle</DialogSubtitle>
</DialogHeader>
{/* Main Contents */}
<DialogFooter>
<DialogClose>
<button>Close</button>
</DialogClose>
</DialogFooter>
</DialogContent>
</DialogOverlay>
</DialogRoot>
```
> Note:
>
> DialogTrigger and DialogClose will merge its onClick event handler to its children.
> Also, there is no default element for those.
> So you always have to provide the clickable children for DialogTrigger and DialogClose.
>
> It is easier to understand if you think of this component as always having the `asChild` prop applied to it.
## Props
### DialogOverlay
#### Special
| Prop | Type | Default | Description |
| :------------- | :-------- | :------ | :--------------------------------------------- |
| `closeOnClick` | `boolean` | `false` | Whether the dialog will be closed when clicked |
## Examples
### Basic Informational Dialog
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.BasicInformationalDialog />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Dialog", "BasicInformationalDialog")} />
</TabContent>
</TabProvider>
### Deleting Item
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.DeletingItem />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Dialog", "DeletingItem")} />
</TabContent>
</TabProvider>
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_DIR_COMP, GITHUB_COMP_PREVIEW, GITHUB_STORY } from "@/components/LoadedCode";
import { DialogDemo } from "./DialogBlocks/Preview";
import Examples from "./DialogBlocks/Examples";
# Dialog
A modal window that prompts the user to take an action or provides critical information.
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<DialogDemo />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Dialog")} />
</TabContent>
</TabProvider>
## Installation
1. Create a new directory named `Dialog` in your component folder.
2. Create following files in the folder, and paste the code into the file.
* `index.ts`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "index.ts")} />
* `Context.ts`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "Context.ts")} />
* `Component.tsx`
<LoadedCode from={GITHUB_DIR_COMP("Dialog", "Component.tsx")} />
## Usage
```tsx
import {
DialogRoot,
DialogTrigger,
DialogOverlay,
DialogContent,
DialogHeader,
DialogTitle,
DialogSubtitle,
DialogFooter,
DialogClose,
} from "@components/Dialog";
```
```html
<DialogRoot>
<DialogTrigger>
<Button>Open Dialog</Button>
</DialogTrigger>
<DialogOverlay>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogSubtitle>Dialog Subtitle</DialogSubtitle>
</DialogHeader>
{/* Main Contents */}
<DialogFooter>
<DialogClose>
<Button>Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</DialogOverlay>
</DialogRoot>
```
> Note:
>
> DialogTrigger and DialogClose will merge its onClick event handler to its children.
> Also, there is no default element for those.
> So you always have to provide the clickable children for DialogTrigger and DialogClose.
>
> It is easier to understand if you think of this component as always having the `asChild` prop applied to it.
## Props
### DialogOverlay
#### Variants
| Prop | Type | Default | Description |
|:----------|:-----------------------|:--------|:---------------------------------------------|
| `blur` | `"sm" \| "md" \| "lg"` | `md` | Whether the background of dialog is blurred |
| `darken` | `"sm" \| "md" \| "lg"` | `md` | Whether the background of dialog is darkened |
| `padding` | `"sm" \| "md" \| "lg"` | `md` | Minimum margin of the dialog |
#### Special
| Prop | Type | Default | Description |
|:---------------|:----------|:--------|:-----------------------------------------------|
| `closeOnClick` | `boolean` | `false` | Whether the dialog will be closed when clicked |
### DialogContent
#### Variants
| Prop | Type | Default | Description |
|:----------|:---------------------------------------------------------------------|:--------|:-----------------------------------------------|
| `size` | `"fit" \| "fullSm" \| "fullMd" \| "fullLg" \| "fullXl" \| "full2xl"` | `fit` | Size of the dialog - width and max width |
| `rounded` | `"sm" \| "md" \| "lg" \| "xl"` | `md` | Roundness of the dialog |
| `padding` | `"sm" \| "md" \| "lg"` | `md` | Padding of the dialog |
| `gap` | `"sm" \| "md" \| "lg"` | `md` | Works like flex's gap - space between children |
### DialogHeader
#### Variants
| Prop | Type | Default | Description |
|:------|:-----------------------|:--------|:----------------------------------------------|
| `gap` | `"sm" \| "md" \| "lg"` | `sm` | Gap between the children - title and subtitle |
### DialogTitle
#### Variants
| Prop | Type | Default | Description |
|:---------|:-----------------------|:--------|:--------------------|
| `size` | `"sm" \| "md" \| "lg"` | `md` | Size of the title |
| `weight` | `"sm" \| "md" \| "lg"` | `lg` | Weight of the title |
### DialogSubtitle
#### Variants
| Prop | Type | Default | Description |
|:----------|:-----------------------|:--------|:------------------------|
| `size` | `"sm" \| "md" \| "lg"` | `sm` | Size of the subtitle |
| `weight` | `"sm" \| "md" \| "lg"` | `md` | Weight of the subtitle |
| `opacity` | `"sm" \| "md" \| "lg"` | `sm` | Opacity of the subtitle |
### DialogFooter
#### Variants
| Prop | Type | Default | Description |
|:------|:-----------------------|:--------|:-------------------------|
| `gap` | `"sm" \| "md" \| "lg"` | `sm` | Gap between the children |
## Examples
### Basic Informational Dialog
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.BasicInformationalDialog />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Dialog", "BasicInformationalDialog")} />
</TabContent>
</TabProvider>
### Deleting Item
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<Examples.DeletingItem />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_STORY("Dialog", "DeletingItem")} />
</TabContent>
</TabProvider>

View File

@ -18,7 +18,7 @@ export function BasicInformationalDialog() {
<Button preset="default">What is this?</Button>
</DialogTrigger>
<DialogOverlay>
<DialogContent>
<DialogContent size={"fullMd"}>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogSubtitle>Dialog Subtitle</DialogSubtitle>

View File

@ -21,7 +21,7 @@ export function DeletingItem() {
<Button preset="danger">Delete Item</Button>
</DialogTrigger>
<DialogOverlay>
<DialogContent>
<DialogContent size={"fullMd"}>
<DialogHeader>
<DialogTitle>Delete Item</DialogTitle>
<DialogSubtitle>

View File

@ -1,27 +0,0 @@
import type { TEMPLATE } from "@/components/LoadedCode";
import { usePgProps } from "@/components/PgHooks";
import { PlaygroundLayout } from "@/components/Playground";
import { DialogDemo, type DialogDemoPlaygroundProps } from "./Preview";
interface TemplateProps extends TEMPLATE, DialogDemoPlaygroundProps {}
export default function DialogPlayground() {
const [props, control] = usePgProps<TemplateProps>({
DialogOverlayProps: {
closeOnClick: {
type: "boolean",
value: false,
},
},
});
return (
<PlaygroundLayout
compName="Dialog"
control={control}
props={props}
>
<DialogDemo {...props} />
</PlaygroundLayout>
);
}

View File

@ -10,25 +10,15 @@ import {
DialogTitle,
DialogTrigger,
} from "@pswui/Dialog";
/* remove */
export interface DialogDemoPlaygroundProps {
DialogOverlayProps: {
closeOnClick: boolean;
};
}
/* end */
/* replace */
export function DialogDemo({ DialogOverlayProps }: DialogDemoPlaygroundProps) {
/* with
export function DialogDemo() {
*/
return (
<DialogRoot>
<DialogTrigger>
<Button preset="default">Open Dialog</Button>
</DialogTrigger>
<DialogOverlay closeOnClick={DialogOverlayProps.closeOnClick}>
<DialogContent>
<DialogOverlay>
<DialogContent size={"fullMd"}>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogSubtitle>Dialog Subtitle</DialogSubtitle>

View File

@ -1,13 +1,26 @@
import { TabProvider, TabTrigger, TabContent, TabList } from "@pswui/Tabs";
import { Story } from "@/components/Story";
import { LoadedCode, GITHUB_COMP, GITHUB_COMP_PREVIEW, GITHUB_STORY } from '@/components/LoadedCode';
import { DrawerDemo } from "./DrawerBlocks/Preview";
import Examples from "./DrawerBlocks/Examples";
import Playground from "./DrawerBlocks/Playground";
# Drawer
Displays a panel that slides out from the edge of the screen, typically used for navigation or additional content.
<Playground />
<TabProvider defaultName="preview">
<TabList>
<TabTrigger name="preview">Preview</TabTrigger>
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered">
<DrawerDemo />
</Story>
</TabContent>
<TabContent name="code">
<LoadedCode from={GITHUB_COMP_PREVIEW("Drawer")} />
</TabContent>
</TabProvider>
## Installation
@ -28,7 +41,7 @@ import {
DrawerHeader,
DrawerBody,
DrawerFooter,
} from "@components/Drawer";
} from "@components/Drawer";
```
```html
@ -55,7 +68,7 @@ import {
```
> Note:
>
>
> DrawerTrigger and DrawerClose will merge its onClick event handler to its children.
> Also, there is no default element for those.
> So you always have to provide the clickable children for DialogTrigger and DialogClose.
@ -85,10 +98,9 @@ import {
#### Variants
| Prop | Type | Default | Description |
|:-----------|:-----------------------------------------|:---------|:--------------------------------------------|
| `position` | `"top" \| "bottom" \| "left" \| "right"` | `"left"` | The position of the drawer |
| `maxSize` | `"sm" \| "md" \| "lg" \| "xl"` | `"sm"` | max width ignored on top or bottom position |
| Prop | Type | Default | Description |
|:-----------|:-----------------------------------------|:---------|:---------------------------|
| `position` | `"top" \| "bottom" \| "left" \| "right"` | `"left"` | The position of the drawer |
#### Special

View File

@ -16,7 +16,7 @@ export const Bottom = () => {
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerOverlay className="z-[99]">
<DrawerContent position="bottom">
<DrawerHeader>
<h1 className="text-2xl font-bold">Drawer</h1>

View File

@ -16,7 +16,7 @@ export const Left = () => {
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerOverlay className="z-[99]">
<DrawerContent
position="left"
className="max-w-[320px]"

View File

@ -16,7 +16,7 @@ export const Right = () => {
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerOverlay className="z-[99]">
<DrawerContent
position="right"
className="max-w-[320px]"

View File

@ -16,7 +16,7 @@ export const Top = () => {
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerOverlay className="z-[99]">
<DrawerContent position="top">
<DrawerHeader>
<h1 className="text-2xl font-bold">Drawer</h1>

View File

@ -1,39 +0,0 @@
import type { TEMPLATE } from "@/components/LoadedCode";
import { usePgProps } from "@/components/PgHooks";
import { PlaygroundLayout } from "@/components/Playground";
import { DrawerDemo, type DrawerDemoPlaygroundProps } from "./Preview";
interface TemplateProps extends TEMPLATE, DrawerDemoPlaygroundProps {}
export default function DrawerPlayground() {
const [props, control] = usePgProps<TemplateProps>({
DrawerRootProps: {
closeThreshold: {
type: "number",
value: 0.3,
},
},
DrawerContentProps: {
position: {
type: "select",
options: ["top", "bottom", "left", "right"],
value: "left",
},
maxSize: {
type: "select",
options: ["sm", "md", "lg", "xl"],
value: "sm",
},
},
});
return (
<PlaygroundLayout
compName="Drawer"
control={control}
props={props}
>
<DrawerDemo {...props} />
</PlaygroundLayout>
);
}

View File

@ -9,35 +9,15 @@ import {
DrawerRoot,
DrawerTrigger,
} from "@pswui/Drawer";
/* remove */
export interface DrawerDemoPlaygroundProps {
DrawerRootProps: {
closeThreshold: number;
};
DrawerContentProps: {
position: "top" | "bottom" | "left" | "right";
maxSize: "sm" | "md" | "lg" | "xl";
};
}
/* end */
/* replace */
export function DrawerDemo({
DrawerRootProps,
DrawerContentProps,
}: DrawerDemoPlaygroundProps) {
/* with
export function DrawerDemo() {
*/
export const DrawerDemo = () => {
return (
<DrawerRoot closeThreshold={DrawerRootProps.closeThreshold}>
<DrawerRoot>
<DrawerTrigger>
<Button>Open Drawer</Button>
</DrawerTrigger>
<DrawerOverlay className="z-99">
<DrawerContent
position={DrawerContentProps.position}
maxSize={DrawerContentProps.maxSize}
>
<DrawerOverlay className="z-[99]">
<DrawerContent className="max-w-[320px]">
<DrawerHeader>
<h1 className="text-2xl font-bold">Drawer</h1>
</DrawerHeader>
@ -57,4 +37,4 @@ export function DrawerDemo() {
</DrawerOverlay>
</DrawerRoot>
);
}
};

View File

@ -51,12 +51,7 @@ export const ThemeSelector = () => {
{theme === "light" ? <LightIcon /> : <DarkIcon />}
</Button>
</PopoverTrigger>
<PopoverContent
direction={"col"}
position={"end"}
anchor={"middle"}
align={"middle"}
>
<PopoverContent anchor={"bottomCenter"}>
<Button
onClick={() => setTheme("dark")}
preset={"ghost"}

View File

@ -77,8 +77,7 @@ const SignInForm = () => {
return (
<PopoverContent
anchor={"end"}
align={"end"}
anchor={"bottomLeft"}
className={"p-4 space-y-3"}
>
<Label>
@ -128,10 +127,7 @@ const UserControlContent = () => {
}
return (
<PopoverContent
anchor={"end"}
align={"end"}
>
<PopoverContent anchor={"bottomLeft"}>
<Button preset={"ghost"}>Dashboard</Button>
<Button
preset={"ghost"}

View File

@ -1,58 +1,328 @@
import type { TEMPLATE } from "@/components/LoadedCode.tsx";
import { usePgProps } from "@/components/PgHooks.tsx";
import { PlaygroundLayout } from "@/components/Playground.tsx";
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";
interface TemplateProps extends TEMPLATE, ControlledPopoverDemoProps {}
export default function PopoverPlayground() {
const [props, control] = usePgProps<TemplateProps>({
const [props, mutate] = useMutable<ControlledPopoverDemoProps>({
PopoverProps: {
opened: {
type: "boolean",
value: false,
},
opened: false,
},
PopoverContentProps: {
direction: {
type: "select",
options: ["col", "row"],
value: "col",
},
position: {
type: "select",
options: ["start", "end"],
value: "end",
},
anchor: {
type: "select",
options: ["start", "middle", "end"],
value: "middle",
},
align: {
type: "select",
options: ["start", "middle", "end"],
value: "middle",
},
offset: {
type: "select",
options: ["sm", "md", "lg"],
value: "md",
},
className: {
type: "string",
value: "",
},
direction: "col",
position: "end",
anchor: "middle",
align: "middle",
offset: "md",
className: "",
},
});
return (
<PlaygroundLayout
compName="Popover"
props={props}
control={control}
>
<PopoverDemo {...props} />
</PlaygroundLayout>
<>
<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,6 +1,5 @@
import { Button } from "@pswui/Button";
import { Popover, PopoverContent, PopoverTrigger } from "@pswui/Popover";
/* remove */
export interface ControlledPopoverDemoProps {
PopoverProps: {
@ -16,17 +15,12 @@ export interface ControlledPopoverDemoProps {
};
}
/* end */
/* replace */
export function PopoverDemo({
PopoverProps,
PopoverContentProps,
}: ControlledPopoverDemoProps) {
/* with
export function PopoverDemo() {
*/
return (
<Popover opened={PopoverProps.opened}>
<Popover {...PopoverProps}>
<PopoverTrigger>
<Button size="icon">
<svg
@ -43,14 +37,7 @@ export function PopoverDemo() {
</svg>
</Button>
</PopoverTrigger>
<PopoverContent
direction={PopoverContentProps.direction}
position={PopoverContentProps.position}
anchor={PopoverContentProps.anchor}
align={PopoverContentProps.align}
offset={PopoverContentProps.offset}
className={PopoverContentProps.className}
>
<PopoverContent {...PopoverContentProps}>
<Button
preset="ghost"
className="gap-2"
@ -67,7 +54,7 @@ export function PopoverDemo() {
d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"
/>
</svg>
<span className="grow text-left">Dashboard</span>
<span className="flex-grow text-left">Dashboard</span>
</Button>
<Button
preset="ghost"
@ -85,7 +72,7 @@ export function PopoverDemo() {
d="m17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5M4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4z"
/>
</svg>
<span className="grow text-left">Log out</span>
<span className="flex-grow text-left">Log out</span>
</Button>
</PopoverContent>
</Popover>

View File

@ -12,7 +12,7 @@ Organizes content into multiple sections with tabbed navigation.
<TabTrigger name="code">Code</TabTrigger>
</TabList>
<TabContent name="preview">
<Story layout="centered" className="flex-col *:w-full">
<Story layout="centered" className="flex-col [&>*]:w-full">
<TabsDemo />
</Story>
</TabContent>

View File

@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
function PageNotFound() {
return (
<main className="grow h-full flex flex-col justify-center items-center gap-8">
<main className="flex-grow h-full flex flex-col justify-center items-center gap-8">
<section className="flex flex-col justify-center items-center text-center gap-2">
<h1 className="text-4xl font-bold">Page not found</h1>
<p className="text-base">

View File

@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
function UnexpectedError() {
return (
<main className="grow h-full flex flex-col justify-center items-center gap-8">
<main className="flex-grow h-full flex flex-col justify-center items-center gap-8">
<section className="flex flex-col justify-center items-center text-center gap-2">
<h1 className="text-4xl font-bold">Something went wrong</h1>
<p className="text-base">

View File

@ -14,18 +14,21 @@ const colors = {
danger: "border-red-400 dark:border-red-600",
},
background: {
default: "bg-white dark:bg-black",
default:
"bg-white dark:bg-black hover:bg-neutral-200 dark:hover:bg-neutral-800",
ghost:
"bg-black/0 dark:bg-white/0 hover:bg-black/20 dark:hover:bg-white/20",
success: "bg-green-100 dark:bg-green-900",
warning: "bg-yellow-100 dark:bg-yellow-900",
danger: "bg-red-100 dark:bg-red-900",
success:
"bg-green-100 dark:bg-green-900 hover:bg-green-200 dark:hover:bg-green-800",
warning:
"bg-yellow-100 dark:bg-yellow-900 hover:bg-yellow-200 dark:hover:bg-yellow-800",
danger: "bg-red-100 dark:bg-red-900 hover:bg-red-200 dark:hover:bg-red-800",
},
underline: "decoration-current",
};
const [buttonVariants, resolveVariants] = vcn({
base: `w-fit flex flex-row items-center justify-between rounded-md outline outline-1 outline-transparent outline-offset-2 hover:brightness-90 dark:hover:brightness-110 ${colors.outline.focus} ${colors.disabled} transition-all cursor-pointer`,
base: `w-fit flex flex-row items-center justify-between rounded-md outline outline-1 outline-transparent outline-offset-2 ${colors.outline.focus} ${colors.disabled} transition-all cursor-pointer`,
variants: {
size: {
link: "p-0 text-base",
@ -108,22 +111,21 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const [variantProps, otherPropsCompressed] = resolveVariants(props);
const { asChild, type, role, ...otherPropsExtracted } =
otherPropsCompressed;
const { asChild, ...otherPropsExtracted } = otherPropsCompressed;
const Comp = asChild ? Slot : "button";
const compProps = {
...otherPropsExtracted,
className: buttonVariants(variantProps),
};
return (
<Comp
ref={ref}
type={type ?? "button"}
className={buttonVariants(variantProps)}
role={role ?? "button"}
{...otherPropsExtracted}
{...compProps}
/>
);
},
);
Button.displayName = "Button";
export { Button };

View File

@ -18,10 +18,12 @@ const checkboxColors = {
disabledCheckedHover:
"has-[input[type='checkbox']:disabled:checked]:hover:bg-neutral-300 dark:has-[input[type='checkbox']:disabled:checked]:hover:bg-neutral-700",
},
checkmark:
"text-black dark:text-white has-[input[type=checkbox]:disabled]:text-neutral-400 dark:has-[input[type=checkbox]:disabled]:text-neutral-500",
};
const [checkboxVariant, resolveCheckboxVariantProps] = vcn({
base: `inline-block rounded-md ${checkboxColors.background.disabled} ${checkboxColors.background.default} ${checkboxColors.background.hover} ${checkboxColors.background.checked} ${checkboxColors.background.checkedHover} ${checkboxColors.background.disabledChecked} ${checkboxColors.background.disabledCheckedHover} has-[input[type="checkbox"]:disabled]:cursor-not-allowed transition-colors duration-150 ease-in-out`,
base: `inline-block rounded-md ${checkboxColors.checkmark} ${checkboxColors.background.disabled} ${checkboxColors.background.default} ${checkboxColors.background.hover} ${checkboxColors.background.checked} ${checkboxColors.background.checkedHover} ${checkboxColors.background.disabledChecked} ${checkboxColors.background.disabledCheckedHover} has-[input[type="checkbox"]:disabled]:cursor-not-allowed transition-colors duration-75 ease-in-out`,
variants: {
size: {
base: "size-[1em] p-0 [&>svg]:size-[1em]",
@ -90,11 +92,23 @@ const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
}
}}
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
className={`${checked ? "opacity-100" : "opacity-0"} transition-opacity duration-75 ease-in-out`}
>
<title>checked</title>
<path
fill="currentColor"
d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59z"
/>
</svg>
</label>
</>
);
},
);
Checkbox.displayName = "Checkbox";
export { Checkbox };

View File

@ -1,5 +1,5 @@
import { Slot, type VariantProps, useDocument, vcn } from "@pswui-lib";
import React, { type ReactNode, useState } from "react";
import { Slot, type VariantProps, vcn } from "@pswui-lib";
import React, { useState } from "react";
import ReactDOM from "react-dom";
import {
@ -55,15 +55,33 @@ const DialogTrigger = ({ children }: DialogTriggerProps) => {
*/
const [dialogOverlayVariant, resolveDialogOverlayVariant] = vcn({
base: "fixed inset-0 z-50 w-full h-screen overflow-y-auto max-w-screen transition-all duration-300 backdrop-blur-md backdrop-brightness-75 [&>div]:p-6",
base: "fixed inset-0 z-50 w-full h-screen overflow-y-auto max-w-screen transition-all duration-300",
variants: {
opened: {
true: "pointer-events-auto opacity-100",
false: "pointer-events-none opacity-0",
},
blur: {
sm: "backdrop-blur-sm",
md: "backdrop-blur-md",
lg: "backdrop-blur-lg",
},
darken: {
sm: "backdrop-brightness-90",
md: "backdrop-brightness-75",
lg: "backdrop-brightness-50",
},
padding: {
sm: "[&>div]:p-4",
md: "[&>div]:p-6",
lg: "[&>div]:p-8",
},
},
defaults: {
opened: false,
blur: "md",
darken: "md",
padding: "md",
},
});
@ -82,36 +100,35 @@ const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlay>(
});
const { children, closeOnClick, onClick, ...otherPropsExtracted } =
otherPropsCompressed;
const document = useDocument();
if (!document) return null;
return ReactDOM.createPortal(
<div
{...otherPropsExtracted}
ref={ref}
className={dialogOverlayVariant(variantProps)}
onClick={(e) => {
if (closeOnClick) {
setContext((p) => ({ ...p, opened: false }));
}
onClick?.(e);
}}
>
<div
className={
"w-screen max-w-full min-h-screen flex flex-col justify-center items-center"
}
>
{/* Layer for overflow positioning */}
{children}
</div>
</div>,
document.body,
return (
<>
{ReactDOM.createPortal(
<div
{...otherPropsExtracted}
ref={ref}
className={dialogOverlayVariant(variantProps)}
onClick={(e) => {
if (closeOnClick) {
setContext((p) => ({ ...p, opened: false }));
}
onClick?.(e);
}}
>
<div
className={
"w-screen max-w-full min-h-screen flex flex-col justify-center items-center"
}
>
{/* Layer for overflow positioning */}
{children}
</div>
</div>,
document.body,
)}
</>
);
},
);
DialogOverlay.displayName = "DialogOverlay";
/**
* =========================
@ -120,15 +137,43 @@ DialogOverlay.displayName = "DialogOverlay";
*/
const [dialogContentVariant, resolveDialogContentVariant] = vcn({
base: "transition-transform duration-300 bg-white dark:bg-black border border-neutral-200 dark:border-neutral-800 p-6 w-full max-w-xl rounded-md flex flex-col justify-start items-start gap-6",
base: "transition-transform duration-300 bg-white dark:bg-black border border-neutral-200 dark:border-neutral-800",
variants: {
opened: {
true: "scale-100",
false: "scale-50",
},
size: {
fit: "w-fit",
fullSm: "w-full max-w-sm",
fullMd: "w-full max-w-md",
fullLg: "w-full max-w-lg",
fullXl: "w-full max-w-xl",
full2xl: "w-full max-w-2xl",
},
rounded: {
sm: "rounded-sm",
md: "rounded-md",
lg: "rounded-lg",
xl: "rounded-xl",
},
padding: {
sm: "p-4",
md: "p-6",
lg: "p-8",
},
gap: {
sm: "space-y-4",
md: "space-y-6",
lg: "space-y-8",
},
},
defaults: {
opened: false,
size: "fit",
rounded: "md",
padding: "md",
gap: "md",
},
});
@ -159,7 +204,6 @@ const DialogContent = React.forwardRef<HTMLDivElement, DialogContentProps>(
);
},
);
DialogContent.displayName = "DialogContent";
/**
* =========================
@ -190,9 +234,17 @@ const DialogClose = ({ children }: DialogCloseProps) => {
*/
const [dialogHeaderVariant, resolveDialogHeaderVariant] = vcn({
base: "flex flex-col gap-2",
variants: {},
defaults: {},
base: "flex flex-col",
variants: {
gap: {
sm: "gap-2",
md: "gap-4",
lg: "gap-6",
},
},
defaults: {
gap: "sm",
},
});
interface DialogHeaderProps
@ -216,8 +268,6 @@ const DialogHeader = React.forwardRef<HTMLElement, DialogHeaderProps>(
},
);
DialogHeader.displayName = "DialogHeader";
/**
* =========================
* DialogTitle / DialogSubtitle
@ -225,9 +275,22 @@ DialogHeader.displayName = "DialogHeader";
*/
const [dialogTitleVariant, resolveDialogTitleVariant] = vcn({
base: "text-xl font-bold",
variants: {},
defaults: {},
variants: {
size: {
sm: "text-lg",
md: "text-xl",
lg: "text-2xl",
},
weight: {
sm: "font-medium",
md: "font-semibold",
lg: "font-bold",
},
},
defaults: {
size: "md",
weight: "lg",
},
});
interface DialogTitleProps
@ -235,9 +298,28 @@ interface DialogTitleProps
VariantProps<typeof dialogTitleVariant> {}
const [dialogSubtitleVariant, resolveDialogSubtitleVariant] = vcn({
base: "text-sm opacity-60 font-normal",
variants: {},
defaults: {},
variants: {
size: {
sm: "text-sm",
md: "text-base",
lg: "text-lg",
},
opacity: {
sm: "opacity-60",
md: "opacity-70",
lg: "opacity-80",
},
weight: {
sm: "font-light",
md: "font-normal",
lg: "font-medium",
},
},
defaults: {
size: "sm",
opacity: "sm",
weight: "md",
},
});
interface DialogSubtitleProps
@ -260,7 +342,6 @@ const DialogTitle = React.forwardRef<HTMLHeadingElement, DialogTitleProps>(
);
},
);
DialogTitle.displayName = "DialogTitle";
const DialogSubtitle = React.forwardRef<
HTMLHeadingElement,
@ -279,7 +360,6 @@ const DialogSubtitle = React.forwardRef<
</h2>
);
});
DialogSubtitle.displayName = "DialogSubtitle";
/**
* =========================
@ -288,9 +368,17 @@ DialogSubtitle.displayName = "DialogSubtitle";
*/
const [dialogFooterVariant, resolveDialogFooterVariant] = vcn({
base: "flex w-full flex-col items-end sm:flex-row sm:items-center sm:justify-end gap-2",
variants: {},
defaults: {},
base: "flex flex-col items-end sm:flex-row sm:items-center sm:justify-end",
variants: {
gap: {
sm: "gap-2",
md: "gap-4",
lg: "gap-6",
},
},
defaults: {
gap: "md",
},
});
interface DialogFooterProps
@ -313,34 +401,6 @@ const DialogFooter = React.forwardRef<HTMLDivElement, DialogFooterProps>(
);
},
);
DialogFooter.displayName = "DialogFooter";
interface DialogControllers {
context: IDialogContext;
setContext: React.Dispatch<React.SetStateAction<IDialogContext>>;
close: () => void;
}
interface DialogControllerProps {
children: (controllers: DialogControllers) => ReactNode;
}
const DialogController = (props: DialogControllerProps) => {
return (
<DialogContext.Consumer>
{([context, setContext]) =>
props.children({
context,
setContext,
close() {
setContext((p) => ({ ...p, opened: false }));
},
})
}
</DialogContext.Consumer>
);
};
export {
DialogRoot,
@ -352,5 +412,4 @@ export {
DialogTitle,
DialogSubtitle,
DialogFooter,
DialogController,
};

View File

@ -1,11 +1,4 @@
import {
type AsChild,
Slot,
type VariantProps,
useAnimatedMount,
useDocument,
vcn,
} from "@pswui-lib";
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
import React, {
type ComponentPropsWithoutRef,
type TouchEvent as ReactTouchEvent,
@ -22,8 +15,6 @@ interface IDrawerContext {
closeThreshold: number;
movePercentage: number;
isDragging: boolean;
isMounted: boolean;
isRendered: boolean;
leaveWhileDragging: boolean;
}
const DrawerContextInitial: IDrawerContext = {
@ -31,8 +22,6 @@ const DrawerContextInitial: IDrawerContext = {
closeThreshold: 0.3,
movePercentage: 0,
isDragging: false,
isMounted: false,
isRendered: false,
leaveWhileDragging: false,
};
const DrawerContext = React.createContext<
@ -107,14 +96,8 @@ interface DrawerOverlayProps
const DrawerOverlay = forwardRef<HTMLDivElement, DrawerOverlayProps>(
(props, ref) => {
const internalRef = useRef<HTMLDivElement | null>(null);
const [state, setState] = useContext(DrawerContext);
const { isMounted, isRendered } = useAnimatedMount(
state.isDragging ? true : state.opened,
internalRef,
);
const [variantProps, restPropsCompressed] =
resolveDrawerOverlayVariantProps(props);
const { asChild, ...restPropsExtracted } = restPropsCompressed;
@ -136,46 +119,25 @@ const DrawerOverlay = forwardRef<HTMLDivElement, DrawerOverlayProps>(
: 1
})`;
const document = useDocument();
if (!document) return null;
return (
<>
<DrawerContext.Provider
value={[{ ...state, isMounted, isRendered }, setState]}
>
{isMounted
? createPortal(
<Comp
{...restPropsExtracted}
className={drawerOverlayVariant({
...variantProps,
opened: isRendered,
})}
onClick={onOutsideClick}
style={{
backdropFilter,
WebkitBackdropFilter: backdropFilter,
transitionDuration: state.isDragging ? "0s" : undefined,
}}
ref={(el: HTMLDivElement) => {
internalRef.current = el;
if (typeof ref === "function") {
ref(el);
} else if (ref) {
ref.current = el;
}
}}
/>,
document.body,
)
: null}
</DrawerContext.Provider>
</>
return createPortal(
<Comp
{...restPropsExtracted}
className={drawerOverlayVariant({
...variantProps,
opened: state.isDragging ? true : state.opened,
})}
onClick={onOutsideClick}
style={{
backdropFilter,
WebkitBackdropFilter: backdropFilter,
transitionDuration: state.isDragging ? "0s" : undefined,
}}
ref={ref}
/>,
document.body,
);
},
);
DrawerOverlay.displayName = "DrawerOverlay";
const drawerContentColors = {
background: "bg-white dark:bg-black",
@ -186,52 +148,25 @@ const [drawerContentVariant, resolveDrawerContentVariantProps] = vcn({
base: `fixed ${drawerContentColors.background} ${drawerContentColors.border} transition-all p-4 flex flex-col justify-between gap-8 overflow-auto`,
variants: {
position: {
top: "top-0 w-full max-w-screen rounded-t-lg border-b-2",
bottom: "bottom-0 w-full max-w-screen rounded-b-lg border-t-2",
left: "left-0 h-screen rounded-l-lg border-r-2",
right: "right-0 h-screen rounded-r-lg border-l-2",
},
maxSize: {
sm: "[&.left-0]:max-w-sm [&.right-0]:max-w-sm",
md: "[&.left-0]:max-w-md [&.right-0]:max-w-md",
lg: "[&.left-0]:max-w-lg [&.right-0]:max-w-lg",
xl: "[&.left-0]:max-w-xl [&.right-0]:max-w-xl",
top: "top-0 inset-x-0 w-full max-w-screen rounded-t-lg border-b-2",
bottom: "bottom-0 inset-x-0 w-full max-w-screen rounded-b-lg border-t-2",
left: "left-0 inset-y-0 h-screen rounded-l-lg border-r-2",
right: "right-0 inset-y-0 h-screen rounded-r-lg border-l-2",
},
opened: {
true: "",
false:
"[&.top-0]:-translate-y-full [&.bottom-0]:translate-y-full [&.left-0]:-translate-x-full [&.right-0]:translate-x-full",
},
internal: {
true: "relative",
false: "fixed",
},
},
defaults: {
position: "left",
opened: false,
maxSize: "sm",
internal: false,
},
dynamics: [
({ position, internal }) => {
if (!internal) {
if (["top", "bottom"].includes(position)) {
return "inset-x-0";
}
return "inset-y-0";
}
return "w-fit";
},
],
});
interface DrawerContentProps
extends Omit<
VariantProps<typeof drawerContentVariant>,
"opened" | "internal"
>,
extends Omit<VariantProps<typeof drawerContentVariant>, "opened">,
AsChild,
ComponentPropsWithoutRef<"div"> {}
@ -372,9 +307,9 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
<div
className={drawerContentVariant({
...variantProps,
opened: state.isRendered,
opened: true,
className: dragState.isDragging
? "transition-[width] duration-0"
? "transition-[width_0ms]"
: variantProps.className,
})}
style={
@ -386,7 +321,6 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
0) +
(position === "top" ? dragState.delta : -dragState.delta),
padding: 0,
[`padding${position.slice(0, 1).toUpperCase()}${position.slice(1)}`]: `${dragState.delta}px`,
}
: {
width:
@ -394,7 +328,6 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
0) +
(position === "left" ? dragState.delta : -dragState.delta),
padding: 0,
[`padding${position.slice(0, 1).toUpperCase()}${position.slice(1)}`]: `${dragState.delta}px`,
}
: { width: 0, height: 0, padding: 0 }
}
@ -403,16 +336,14 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
{...restPropsExtracted}
className={drawerContentVariant({
...variantProps,
opened: state.isRendered,
internal: true,
opened: state.opened,
})}
style={{
transform:
dragState.isDragging &&
((["top", "left"].includes(position) && dragState.delta < 0) ||
(["bottom", "right"].includes(position) && dragState.delta > 0))
? `translate${["top", "bottom"].includes(position) ? "Y" : "X"}(${dragState.delta}px)`
: undefined,
transform: dragState.isDragging
? `translate${["top", "bottom"].includes(position) ? "Y" : "X"}(${
dragState.delta
}px)`
: undefined,
transitionDuration: dragState.isDragging ? "0s" : undefined,
userSelect: dragState.isDragging ? "none" : undefined,
}}
@ -443,7 +374,6 @@ const DrawerContent = forwardRef<HTMLDivElement, DrawerContentProps>(
);
},
);
DrawerContent.displayName = "DrawerContent";
const DrawerClose = forwardRef<
HTMLButtonElement,
@ -458,7 +388,6 @@ const DrawerClose = forwardRef<
/>
);
});
DrawerClose.displayName = "DrawerClose";
const [drawerHeaderVariant, resolveDrawerHeaderVariantProps] = vcn({
base: "flex flex-col gap-2",
@ -488,10 +417,9 @@ const DrawerHeader = forwardRef<HTMLDivElement, DrawerHeaderProps>(
);
},
);
DrawerHeader.displayName = "DrawerHeader";
const [drawerBodyVariant, resolveDrawerBodyVariantProps] = vcn({
base: "grow",
base: "flex-grow",
variants: {},
defaults: {},
});
@ -516,7 +444,6 @@ const DrawerBody = forwardRef<HTMLDivElement, DrawerBodyProps>((props, ref) => {
/>
);
});
DrawerBody.displayName = "DrawerBody";
const [drawerFooterVariant, resolveDrawerFooterVariantProps] = vcn({
base: "flex flex-row justify-end gap-2",
@ -546,7 +473,6 @@ const DrawerFooter = forwardRef<HTMLDivElement, DrawerFooterProps>(
);
},
);
DrawerFooter.displayName = "DrawerFooter";
export {
DrawerRoot,

View File

@ -1,183 +0,0 @@
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
import {
type ComponentPropsWithoutRef,
createContext,
forwardRef,
useContext,
useEffect,
useRef,
} from "react";
/**
Form Item Context
**/
interface IFormItemContext {
invalid?: string | null | undefined;
}
const FormItemContext = createContext<IFormItemContext>({});
/**
FormItem
**/
const [formItemVariant, resolveFormItemVariantProps] = vcn({
base: "flex flex-col gap-2 items-start w-full",
variants: {},
defaults: {},
});
interface FormItemProps
extends VariantProps<typeof formItemVariant>,
AsChild,
ComponentPropsWithoutRef<"label"> {
invalid?: string | null | undefined;
}
const FormItem = forwardRef<HTMLLabelElement, FormItemProps>((props, ref) => {
const [variantProps, restPropsCompressed] =
resolveFormItemVariantProps(props);
const { asChild, children, invalid, ...restPropsExtracted } =
restPropsCompressed;
const innerRef = useRef<HTMLLabelElement | null>(null);
useEffect(() => {
const invalidAsString = invalid ? invalid : "";
const input = innerRef.current?.querySelector?.("input");
if (!input) return;
input.setCustomValidity(invalidAsString);
}, [invalid]);
const Comp = asChild ? Slot : "label";
return (
<FormItemContext.Provider value={{ invalid }}>
<Comp
ref={(el: HTMLLabelElement | null) => {
innerRef.current = el;
if (typeof ref === "function") {
ref(el);
} else if (ref) {
ref.current = el;
}
}}
className={formItemVariant(variantProps)}
{...restPropsExtracted}
>
{children}
</Comp>
</FormItemContext.Provider>
);
});
FormItem.displayName = "FormItem";
/**
FormLabel
**/
const [formLabelVariant, resolveFormLabelVariantProps] = vcn({
base: "text-sm font-bold",
variants: {},
defaults: {},
});
interface FormLabelProps
extends VariantProps<typeof formLabelVariant>,
AsChild,
ComponentPropsWithoutRef<"span"> {}
const FormLabel = forwardRef<HTMLSpanElement, FormLabelProps>((props, ref) => {
const [variantProps, otherPropsCompressed] =
resolveFormLabelVariantProps(props);
const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
const Comp = asChild ? Slot : "span";
return (
<Comp
ref={ref}
className={formLabelVariant(variantProps)}
{...otherPropsExtracted}
>
{children}
</Comp>
);
});
FormLabel.displayName = "FormLabel";
/**
FormHelper
**/
const [formHelperVariant, resolveFormHelperVariantProps] = vcn({
base: "opacity-75 text-sm font-light",
variants: {},
defaults: {},
});
interface FormHelperProps
extends VariantProps<typeof formHelperVariant>,
AsChild,
ComponentPropsWithoutRef<"span"> {
hiddenOnInvalid?: boolean;
}
const FormHelper = forwardRef<HTMLSpanElement, FormHelperProps>(
(props, ref) => {
const [variantProps, otherPropsCompressed] =
resolveFormHelperVariantProps(props);
const { asChild, children, hiddenOnInvalid, ...otherPropsExtracted } =
otherPropsCompressed;
const item = useContext(FormItemContext);
if (item.invalid && hiddenOnInvalid) return null;
const Comp = asChild ? Slot : "span";
return (
<Comp
ref={ref}
className={formHelperVariant(variantProps)}
{...otherPropsExtracted}
>
{children}
</Comp>
);
},
);
FormHelper.displayName = "FormHelper";
/**
FormError
**/
const [formErrorVariant, resolveFormErrorVariantProps] = vcn({
base: "text-sm text-red-500",
variants: {},
defaults: {},
});
interface FormErrorProps
extends VariantProps<typeof formErrorVariant>,
AsChild,
Omit<ComponentPropsWithoutRef<"span">, "children"> {}
const FormError = forwardRef<HTMLSpanElement, FormErrorProps>((props, ref) => {
const [variantProps, otherPropsCompressed] =
resolveFormErrorVariantProps(props);
const { asChild, ...otherPropsExtracted } = otherPropsCompressed;
const item = useContext(FormItemContext);
const Comp = asChild ? Slot : "span";
return item.invalid ? (
<Comp
ref={ref}
className={formErrorVariant(variantProps)}
{...otherPropsExtracted}
>
{item.invalid}
</Comp>
) : null;
});
FormError.displayName = "FormError";
export { FormItem, FormLabel, FormHelper, FormError };

View File

@ -1,38 +1,37 @@
import { type AsChild, Slot, type VariantProps, vcn } from "@pswui-lib";
import { type VariantProps, vcn } from "@pswui-lib";
import React from "react";
const inputColors = {
background: {
default: "bg-neutral-50 dark:bg-neutral-900",
hover:
"hover:bg-neutral-100 dark:hover:bg-neutral-800 has-[input:hover]:bg-neutral-100 dark:has-[input:hover]:bg-neutral-800",
hover: "hover:bg-neutral-100 dark:hover:bg-neutral-800",
invalid:
"invalid:bg-red-100 dark:invalid:bg-red-900 has-[input:invalid]:bg-red-100 dark:has-[input:invalid]:bg-red-900",
"invalid:bg-red-100 invalid:dark:bg-red-900 has-[input:invalid]:bg-red-100 dark:has-[input:invalid]:bg-red-900",
invalidHover:
"hover:invalid:bg-red-200 dark:hover:invalid:bg-red-800 has-[input:invalid:hover]:bg-red-200 dark:has-[input:invalid:hover]:bg-red-800",
},
border: {
default: "border-neutral-400 dark:border-neutral-600",
invalid:
"invalid:border-red-400 dark:invalid:border-red-600 has-[input:invalid]:border-red-400 dark:has-[input:invalid]:border-red-600",
"invalid:border-red-400 invalid:dark:border-red-600 has-[input:invalid]:border-red-400 dark:has-[input:invalid]:border-red-600",
},
ring: {
default: "ring-transparent focus-within:ring-current",
invalid:
"invalid:focus-within:ring-red-400 dark:invalid:focus-within:ring-red-600 has-[input:invalid]:focus-within:ring-red-400 dark:has-[input:invalid]:focus-within:ring-red-600",
"invalid:focus-within:ring-red-400 invalid:focus-within:dark:ring-red-600 has-[input:invalid]:focus-within:ring-red-400 dark:has-[input:invalid]:focus-within:ring-red-600",
},
};
const [inputVariant, resolveInputVariantProps] = vcn({
base: `rounded-md p-2 border ring-1 outline-hidden transition-all duration-200 [appearance:textfield] disabled:brightness-50 disabled:saturate-0 disabled:cursor-not-allowed ${inputColors.background.default} ${inputColors.background.hover} ${inputColors.border.default} ${inputColors.ring.default} ${inputColors.background.invalid} ${inputColors.background.invalidHover} ${inputColors.border.invalid} ${inputColors.ring.invalid} [&:has(input)]:flex`,
base: `rounded-md p-2 border ring-1 outline-none transition-all duration-200 [appearance:textfield] disabled:brightness-50 disabled:saturate-0 disabled:cursor-not-allowed ${inputColors.background.default} ${inputColors.background.hover} ${inputColors.border.default} ${inputColors.ring.default} ${inputColors.background.invalid} ${inputColors.background.invalidHover} ${inputColors.border.invalid} ${inputColors.ring.invalid} [&:has(input)]:flex [&:has(input)]:w-fit`,
variants: {
unstyled: {
true: "bg-transparent border-none p-0 ring-0 hover:bg-transparent invalid:hover:bg-transparent invalid:focus-within:bg-transparent invalid:focus-within:ring-0",
false: "",
},
full: {
true: "[&:has(input)]:w-full w-full",
false: "[&:has(input)]:w-fit w-fit",
true: "w-full",
false: "w-fit",
},
},
defaults: {
@ -43,8 +42,7 @@ const [inputVariant, resolveInputVariantProps] = vcn({
interface InputFrameProps
extends VariantProps<typeof inputVariant>,
React.ComponentPropsWithoutRef<"label">,
AsChild {
React.ComponentPropsWithoutRef<"label"> {
children?: React.ReactNode;
}
@ -52,22 +50,19 @@ const InputFrame = React.forwardRef<HTMLLabelElement, InputFrameProps>(
(props, ref) => {
const [variantProps, otherPropsCompressed] =
resolveInputVariantProps(props);
const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
const Comp = asChild ? Slot : "label";
const { children, ...otherPropsExtracted } = otherPropsCompressed;
return (
<Comp
<label
ref={ref}
className={`group/input-frame ${inputVariant(variantProps)}`}
{...otherPropsExtracted}
>
{children}
</Comp>
</label>
);
},
);
InputFrame.displayName = "InputFrame";
interface InputProps
extends VariantProps<typeof inputVariant>,
@ -118,6 +113,5 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => {
/>
);
});
Input.displayName = "Input";
export { InputFrame, Input };

View File

@ -29,6 +29,5 @@ const Label = React.forwardRef<HTMLLabelElement, LabelProps>((props, ref) => {
/>
);
});
Label.displayName = "Label";
export { Label };

View File

@ -65,7 +65,7 @@ const popoverColors = {
};
const [popoverContentVariant, resolvePopoverContentVariantProps] = vcn({
base: `absolute transition-all duration-150 border rounded-lg p-0.5 z-10 *:w-full ${popoverColors.background} ${popoverColors.border}`,
base: `absolute transition-all duration-150 border rounded-lg p-0.5 [&>*]:w-full z-10 ${popoverColors.background} ${popoverColors.border}`,
variants: {
direction: {
row: "",
@ -200,7 +200,7 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
(props, ref) => {
const [variantProps, otherPropsCompressed] =
resolvePopoverContentVariantProps(props);
const { children, asChild, ...otherPropsExtracted } = otherPropsCompressed;
const { children, ...otherPropsExtracted } = otherPropsCompressed;
const [state, setState] = useContext(PopoverContext);
const internalRef = useRef<HTMLDivElement | null>(null);
@ -221,16 +221,14 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
};
}, [state.controlled, setState]);
const Comp = asChild ? Slot : "div";
return (
<Comp
<div
{...otherPropsExtracted}
className={popoverContentVariant({
...variantProps,
opened: state.opened,
})}
ref={(el: HTMLDivElement) => {
ref={(el) => {
internalRef.current = el;
if (typeof ref === "function") {
ref(el);
@ -240,10 +238,9 @@ const PopoverContent = React.forwardRef<HTMLDivElement, PopoverContentProps>(
}}
>
{children}
</Comp>
</div>
);
},
);
PopoverContent.displayName = "PopoverContent";
export { Popover, PopoverTrigger, PopoverContent };

View File

@ -80,6 +80,5 @@ const Switch = React.forwardRef<HTMLInputElement, SwitchProps>((props, ref) => {
</label>
);
});
Switch.displayName = "Switch";
export { Switch };

View File

@ -39,7 +39,7 @@ const TabList = (props: TabListProps) => {
};
const [TabTriggerVariant, resolveTabTriggerVariantProps] = vcn({
base: "py-1.5 rounded-md grow transition-all text-sm",
base: "py-1.5 rounded-md flex-grow transition-all text-sm",
variants: {
active: {
true: "bg-white/100 dark:bg-black/100 text-black dark:text-white",

View File

@ -1,4 +1,4 @@
import { type VariantProps, useDocument, vcn } from "@pswui-lib";
import { type VariantProps, vcn } from "@pswui-lib";
import React, { useEffect, useId, useRef } from "react";
import ReactDOM from "react-dom";
@ -168,10 +168,6 @@ const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>((props, ref) => {
};
}, [defaultOption]);
const document = useDocument();
if (!document) return null;
const toasterInstance = document.querySelector("div[data-toaster-root]");
if (toasterInstance && id !== toasterInstance.id) {
if (process.env.NODE_ENV === "development" && !muteDuplicationWarning) {
@ -212,6 +208,5 @@ const Toaster = React.forwardRef<HTMLDivElement, ToasterProps>((props, ref) => {
</>
);
});
Toaster.displayName = "Toaster";
export { Toaster };

View File

@ -71,7 +71,6 @@ const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>((props, ref) => {
</TooltipContext.Provider>
);
});
Tooltip.displayName = "Tooltip";
const tooltipContentColors = {
variants: {
@ -84,10 +83,10 @@ const tooltipContentColors = {
};
const [tooltipContentVariant, resolveTooltipContentVariantProps] = vcn({
base: `absolute py-1 px-3 rounded-md border opacity-0 transition-all
group-[:not(.controlled):hover]/tooltip:opacity-100 group-[.opened]/tooltip:opacity-100
select-none pointer-events-none
group-[:not(.controlled):hover]/tooltip:select-auto group-[.opened]/tooltip:select-auto group-[:not(.controlled):hover]/tooltip:pointer-events-auto group-[.opened]/tooltip:pointer-events-auto
base: `absolute py-1 px-3 rounded-md border opacity-0 transition-all
group-[:not(.controlled):hover]/tooltip:opacity-100 group-[.opened]/tooltip:opacity-100
select-none pointer-events-none
group-[:not(.controlled):hover]/tooltip:select-auto group-[.opened]/tooltip:select-auto group-[:not(.controlled):hover]/tooltip:pointer-events-auto group-[.opened]/tooltip:pointer-events-auto
group-[:not(.controlled):hover]/tooltip:[transition:transform_150ms_ease-out_var(--delay),opacity_150ms_ease-out_var(--delay),background-color_150ms_ease-in-out,color_150ms_ease-in-out,border-color_150ms_ease-in-out]`,
variants: {
position: {
@ -140,12 +139,10 @@ const TooltipContent = React.forwardRef<HTMLDivElement, TooltipContentProps>(
...variantProps,
position: contextState.position,
})}
role="tooltip"
{...rest}
/>
);
},
);
TooltipContent.displayName = "TooltipContent";
export { Tooltip, TooltipContent };

View File

@ -85,10 +85,7 @@ export const Slot = React.forwardRef<
return null;
}
return React.cloneElement(children, {
...mergeReactProps(
safeSlotProps,
children.props as Record<string, unknown>,
),
...mergeReactProps(safeSlotProps, children.props),
ref: combinedRef([
ref,
(children as unknown as { ref: React.Ref<HTMLElement> }).ref,

View File

@ -1,4 +1,2 @@
export * from "./vcn";
export * from "./Slot";
export * from "./useDocument";
export * from "./useAnimatedMount";

View File

@ -1,85 +0,0 @@
import { type MutableRefObject, useCallback, useEffect, useState } from "react";
function getCalculatedTransitionDuration(
ref: MutableRefObject<HTMLElement>,
): number {
let transitionDuration: {
value: number;
unit: string;
} | null;
if (ref.current.computedStyleMap !== undefined) {
transitionDuration = ref.current
.computedStyleMap()
.get("transition-duration") as { value: number; unit: string };
} else {
const style = /(\d+(\.\d+)?)(.+)/.exec(
window.getComputedStyle(ref.current).transitionDuration,
);
if (!style) return 0;
transitionDuration = {
value: Number.parseFloat(style[1] ?? "0"),
unit: style[3] ?? style[2] ?? "s",
};
}
return (
transitionDuration.value *
({
s: 1000,
ms: 1,
}[transitionDuration.unit] ?? 1)
);
}
/*
* isMounted: true isRendered: true isRendered: false isMounted: false
* Component Mount Component Appear Component Disappear Component Unmount
* v v v v
* |-|=================|------------------------|======================|-|
*/
function useAnimatedMount(
visible: boolean,
ref: MutableRefObject<HTMLElement | null>,
callbacks?: { onMount: () => void; onUnmount: () => void },
) {
const [state, setState] = useState<{
isMounted: boolean;
isRendered: boolean;
}>({ isMounted: visible, isRendered: visible });
const umountCallback = useCallback(() => {
setState((p) => ({ ...p, isRendered: false }));
const calculatedTransitionDuration = ref.current
? getCalculatedTransitionDuration(ref as MutableRefObject<HTMLElement>)
: 0;
setTimeout(() => {
setState((p) => ({ ...p, isMounted: false }));
callbacks?.onUnmount?.();
}, calculatedTransitionDuration);
}, [ref, callbacks]);
const mountCallback = useCallback(() => {
setState((p) => ({ ...p, isMounted: true }));
callbacks?.onMount?.();
requestAnimationFrame(function onMount() {
if (!ref.current) return requestAnimationFrame(onMount);
setState((p) => ({ ...p, isRendered: true }));
});
}, [ref.current, callbacks]);
useEffect(() => {
console.log(state);
if (!visible && state.isRendered) {
umountCallback();
} else if (visible && !state.isMounted) {
mountCallback();
}
}, [state, visible, mountCallback, umountCallback]);
return state;
}
export { getCalculatedTransitionDuration, useAnimatedMount };

View File

@ -1,21 +0,0 @@
"use client";
import { useEffect, useState } from "react";
/**
* This hook allows components to use `document` as like they're always in the client side.
* Return undefined if there is no `document` (which represents it's server side) or initial render(to avoid hydration error).
*/
function useDocument(): undefined | Document {
const [initialRender, setInitialState] = useState(true);
useEffect(() => {
setInitialState(false);
}, []);
if (typeof document === "undefined" || initialRender) return undefined;
return document;
}
export { useDocument };

View File

@ -270,7 +270,6 @@ export function vcn<
for (const [variantName, variantKey] of Object.entries(
otherVariantProps,
) as VariantKVEntry<V>) {
if (typeof variantKey === "undefined") continue;
kv[variantName] = variantKey;
}

View File

@ -1,41 +1,8 @@
@import url('https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css')
layer(base);
@import 'tailwindcss';
@plugin '@tailwindcss/typography';
@plugin 'tailwind-scrollbar';
@source '../{components,stories,src}/**/*.{js,jsx,ts,tsx,css,mdx}';
@custom-variant dark {
@media (prefers-color-scheme: dark) {
&:is(.system *) {
@slot;
}
}
&:is(.dark *) {
@slot;
}
}
/*
The default border color has changed to `currentColor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer base {
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-gray-200, currentColor);
}
}
@import url("https://cdn.jsdelivr.net/gh/wanteddev/wanted-sans@v1.0.3/packages/wanted-sans/fonts/webfonts/variable/split/WantedSansVariable.min.css");
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--dark-bg-color: #000;

15
tailwind.config.js Normal file
View File

@ -0,0 +1,15 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./{components,stories,src}/**/*.{js,jsx,ts,tsx,css,mdx}"],
darkMode: [
"variant",
[
"@media (prefers-color-scheme: dark) { &:is(.system *) }",
"&:is(.dark *)",
],
],
theme: {
extend: {},
},
plugins: [require("@tailwindcss/typography"), require("tailwind-scrollbar")],
};

View File

@ -2,10 +2,10 @@ import { resolve } from "node:path";
import mdx from "@mdx-js/rollup";
import withToc from "@stefanprobst/rehype-extract-toc";
import withTocExport from "@stefanprobst/rehype-extract-toc/mdx";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import withSlug from "rehype-slug";
import remarkGfm from "remark-gfm";
import tailwindcss from "tailwindcss";
import { defineConfig } from "vite";
import dynamicImport from "vite-plugin-dynamic-import";
@ -13,13 +13,17 @@ import dynamicImport from "vite-plugin-dynamic-import";
export default defineConfig({
plugins: [
react(),
tailwindcss(),
mdx({
rehypePlugins: [withSlug, withToc, withTocExport],
remarkPlugins: [remarkGfm],
}),
dynamicImport(),
],
css: {
postcss: {
plugins: [tailwindcss()],
},
},
resolve: {
alias: {
"@pswui": resolve(__dirname, "./src/pswui/components"),

4020
yarn.lock

File diff suppressed because it is too large Load Diff