mirror of
https://github.com/makeplane/plane.git
synced 2026-02-14 01:59:28 -06:00
[WEB-5083] chore: add stories to propel #7888
This commit is contained in:
@@ -11,7 +11,7 @@ function getAbsolutePath(value: string) {
|
||||
}
|
||||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(ts|tsx)"],
|
||||
addons: ["@storybook/addon-designs", "@storybook/addon-docs"],
|
||||
addons: [getAbsolutePath("@storybook/addon-designs"), getAbsolutePath("@storybook/addon-docs")],
|
||||
framework: {
|
||||
name: getAbsolutePath("@storybook/react-vite"),
|
||||
options: {},
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
"@plane/types": "workspace:*",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"frimousse": "^0.3.0",
|
||||
"lucide-react": "catalog:",
|
||||
"react": "catalog:",
|
||||
@@ -71,12 +71,12 @@
|
||||
"@plane/tailwind-config": "workspace:*",
|
||||
"@plane/typescript-config": "workspace:*",
|
||||
"@storybook/addon-designs": "10.0.2",
|
||||
"@storybook/addon-docs": "9.1.2",
|
||||
"@storybook/react-vite": "9.1.2",
|
||||
"@storybook/addon-docs": "9.1.10",
|
||||
"@storybook/react-vite": "9.1.10",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"eslint-plugin-storybook": "9.1.2",
|
||||
"storybook": "9.1.2",
|
||||
"eslint-plugin-storybook": "9.1.10",
|
||||
"storybook": "9.1.10",
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
|
||||
198
packages/propel/src/accordion/accordion.stories.tsx
Normal file
198
packages/propel/src/accordion/accordion.stories.tsx
Normal file
@@ -0,0 +1,198 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Accordion } from "./accordion";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Accordion",
|
||||
component: Accordion.Root,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
controls: { disable: true },
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
subcomponents: {
|
||||
Item: Accordion.Item,
|
||||
Trigger: Accordion.Trigger,
|
||||
Content: Accordion.Content,
|
||||
},
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
} satisfies Meta<typeof Accordion.Root>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>What is Plane?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Plane is an open-source project management tool designed for developers and teams to plan, track, and manage
|
||||
their work efficiently.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>How do I get started?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
You can get started by signing up for an account, creating your first workspace, and inviting your team
|
||||
members to collaborate.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Is it free to use?</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
Plane offers both free and paid plans. The free plan includes essential features for small teams, while paid
|
||||
plans unlock advanced functionality.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SingleOpen: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root defaultValue={["item-1"]} className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>Section 1</Accordion.Trigger>
|
||||
<Accordion.Content>Content for section 1. Only one section can be open at a time.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>Section 2</Accordion.Trigger>
|
||||
<Accordion.Content>Content for section 2.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Section 3</Accordion.Trigger>
|
||||
<Accordion.Content>Content for section 3.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllowMultiple: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root allowMultiple defaultValue={["item-1", "item-2"]} className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>First Section</Accordion.Trigger>
|
||||
<Accordion.Content>Multiple sections can be open at the same time.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger>Second Section</Accordion.Trigger>
|
||||
<Accordion.Content>This section is also open by default.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Third Section</Accordion.Trigger>
|
||||
<Accordion.Content>You can open this section while keeping the others open.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDisabledItem: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger>Enabled Section</Accordion.Trigger>
|
||||
<Accordion.Content>This section can be toggled.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2" disabled>
|
||||
<Accordion.Trigger>Disabled Section</Accordion.Trigger>
|
||||
<Accordion.Content>This content cannot be accessed.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-3">
|
||||
<Accordion.Trigger>Another Enabled Section</Accordion.Trigger>
|
||||
<Accordion.Content>This section can also be toggled.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomIcon: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger
|
||||
icon={
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="transition-transform group-data-[panel-open]:rotate-180"
|
||||
>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
Custom Chevron Icon
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
This accordion uses a custom chevron icon instead of the default plus icon.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger
|
||||
icon={
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
className="transition-transform group-data-[panel-open]:rotate-180"
|
||||
>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg>
|
||||
}
|
||||
>
|
||||
Another Section
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>All items in this accordion use the custom icon.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AsChildTrigger: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Accordion.Root className="w-96">
|
||||
<Accordion.Item value="item-1">
|
||||
<Accordion.Trigger asChild>
|
||||
<button className="w-full rounded-md bg-blue-500 px-4 py-2 text-left text-white hover:bg-blue-600">
|
||||
Custom Button Trigger
|
||||
</button>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>
|
||||
When using asChild, you can completely customize the trigger element without the default icon wrapper.
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item value="item-2">
|
||||
<Accordion.Trigger asChild>
|
||||
<button className="w-full rounded-md bg-green-500 px-4 py-2 text-left text-white hover:bg-green-600">
|
||||
Another Custom Trigger
|
||||
</button>
|
||||
</Accordion.Trigger>
|
||||
<Accordion.Content>This gives you full control over the trigger styling and behavior.</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion.Root>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -2,21 +2,21 @@ import * as React from "react";
|
||||
import { Accordion as BaseAccordion } from "@base-ui-components/react";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
interface AccordionRootProps {
|
||||
export interface AccordionRootProps {
|
||||
defaultValue?: string[];
|
||||
allowMultiple?: boolean;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface AccordionItemProps {
|
||||
export interface AccordionItemProps {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface AccordionTriggerProps {
|
||||
export interface AccordionTriggerProps {
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
@@ -24,7 +24,7 @@ interface AccordionTriggerProps {
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
interface AccordionContentProps {
|
||||
export interface AccordionContentProps {
|
||||
className?: string;
|
||||
contentWrapperClassName?: string;
|
||||
children: React.ReactNode;
|
||||
|
||||
@@ -1,55 +1,333 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { AnimatedCounter } from "./animated-counter";
|
||||
|
||||
const meta: Meta<typeof AnimatedCounter> = {
|
||||
const meta = {
|
||||
title: "Components/AnimatedCounter",
|
||||
component: AnimatedCounter,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["sm", "md", "lg"],
|
||||
},
|
||||
args: {
|
||||
size: "md",
|
||||
count: 0,
|
||||
},
|
||||
};
|
||||
} satisfies Meta<typeof AnimatedCounter>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof AnimatedCounter>;
|
||||
|
||||
const AnimatedCounterDemo = (args: React.ComponentProps<typeof AnimatedCounter>) => {
|
||||
const [count, setCount] = useState(args.count || 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex items-center justify-center gap-6">
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white font-medium rounded-lg hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2 transition-colors shadow-md"
|
||||
onClick={() => setCount((prev) => Math.max(0, prev - 1))}
|
||||
>
|
||||
-1
|
||||
</button>
|
||||
<div className="flex items-center justify-center min-w-[60px] h-12 bg-gray-50 border border-gray-200 rounded-lg">
|
||||
<AnimatedCounter {...args} count={count} />
|
||||
</div>
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white font-medium rounded-lg hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2 transition-colors shadow-md"
|
||||
onClick={() => setCount((prev) => prev + 1)}
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => <AnimatedCounterDemo {...args} />,
|
||||
args: {
|
||||
count: 5,
|
||||
size: "md",
|
||||
render(args) {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex items-center justify-center gap-6">
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white font-medium rounded-lg hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2 transition-colors shadow-md"
|
||||
onClick={() => setCount((prev) => Math.max(0, prev - 1))}
|
||||
>
|
||||
-1
|
||||
</button>
|
||||
<div className="flex items-center justify-center min-w-[60px] h-12 bg-gray-50 border border-gray-200 rounded-lg">
|
||||
<AnimatedCounter {...args} count={count} />
|
||||
</div>
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white font-medium rounded-lg hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-400 focus:ring-offset-2 transition-colors shadow-md"
|
||||
onClick={() => setCount((prev) => prev + 1)}
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render() {
|
||||
const [count, setCount] = useState(42);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="px-3 py-1 bg-custom-background-80 text-sm rounded hover:bg-custom-background-90"
|
||||
onClick={() => setCount((prev) => Math.max(0, prev - 1))}
|
||||
>
|
||||
-1
|
||||
</button>
|
||||
<button
|
||||
className="px-3 py-1 bg-custom-background-80 text-sm rounded hover:bg-custom-background-90"
|
||||
onClick={() => setCount((prev) => prev + 1)}
|
||||
>
|
||||
+1
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-custom-text-400 w-20">Small:</span>
|
||||
<div className="flex items-center justify-center min-w-[40px] h-8 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={count} size="sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-custom-text-400 w-20">Medium:</span>
|
||||
<div className="flex items-center justify-center min-w-[50px] h-10 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={count} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-custom-text-400 w-20">Large:</span>
|
||||
<div className="flex items-center justify-center min-w-[60px] h-12 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={count} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeNumbers: Story = {
|
||||
render() {
|
||||
const [count, setCount] = useState(1234567);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
className="px-3 py-1 bg-red-500 text-white text-sm rounded hover:bg-red-600"
|
||||
onClick={() => setCount((prev) => Math.max(0, prev - 1000))}
|
||||
>
|
||||
-1000
|
||||
</button>
|
||||
<button
|
||||
className="px-3 py-1 bg-green-500 text-white text-sm rounded hover:bg-green-600"
|
||||
onClick={() => setCount((prev) => prev + 1000)}
|
||||
>
|
||||
+1000
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center justify-center min-w-[100px] h-12 bg-custom-background-80 border border-custom-border-200 rounded-lg">
|
||||
<AnimatedCounter count={count} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Countdown: Story = {
|
||||
render() {
|
||||
const [count, setCount] = useState(10);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRunning && count > 0) {
|
||||
const timer = setTimeout(() => setCount((prev) => prev - 1), 1000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
if (count === 0) {
|
||||
setIsRunning(false);
|
||||
}
|
||||
}, [count, isRunning]);
|
||||
|
||||
const handleStart = () => {
|
||||
setCount(10);
|
||||
setIsRunning(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center justify-center min-w-[60px] h-16 bg-custom-background-80 border-2 border-custom-border-200 rounded-lg">
|
||||
<AnimatedCounter count={count} size="lg" className="text-2xl" />
|
||||
</div>
|
||||
<button
|
||||
className="px-6 py-2 bg-custom-primary-100 text-white font-medium rounded-lg hover:bg-custom-primary-200"
|
||||
onClick={handleStart}
|
||||
disabled={isRunning}
|
||||
>
|
||||
{isRunning ? "Counting..." : "Start Countdown"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LiveCounter: Story = {
|
||||
render() {
|
||||
const [count, setCount] = useState(0);
|
||||
const [isRunning, setIsRunning] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRunning) {
|
||||
const timer = setInterval(() => setCount((prev) => prev + 1), 500);
|
||||
return () => clearInterval(timer);
|
||||
}
|
||||
}, [isRunning]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center justify-center min-w-[80px] h-16 bg-custom-background-80 border-2 border-custom-border-200 rounded-lg">
|
||||
<AnimatedCounter count={count} size="lg" className="text-2xl" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="px-4 py-2 bg-green-500 text-white font-medium rounded hover:bg-green-600"
|
||||
onClick={() => setIsRunning(true)}
|
||||
disabled={isRunning}
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-red-500 text-white font-medium rounded hover:bg-red-600"
|
||||
onClick={() => setIsRunning(false)}
|
||||
disabled={!isRunning}
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-gray-500 text-white font-medium rounded hover:bg-gray-600"
|
||||
onClick={() => {
|
||||
setIsRunning(false);
|
||||
setCount(0);
|
||||
}}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleCounters: Story = {
|
||||
render() {
|
||||
const [likes, setLikes] = useState(42);
|
||||
const [comments, setComments] = useState(15);
|
||||
const [shares, setShares] = useState(8);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="max-w-md border border-custom-border-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="font-medium">Engagement Stats</h3>
|
||||
</div>
|
||||
<div className="flex gap-4">
|
||||
<div className="flex-1 flex flex-col items-center gap-2">
|
||||
<div className="text-custom-text-400 text-sm">Likes</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="w-8 h-8 flex items-center justify-center bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setLikes((prev) => prev + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div className="flex items-center justify-center min-w-[40px] h-10 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={likes} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col items-center gap-2">
|
||||
<div className="text-custom-text-400 text-sm">Comments</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="w-8 h-8 flex items-center justify-center bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setComments((prev) => prev + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div className="flex items-center justify-center min-w-[40px] h-10 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={comments} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col items-center gap-2">
|
||||
<div className="text-custom-text-400 text-sm">Shares</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="w-8 h-8 flex items-center justify-center bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setShares((prev) => prev + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<div className="flex items-center justify-center min-w-[40px] h-10 bg-custom-background-80 border border-custom-border-200 rounded">
|
||||
<AnimatedCounter count={shares} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InBadge: Story = {
|
||||
render() {
|
||||
const [notifications, setNotifications] = useState(3);
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="relative">
|
||||
<button className="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded-lg">
|
||||
Notifications
|
||||
</button>
|
||||
<div className="absolute -top-2 -right-2 min-w-[24px] h-6 flex items-center justify-center bg-red-500 text-white rounded-full px-1.5">
|
||||
<AnimatedCounter count={notifications} size="sm" className="text-xs font-medium" />
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="px-4 py-2 bg-custom-primary-100 text-white rounded hover:bg-custom-primary-200"
|
||||
onClick={() => setNotifications((prev) => prev + 1)}
|
||||
>
|
||||
Add Notification
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const FastAnimation: Story = {
|
||||
render() {
|
||||
const [count, setCount] = useState(0);
|
||||
|
||||
const incrementFast = () => {
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
setTimeout(() => setCount((prev) => prev + 1), i * 50);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-4">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="flex items-center justify-center min-w-[60px] h-12 bg-custom-background-80 border border-custom-border-200 rounded-lg">
|
||||
<AnimatedCounter count={count} size="lg" />
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
className="px-4 py-2 bg-custom-primary-100 text-white rounded hover:bg-custom-primary-200"
|
||||
onClick={incrementFast}
|
||||
>
|
||||
+10 Fast
|
||||
</button>
|
||||
<button
|
||||
className="px-4 py-2 bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setCount(0)}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
171
packages/propel/src/avatar/avatar.stories.tsx
Normal file
171
packages/propel/src/avatar/avatar.stories.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Avatar } from "./avatar";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Avatar",
|
||||
component: Avatar,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
name: "John Doe",
|
||||
src: "https://i.pravatar.cc/150?img=1",
|
||||
},
|
||||
} satisfies Meta<typeof Avatar>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithName: Story = {
|
||||
args: {
|
||||
name: "Jane Smith",
|
||||
src: "https://i.pravatar.cc/150?img=5",
|
||||
},
|
||||
};
|
||||
|
||||
export const Fallback: Story = {
|
||||
args: {
|
||||
name: "Alice Johnson",
|
||||
src: "invalid-url",
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackWithCustomColor: Story = {
|
||||
args: {
|
||||
name: "Bob Wilson",
|
||||
src: "invalid-url",
|
||||
fallbackBackgroundColor: "#3b82f6",
|
||||
fallbackTextColor: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackWithCustomText: Story = {
|
||||
args: {
|
||||
fallbackText: "AB",
|
||||
src: "invalid-url",
|
||||
fallbackBackgroundColor: "#10b981",
|
||||
fallbackTextColor: "#ffffff",
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
name: "Small Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=2",
|
||||
size: "sm",
|
||||
},
|
||||
};
|
||||
|
||||
export const Medium: Story = {
|
||||
args: {
|
||||
name: "Medium Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=3",
|
||||
size: "md",
|
||||
},
|
||||
};
|
||||
|
||||
export const Base: Story = {
|
||||
args: {
|
||||
name: "Base Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=4",
|
||||
size: "base",
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
name: "Large Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=6",
|
||||
size: "lg",
|
||||
},
|
||||
};
|
||||
|
||||
export const CircleShape: Story = {
|
||||
args: {
|
||||
name: "Circle Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=7",
|
||||
shape: "circle",
|
||||
},
|
||||
};
|
||||
|
||||
export const SquareShape: Story = {
|
||||
args: {
|
||||
name: "Square Avatar",
|
||||
src: "https://i.pravatar.cc/150?img=8",
|
||||
shape: "square",
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
parameters: {
|
||||
controls: { disable: true },
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar name="Small" src="https://i.pravatar.cc/150?img=10" size="sm" />
|
||||
<Avatar name="Medium" src="https://i.pravatar.cc/150?img=11" size="md" />
|
||||
<Avatar name="Base" src="https://i.pravatar.cc/150?img=12" size="base" />
|
||||
<Avatar name="Large" src="https://i.pravatar.cc/150?img=13" size="lg" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllShapes: Story = {
|
||||
parameters: {
|
||||
controls: { disable: true },
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar name="Circle" src="https://i.pravatar.cc/150?img=14" shape="circle" />
|
||||
<Avatar name="Square" src="https://i.pravatar.cc/150?img=15" shape="square" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const FallbackVariations: Story = {
|
||||
parameters: {
|
||||
controls: { disable: true },
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Avatar name="Alice" src="invalid-url" fallbackBackgroundColor="#ef4444" fallbackTextColor="#ffffff" />
|
||||
<Avatar name="Bob" src="invalid-url" fallbackBackgroundColor="#f59e0b" fallbackTextColor="#ffffff" />
|
||||
<Avatar name="Charlie" src="invalid-url" fallbackBackgroundColor="#10b981" fallbackTextColor="#ffffff" />
|
||||
<Avatar name="David" src="invalid-url" fallbackBackgroundColor="#3b82f6" fallbackTextColor="#ffffff" />
|
||||
<Avatar name="Eve" src="invalid-url" fallbackBackgroundColor="#8b5cf6" fallbackTextColor="#ffffff" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AvatarGroup: Story = {
|
||||
parameters: {
|
||||
controls: { disable: true },
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex -space-x-2">
|
||||
<Avatar name="User 1" src="https://i.pravatar.cc/150?img=20" size="md" className="ring-2 ring-white" />
|
||||
<Avatar name="User 2" src="https://i.pravatar.cc/150?img=21" size="md" className="ring-2 ring-white" />
|
||||
<Avatar name="User 3" src="https://i.pravatar.cc/150?img=22" size="md" className="ring-2 ring-white" />
|
||||
<Avatar name="User 4" src="https://i.pravatar.cc/150?img=23" size="md" className="ring-2 ring-white" />
|
||||
<Avatar
|
||||
fallbackText="+5"
|
||||
src="invalid-url"
|
||||
size="md"
|
||||
fallbackBackgroundColor="#6b7280"
|
||||
fallbackTextColor="#ffffff"
|
||||
className="ring-2 ring-white"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,56 +1,22 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Button } from "./button";
|
||||
import type { TButtonVariant, TButtonSizes } from "./helper";
|
||||
|
||||
const buttonVariants: TButtonVariant[] = [
|
||||
"primary",
|
||||
"accent-primary",
|
||||
"outline-primary",
|
||||
"neutral-primary",
|
||||
"link-primary",
|
||||
"danger",
|
||||
"accent-danger",
|
||||
"outline-danger",
|
||||
"link-danger",
|
||||
"tertiary-danger",
|
||||
"link-neutral",
|
||||
];
|
||||
|
||||
const buttonSizes: TButtonSizes[] = ["sm", "md", "lg", "xl"];
|
||||
|
||||
const meta: Meta<typeof Button> = {
|
||||
const meta = {
|
||||
title: "Components/Button",
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: buttonVariants,
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: buttonSizes,
|
||||
},
|
||||
loading: {
|
||||
control: "boolean",
|
||||
},
|
||||
disabled: {
|
||||
control: "boolean",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Button>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: "Button",
|
||||
},
|
||||
};
|
||||
} satisfies Meta<typeof Button>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
@@ -212,62 +178,68 @@ export const WithAppendIcon: Story = {
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Primary Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="accent-primary">Accent Primary</Button>
|
||||
<Button variant="outline-primary">Outline Primary</Button>
|
||||
<Button variant="neutral-primary">Neutral Primary</Button>
|
||||
<Button variant="link-primary">Link Primary</Button>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Primary Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="accent-primary">Accent Primary</Button>
|
||||
<Button variant="outline-primary">Outline Primary</Button>
|
||||
<Button variant="neutral-primary">Neutral Primary</Button>
|
||||
<Button variant="link-primary">Link Primary</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Danger Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="danger">Danger</Button>
|
||||
<Button variant="accent-danger">Accent Danger</Button>
|
||||
<Button variant="outline-danger">Outline Danger</Button>
|
||||
<Button variant="link-danger">Link Danger</Button>
|
||||
<Button variant="tertiary-danger">Tertiary Danger</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Other Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="link-neutral">Link Neutral</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Danger Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="danger">Danger</Button>
|
||||
<Button variant="accent-danger">Accent Danger</Button>
|
||||
<Button variant="outline-danger">Outline Danger</Button>
|
||||
<Button variant="link-danger">Link Danger</Button>
|
||||
<Button variant="tertiary-danger">Tertiary Danger</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Other Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="link-neutral">Link Neutral</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="xl">Extra Large</Button>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="md">Medium</Button>
|
||||
<Button size="lg">Large</Button>
|
||||
<Button size="xl">Extra Large</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllStates: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Button States</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button>Default</Button>
|
||||
<Button loading>Loading</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">Button States</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button>Default</Button>
|
||||
<Button loading>Loading</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ComponentProps, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import type { DateRange } from "react-day-picker";
|
||||
import { Calendar } from "./root";
|
||||
|
||||
type CalendarProps = ComponentProps<typeof Calendar>;
|
||||
|
||||
const meta: Meta<CalendarProps> = {
|
||||
const meta = {
|
||||
title: "Components/Calendar",
|
||||
component: Calendar,
|
||||
parameters: {
|
||||
@@ -13,14 +12,13 @@ const meta: Meta<CalendarProps> = {
|
||||
args: {
|
||||
showOutsideDays: true,
|
||||
},
|
||||
};
|
||||
} satisfies Meta<typeof Calendar>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<CalendarProps>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {},
|
||||
render: (args: CalendarProps) => {
|
||||
export const SingleDate: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>(new Date());
|
||||
|
||||
return (
|
||||
@@ -30,3 +28,167 @@ export const Default: Story = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleDates: Story = {
|
||||
render(args) {
|
||||
const [dates, setDates] = useState<Date[] | undefined>([
|
||||
new Date(2024, 0, 15),
|
||||
new Date(2024, 0, 20),
|
||||
new Date(2024, 0, 25),
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar {...args} mode="multiple" selected={dates} onSelect={setDates} className="rounded-md border" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const RangeSelection: Story = {
|
||||
render(args) {
|
||||
const [range, setRange] = useState<DateRange | undefined>({
|
||||
from: new Date(2024, 0, 10),
|
||||
to: new Date(2024, 0, 20),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar {...args} mode="range" selected={range} onSelect={setRange} className="rounded-md border" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledDates: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>();
|
||||
const disabledDays = [new Date(2024, 0, 5), new Date(2024, 0, 12), new Date(2024, 0, 19), new Date(2024, 0, 26)];
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={disabledDays}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledWeekends: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>();
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={(date) => date.getDay() === 0 || date.getDay() === 6}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MinMaxDates: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>();
|
||||
const today = new Date();
|
||||
const tenDaysAgo = new Date(today);
|
||||
tenDaysAgo.setDate(today.getDate() - 10);
|
||||
const tenDaysFromNow = new Date(today);
|
||||
tenDaysFromNow.setDate(today.getDate() + 10);
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
disabled={(date) => date < tenDaysAgo || date > tenDaysFromNow}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WeekStartsOnMonday: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>(new Date());
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
weekStartsOn={1}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutOutsideDays: Story = {
|
||||
render(args) {
|
||||
const [date, setDate] = useState<Date | undefined>(new Date());
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={setDate}
|
||||
showOutsideDays={false}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const TwoMonths: Story = {
|
||||
render(args) {
|
||||
const [range, setRange] = useState<DateRange | undefined>({
|
||||
from: new Date(2024, 0, 10),
|
||||
to: new Date(2024, 1, 15),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar
|
||||
{...args}
|
||||
mode="range"
|
||||
selected={range}
|
||||
onSelect={setRange}
|
||||
numberOfMonths={2}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Uncontrolled: Story = {
|
||||
render(args) {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Calendar {...args} mode="single" defaultMonth={new Date(2024, 0)} className="rounded-md border" />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
211
packages/propel/src/card/card.stories.tsx
Normal file
211
packages/propel/src/card/card.stories.tsx
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Card, ECardVariant, ECardSpacing, ECardDirection } from "./card";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Card",
|
||||
component: Card,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Card Title</h3>
|
||||
<p className="text-sm text-gray-600">This is a default card with shadow and large spacing.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
} satisfies Meta<typeof Card>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithShadow: Story = {
|
||||
args: {
|
||||
variant: ECardVariant.WITH_SHADOW,
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Card with Shadow</h3>
|
||||
<p className="text-sm text-gray-600">Hover over this card to see the shadow effect.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutShadow: Story = {
|
||||
args: {
|
||||
variant: ECardVariant.WITHOUT_SHADOW,
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Card without Shadow</h3>
|
||||
<p className="text-sm text-gray-600">This card has no shadow effect on hover.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const SmallSpacing: Story = {
|
||||
args: {
|
||||
spacing: ECardSpacing.SM,
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Small Spacing</h3>
|
||||
<p className="text-sm text-gray-600">This card uses small spacing (p-4).</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeSpacing: Story = {
|
||||
args: {
|
||||
spacing: ECardSpacing.LG,
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Large Spacing</h3>
|
||||
<p className="text-sm text-gray-600">This card uses large spacing (p-6).</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const ColumnDirection: Story = {
|
||||
args: {
|
||||
direction: ECardDirection.COLUMN,
|
||||
children: (
|
||||
<>
|
||||
<h3 className="text-lg font-semibold">Column Direction</h3>
|
||||
<p className="text-sm text-gray-600">Content is arranged vertically.</p>
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-white">Action</button>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const RowDirection: Story = {
|
||||
args: {
|
||||
direction: ECardDirection.ROW,
|
||||
children: (
|
||||
<>
|
||||
<div className="flex-shrink-0">
|
||||
<div className="h-12 w-12 rounded bg-blue-500" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold">Row Direction</h3>
|
||||
<p className="text-sm text-gray-600">Content is arranged horizontally.</p>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const ProductCard: Story = {
|
||||
args: {
|
||||
variant: ECardVariant.WITH_SHADOW,
|
||||
spacing: ECardSpacing.LG,
|
||||
direction: ECardDirection.COLUMN,
|
||||
children: (
|
||||
<>
|
||||
<div className="h-48 w-full rounded bg-gray-200" />
|
||||
<h3 className="text-xl font-bold">Product Name</h3>
|
||||
<p className="text-sm text-gray-600">A brief description of the product goes here.</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg font-semibold">$99.99</span>
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">Add to Cart</button>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const UserCard: Story = {
|
||||
args: {
|
||||
variant: ECardVariant.WITH_SHADOW,
|
||||
spacing: ECardSpacing.LG,
|
||||
direction: ECardDirection.ROW,
|
||||
children: (
|
||||
<>
|
||||
<div className="h-16 w-16 flex-shrink-0 rounded-full bg-blue-500" />
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-semibold">John Doe</h3>
|
||||
<p className="text-sm text-gray-600">Software Engineer</p>
|
||||
<p className="text-xs text-gray-500">john.doe@example.com</p>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const NotificationCard: Story = {
|
||||
args: {
|
||||
variant: ECardVariant.WITHOUT_SHADOW,
|
||||
spacing: ECardSpacing.SM,
|
||||
direction: ECardDirection.COLUMN,
|
||||
children: (
|
||||
<>
|
||||
<div className="flex items-start justify-between">
|
||||
<h4 className="font-semibold">New Message</h4>
|
||||
<span className="text-xs text-gray-500">2m ago</span>
|
||||
</div>
|
||||
<p className="text-sm text-gray-600">You have received a new message from Alice.</p>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card variant={ECardVariant.WITH_SHADOW}>
|
||||
<h3 className="font-semibold">With Shadow</h3>
|
||||
<p className="text-sm text-gray-600">Hover to see the shadow effect</p>
|
||||
</Card>
|
||||
<Card variant={ECardVariant.WITHOUT_SHADOW}>
|
||||
<h3 className="font-semibold">Without Shadow</h3>
|
||||
<p className="text-sm text-gray-600">No shadow on hover</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSpacings: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card spacing={ECardSpacing.SM}>
|
||||
<h3 className="font-semibold">Small Spacing (p-4)</h3>
|
||||
<p className="text-sm text-gray-600">Compact padding</p>
|
||||
</Card>
|
||||
<Card spacing={ECardSpacing.LG}>
|
||||
<h3 className="font-semibold">Large Spacing (p-6)</h3>
|
||||
<p className="text-sm text-gray-600">More generous padding</p>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllDirections: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card direction={ECardDirection.COLUMN}>
|
||||
<h3 className="font-semibold">Column Direction</h3>
|
||||
<p className="text-sm text-gray-600">Vertical layout</p>
|
||||
<button className="w-fit rounded bg-blue-500 px-4 py-2 text-white">Button</button>
|
||||
</Card>
|
||||
<Card direction={ECardDirection.ROW}>
|
||||
<div className="h-12 w-12 flex-shrink-0 rounded bg-blue-500" />
|
||||
<div>
|
||||
<h3 className="font-semibold">Row Direction</h3>
|
||||
<p className="text-sm text-gray-600">Horizontal layout</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
174
packages/propel/src/collapsible/collapsible.stories.tsx
Normal file
174
packages/propel/src/collapsible/collapsible.stories.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { useArgs } from "storybook/preview-api";
|
||||
import { Collapsible } from "./collapsible";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Collapsible",
|
||||
component: Collapsible.CollapsibleRoot,
|
||||
subcomponents: {
|
||||
CollapsibleTrigger: Collapsible.CollapsibleTrigger,
|
||||
CollapsibleContent: Collapsible.CollapsibleContent,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
children: null,
|
||||
isOpen: false,
|
||||
onToggle: () => {},
|
||||
},
|
||||
render(args) {
|
||||
const [{ isOpen }, updateArgs] = useArgs();
|
||||
const toggleOpen = () => updateArgs({ isOpen: !isOpen });
|
||||
|
||||
return (
|
||||
<Collapsible.CollapsibleRoot {...args} isOpen={isOpen} onToggle={toggleOpen} className="w-96">
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">Click to toggle</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm">This is the collapsible content that can be shown or hidden.</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
);
|
||||
},
|
||||
} satisfies Meta<typeof Collapsible.CollapsibleRoot>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const DefaultOpen: Story = {
|
||||
args: { isOpen: true },
|
||||
};
|
||||
|
||||
export const Controlled: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setIsOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-sm text-white">
|
||||
Open
|
||||
</button>
|
||||
<button onClick={() => setIsOpen(false)} className="rounded bg-gray-500 px-4 py-2 text-sm text-white">
|
||||
Close
|
||||
</button>
|
||||
<button onClick={() => setIsOpen(!isOpen)} className="rounded bg-green-500 px-4 py-2 text-sm text-white">
|
||||
Toggle
|
||||
</button>
|
||||
</div>
|
||||
<Collapsible.CollapsibleRoot isOpen={isOpen} onToggle={() => setIsOpen(!isOpen)} className="w-96">
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">Controlled Collapsible</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm">This collapsible is controlled by external state.</p>
|
||||
<p className="mt-2 text-sm">Current state: {isOpen ? "Open" : "Closed"}</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const NestedContent: Story = {
|
||||
render(args) {
|
||||
const [isOpen, setIsOpen] = useState(args.isOpen);
|
||||
return (
|
||||
<Collapsible.CollapsibleRoot {...args} isOpen={isOpen} onToggle={() => setIsOpen(!isOpen)} className="w-96">
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">Collapsible with Nested Content</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="space-y-2 rounded-md border border-gray-200 p-4">
|
||||
<h4 className="font-semibold">Section 1</h4>
|
||||
<p className="text-sm">This is some content in the first section.</p>
|
||||
<h4 className="font-semibold">Section 2</h4>
|
||||
<p className="text-sm">This is some content in the second section.</p>
|
||||
<ul className="list-inside list-disc text-sm">
|
||||
<li>Item 1</li>
|
||||
<li>Item 2</li>
|
||||
<li>Item 3</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyling: Story = {
|
||||
render(args) {
|
||||
const [isOpen, setIsOpen] = useState(args.isOpen);
|
||||
return (
|
||||
<Collapsible.CollapsibleRoot {...args} isOpen={isOpen} onToggle={() => setIsOpen(!isOpen)} className="w-96">
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-lg bg-gradient-to-r from-purple-500 to-pink-500 px-6 py-3 text-white shadow-lg transition-all hover:shadow-xl">
|
||||
<span className="text-lg font-bold">Custom Styled Trigger</span>
|
||||
<ChevronDown className="h-5 w-5 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-4">
|
||||
<div className="rounded-lg bg-gradient-to-br from-purple-100 to-pink-100 p-6 shadow-md">
|
||||
<p className="text-purple-900">This collapsible has custom styling with gradients, shadows, and colors.</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleCollapsibles: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="w-96 space-y-2">
|
||||
<Collapsible.CollapsibleRoot>
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">First Item</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm">Content for the first item.</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
|
||||
<Collapsible.CollapsibleRoot>
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">Second Item</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm">Content for the second item.</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
|
||||
<Collapsible.CollapsibleRoot>
|
||||
<Collapsible.CollapsibleTrigger className="flex w-full items-center justify-between rounded-md bg-gray-100 px-4 py-2 hover:bg-gray-200">
|
||||
<span className="font-semibold">Third Item</span>
|
||||
<ChevronDown className="h-4 w-4 transition-transform group-data-[panel-open]:rotate-180" />
|
||||
</Collapsible.CollapsibleTrigger>
|
||||
<Collapsible.CollapsibleContent className="mt-2">
|
||||
<div className="rounded-md border border-gray-200 p-4">
|
||||
<p className="text-sm">Content for the third item.</p>
|
||||
</div>
|
||||
</Collapsible.CollapsibleContent>
|
||||
</Collapsible.CollapsibleRoot>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
260
packages/propel/src/combobox/combobox.stories.tsx
Normal file
260
packages/propel/src/combobox/combobox.stories.tsx
Normal file
@@ -0,0 +1,260 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Check, ChevronsUpDown } from "lucide-react";
|
||||
import { useArgs } from "storybook/preview-api";
|
||||
import { Combobox } from "./combobox";
|
||||
|
||||
const frameworks = [
|
||||
{ value: "react", label: "React" },
|
||||
{ value: "vue", label: "Vue" },
|
||||
{ value: "angular", label: "Angular" },
|
||||
{ value: "svelte", label: "Svelte" },
|
||||
{ value: "solid", label: "Solid" },
|
||||
{ value: "next", label: "Next.js" },
|
||||
{ value: "nuxt", label: "Nuxt" },
|
||||
{ value: "remix", label: "Remix" },
|
||||
];
|
||||
|
||||
const meta = {
|
||||
title: "Components/Combobox",
|
||||
component: Combobox,
|
||||
subcomponents: {
|
||||
ComboboxButton: Combobox.Button,
|
||||
ComboboxOptions: Combobox.Options,
|
||||
ComboboxOption: Combobox.Option,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
children: null,
|
||||
value: "",
|
||||
onValueChange: () => {},
|
||||
},
|
||||
render(args) {
|
||||
const [{ value }, updateArgs] = useArgs();
|
||||
const setValue = (newValue: string | string[]) => updateArgs({ value: newValue });
|
||||
return (
|
||||
<Combobox {...args} value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
} satisfies Meta<typeof Combobox>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const WithoutSearch: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiSelect: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<Combobox multiSelect value={value} onValueChange={(v) => setValue(v as string[])}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span className="truncate">{value.length > 0 ? `${value.length} selected` : "Select frameworks..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value.includes(framework.value) && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultiSelectWithLimit: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState<string[]>([]);
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<Combobox multiSelect maxSelections={3} value={value} onValueChange={(v) => setValue(v as string[])}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span className="truncate">
|
||||
{value.length > 0 ? `${value.length}/3 selected` : "Select up to 3 frameworks..."}
|
||||
</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value.includes(framework.value) && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
<p className="text-xs text-gray-500">Maximum 3 selections allowed</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true },
|
||||
render() {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<Combobox disabled value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-gray-100 px-4 py-2 opacity-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledOptions: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
disabled={framework.value === "angular" || framework.value === "svelte"}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomMaxHeight: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options showSearch searchPlaceholder="Search framework..." maxHeight="sm" className="w-72">
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomEmptyMessage: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState("");
|
||||
return (
|
||||
<Combobox value={value} onValueChange={(v) => setValue(v as string)}>
|
||||
<Combobox.Button className="flex w-72 items-center justify-between rounded-md border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<span>{value ? frameworks.find((f) => f.value === value)?.label : "Select framework..."}</span>
|
||||
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Combobox.Button>
|
||||
<Combobox.Options
|
||||
showSearch
|
||||
searchPlaceholder="Search framework..."
|
||||
emptyMessage="No frameworks found. Try a different search."
|
||||
className="w-72"
|
||||
>
|
||||
{frameworks.map((framework) => (
|
||||
<Combobox.Option
|
||||
key={framework.value}
|
||||
value={framework.value}
|
||||
className="flex items-center gap-2 px-4 py-2"
|
||||
>
|
||||
{value === framework.value && <Check className="h-4 w-4" />}
|
||||
<span>{framework.label}</span>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
</Combobox.Options>
|
||||
</Combobox>
|
||||
);
|
||||
},
|
||||
};
|
||||
203
packages/propel/src/command/command.stories.tsx
Normal file
203
packages/propel/src/command/command.stories.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { File, Folder, Settings, User } from "lucide-react";
|
||||
import { Command } from "./command";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Command",
|
||||
component: Command,
|
||||
subcomponents: {
|
||||
CommandInput: Command.Input,
|
||||
CommandList: Command.List,
|
||||
CommandItem: Command.Item,
|
||||
CommandEmpty: Command.Empty,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Command>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input placeholder="Search..." className="h-9 w-full bg-transparent py-3 text-sm outline-none" />
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">Item 1</Command.Item>
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">Item 2</Command.Item>
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">Item 3</Command.Item>
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">No results found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input
|
||||
placeholder="Search files and folders..."
|
||||
className="h-9 w-full bg-transparent py-3 text-sm outline-none"
|
||||
/>
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Folder className="h-4 w-4" />
|
||||
<span>Documents</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Folder className="h-4 w-4" />
|
||||
<span>Downloads</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<File className="h-4 w-4" />
|
||||
<span>README.md</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<File className="h-4 w-4" />
|
||||
<span>package.json</span>
|
||||
</Command.Item>
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">No files or folders found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCategories: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input
|
||||
placeholder="Search commands..."
|
||||
className="h-9 w-full bg-transparent py-3 text-sm outline-none"
|
||||
/>
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<div className="px-2 py-1.5 text-xs font-semibold text-gray-500">User</div>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<User className="h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
</Command.Item>
|
||||
|
||||
<div className="mt-2 px-2 py-1.5 text-xs font-semibold text-gray-500">Files</div>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Folder className="h-4 w-4" />
|
||||
<span>Open Folder</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<File className="h-4 w-4" />
|
||||
<span>New File</span>
|
||||
</Command.Item>
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">No commands found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyState: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input placeholder="Search..." className="h-9 w-full bg-transparent py-3 text-sm outline-none" />
|
||||
<Command.List className="max-h-80 overflow-auto py-2">{/* No items - will show empty state */}</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">
|
||||
<p className="font-semibold">No results found</p>
|
||||
<p className="mt-1 text-xs">Try searching for something else</p>
|
||||
</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LongList: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input placeholder="Search items..." className="h-9 w-full bg-transparent py-3 text-sm outline-none" />
|
||||
<Command.List className="max-h-60 overflow-auto py-2">
|
||||
{Array.from({ length: 20 }, (_, i) => (
|
||||
<Command.Item key={i} className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">
|
||||
Item {i + 1}
|
||||
</Command.Item>
|
||||
))}
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">No results found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutSearch: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<User className="h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
</Command.Item>
|
||||
<Command.Item className="flex cursor-pointer items-center gap-2 rounded px-3 py-2 hover:bg-gray-100">
|
||||
<Folder className="h-4 w-4" />
|
||||
<span>Files</span>
|
||||
</Command.Item>
|
||||
</Command.List>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyling: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border-2 border-blue-300 bg-blue-50 p-2 shadow-lg">
|
||||
<Command.Input
|
||||
placeholder="Search with custom styling..."
|
||||
className="h-9 w-full bg-transparent py-3 text-sm text-blue-900 outline-none placeholder:text-blue-400"
|
||||
/>
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 text-blue-900 hover:bg-blue-200">
|
||||
Custom Item 1
|
||||
</Command.Item>
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 text-blue-900 hover:bg-blue-200">
|
||||
Custom Item 2
|
||||
</Command.Item>
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 text-blue-900 hover:bg-blue-200">
|
||||
Custom Item 3
|
||||
</Command.Item>
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-blue-500">No matching items found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledItems: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Command className="w-96 rounded-lg border border-gray-200 p-2">
|
||||
<Command.Input placeholder="Search..." className="h-9 w-full bg-transparent py-3 text-sm outline-none" />
|
||||
<Command.List className="max-h-80 overflow-auto py-2">
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">Active Item 1</Command.Item>
|
||||
<Command.Item disabled className="cursor-not-allowed rounded px-3 py-2 opacity-50">
|
||||
Disabled Item
|
||||
</Command.Item>
|
||||
<Command.Item className="cursor-pointer rounded px-3 py-2 hover:bg-gray-100">Active Item 2</Command.Item>
|
||||
</Command.List>
|
||||
<Command.Empty className="py-6 text-center text-sm text-gray-500">No results found.</Command.Empty>
|
||||
</Command>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,9 +1,23 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Copy, Download, Edit, Share, Trash, ChevronRight, Star, Archive } from "lucide-react";
|
||||
import { ContextMenu } from "./context-menu";
|
||||
|
||||
// cannot use satisfies here because base-ui does not have portable types.
|
||||
const meta: Meta<typeof ContextMenu> = {
|
||||
title: "Components/ContextMenu",
|
||||
component: ContextMenu,
|
||||
subcomponents: {
|
||||
ContextMenuTrigger: ContextMenu.Trigger,
|
||||
ContextMenuPortal: ContextMenu.Portal,
|
||||
ContextMenuContent: ContextMenu.Content,
|
||||
ContextMenuItem: ContextMenu.Item,
|
||||
ContextMenuSeparator: ContextMenu.Separator,
|
||||
ContextMenuSubmenu: ContextMenu.Submenu,
|
||||
ContextMenuSubmenuTrigger: ContextMenu.SubmenuTrigger,
|
||||
},
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
@@ -11,25 +25,354 @@ const meta: Meta<typeof ContextMenu> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ContextMenu>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>Back</ContextMenu.Item>
|
||||
<ContextMenu.Item>Forward</ContextMenu.Item>
|
||||
<ContextMenu.Item>Reload</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>More Tools</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
),
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>Back</ContextMenu.Item>
|
||||
<ContextMenu.Item>Forward</ContextMenu.Item>
|
||||
<ContextMenu.Item>Reload</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>More Tools</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
Share
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSubmenus: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTrigger>
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
Share
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</ContextMenu.SubmenuTrigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>Email</ContextMenu.Item>
|
||||
<ContextMenu.Item>Message</ContextMenu.Item>
|
||||
<ContextMenu.Item>Copy Link</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Submenu>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledItems: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item disabled>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit (Disabled)
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item disabled>
|
||||
<Share className="mr-2 h-4 w-4" />
|
||||
Share (Disabled)
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OnFileCard: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="w-64 p-4 border border-custom-border-200 rounded-lg hover:bg-custom-background-80 cursor-pointer">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 bg-custom-primary-100 rounded flex items-center justify-center text-white text-lg">
|
||||
📄
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">Document.pdf</div>
|
||||
<div className="text-sm text-custom-text-400">2.4 MB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy Link
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Star className="mr-2 h-4 w-4" />
|
||||
Add to Favorites
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>
|
||||
<Archive className="mr-2 h-4 w-4" />
|
||||
Archive
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OnImage: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="relative w-80 h-56 bg-custom-background-80 rounded-lg overflow-hidden cursor-pointer">
|
||||
<div className="absolute inset-0 flex items-center justify-center text-custom-text-400">
|
||||
Image Placeholder
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Save Image
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy Image
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy Image URL
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>Open Image in New Tab</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OnText: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="w-96 p-6 border border-custom-border-200 rounded-lg">
|
||||
<h3 className="text-lg font-semibold mb-2">Context Menu on Text</h3>
|
||||
<p className="text-custom-text-300">
|
||||
Right click anywhere on this text area to see the context menu. This demonstrates how context menus can be
|
||||
applied to text content areas.
|
||||
</p>
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>Select All</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const NestedSubmenus: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>New File</ContextMenu.Item>
|
||||
<ContextMenu.Item>New Folder</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTrigger>
|
||||
Import
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</ContextMenu.SubmenuTrigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>From File</ContextMenu.Item>
|
||||
<ContextMenu.Item>From URL</ContextMenu.Item>
|
||||
<ContextMenu.Submenu>
|
||||
<ContextMenu.SubmenuTrigger>
|
||||
From Cloud
|
||||
<ChevronRight className="ml-auto h-4 w-4" />
|
||||
</ContextMenu.SubmenuTrigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>Google Drive</ContextMenu.Item>
|
||||
<ContextMenu.Item>Dropbox</ContextMenu.Item>
|
||||
<ContextMenu.Item>OneDrive</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Submenu>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu.Submenu>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithKeyboardShortcuts: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger>
|
||||
<div className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed border-custom-border-300 text-sm">
|
||||
Right click here
|
||||
</div>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content>
|
||||
<ContextMenu.Item>
|
||||
<Copy className="mr-2 h-4 w-4" />
|
||||
Copy
|
||||
<span className="ml-auto text-xs text-custom-text-400">⌘C</span>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
Edit
|
||||
<span className="ml-auto text-xs text-custom-text-400">⌘E</span>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Download
|
||||
<span className="ml-auto text-xs text-custom-text-400">⌘D</span>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Separator />
|
||||
<ContextMenu.Item>
|
||||
<Trash className="mr-2 h-4 w-4 text-red-500" />
|
||||
<span className="text-red-500">Delete</span>
|
||||
<span className="ml-auto text-xs text-custom-text-400">⌘⌫</span>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
399
packages/propel/src/dialog/dialog.stories.tsx
Normal file
399
packages/propel/src/dialog/dialog.stories.tsx
Normal file
@@ -0,0 +1,399 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { X } from "lucide-react";
|
||||
import { useArgs } from "storybook/preview-api";
|
||||
import { Dialog, EDialogWidth } from "./root";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Dialog",
|
||||
component: Dialog,
|
||||
subcomponents: {
|
||||
DialogPanel: Dialog.Panel,
|
||||
DialogTitle: Dialog.Title,
|
||||
},
|
||||
args: {
|
||||
children: null,
|
||||
open: false,
|
||||
onOpenChange: () => {},
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
render(args) {
|
||||
const [{ open }, updateArgs] = useArgs();
|
||||
const setOpen = (value: boolean) => updateArgs({ open: value });
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Dialog
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Dialog Title</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">This is the dialog content. You can put any content here.</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
} satisfies Meta<typeof Dialog>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const TopPosition: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Dialog (Top)
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel position="top">
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Top Positioned Dialog</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
This dialog appears at the top of the screen instead of centered.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SmallWidth: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Small Dialog
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel width={EDialogWidth.SM}>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Small Dialog</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">This is a small dialog.</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeWidth: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Large Dialog
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel width={EDialogWidth.XXXXL}>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Large Dialog</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
This is a large dialog with more horizontal space for content.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCloseButton: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Dialog with Close Button
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel>
|
||||
<div className="p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<Dialog.Title>Dialog with Close Button</Dialog.Title>
|
||||
<button onClick={() => setOpen(false)} className="rounded-full p-1 hover:bg-gray-100">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">This dialog has a close button in the header.</p>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ConfirmationDialog: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
const handleConfirm = () => {
|
||||
alert("Confirmed!");
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600">
|
||||
Delete Item
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel width={EDialogWidth.SM}>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Confirm Deletion</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">
|
||||
Are you sure you want to delete this item? This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handleConfirm}
|
||||
className="rounded bg-red-500 px-4 py-2 text-sm text-white hover:bg-red-600"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const FormDialog: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
alert("Form submitted!");
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Form
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel width={EDialogWidth.MD}>
|
||||
<form onSubmit={handleSubmit} className="p-6">
|
||||
<Dialog.Title>Create New Item</Dialog.Title>
|
||||
<div className="mt-4 space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
className="mt-1 w-full rounded border border-gray-300 px-3 py-2 text-sm"
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
id="description"
|
||||
rows={3}
|
||||
className="mt-1 w-full rounded border border-gray-300 px-3 py-2 text-sm"
|
||||
placeholder="Enter description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-gray-200 px-4 py-2 text-sm hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600">
|
||||
Create
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollableContent: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Scrollable Dialog
|
||||
</button>
|
||||
{open && (
|
||||
<Dialog {...args} open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Panel width={EDialogWidth.MD}>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>Scrollable Content</Dialog.Title>
|
||||
<div className="mt-4 max-h-96 overflow-y-auto">
|
||||
{Array.from({ length: 20 }, (_, i) => (
|
||||
<p key={i} className="mb-2 text-sm text-gray-600">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut
|
||||
labore et dolore magna aliqua.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<button
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllWidths: Story = {
|
||||
render() {
|
||||
const [openWidth, setOpenWidth] = useState<EDialogWidth | null>(null);
|
||||
|
||||
const widths = [
|
||||
{ width: EDialogWidth.SM, label: "Small" },
|
||||
{ width: EDialogWidth.MD, label: "Medium" },
|
||||
{ width: EDialogWidth.LG, label: "Large" },
|
||||
{ width: EDialogWidth.XL, label: "XL" },
|
||||
{ width: EDialogWidth.XXL, label: "2XL" },
|
||||
{ width: EDialogWidth.XXXL, label: "3XL" },
|
||||
{ width: EDialogWidth.XXXXL, label: "4XL" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{widths.map(({ width, label }) => (
|
||||
<button
|
||||
key={width}
|
||||
onClick={() => setOpenWidth(width)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
{widths.map(({ width, label }) => (
|
||||
<Dialog key={width} open={openWidth === width} onOpenChange={() => setOpenWidth(null)}>
|
||||
<Dialog.Panel width={width}>
|
||||
<div className="p-6">
|
||||
<Dialog.Title>{label} Dialog</Dialog.Title>
|
||||
<div className="mt-4">
|
||||
<p className="text-sm text-gray-600">This dialog uses the {label} width variant.</p>
|
||||
</div>
|
||||
<div className="mt-6 flex justify-end">
|
||||
<button
|
||||
onClick={() => setOpenWidth(null)}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
</Dialog>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { forwardRef, memo, useMemo } from "react";
|
||||
import { Dialog as BaseDialog } from "@base-ui-components/react";
|
||||
import { cn } from "../utils/classname";
|
||||
|
||||
@@ -41,44 +41,41 @@ const OVERLAY_CLASSNAME = cn("fixed inset-0 z-backdrop bg-custom-backdrop");
|
||||
const BASE_CLASSNAME = "relative text-left bg-custom-background-100 rounded-lg shadow-md w-full z-modal";
|
||||
|
||||
// Utility functions
|
||||
const getPositionClassNames = React.useCallback(
|
||||
(position: DialogPosition) =>
|
||||
cn("isolate fixed z-modal", {
|
||||
"top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2": position === "center",
|
||||
"top-8 left-1/2 -translate-x-1/2": position === "top",
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const getPositionClassNames = (position: DialogPosition) =>
|
||||
cn("isolate fixed z-modal", {
|
||||
"top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2": position === "center",
|
||||
"top-8 left-1/2 -translate-x-1/2": position === "top",
|
||||
});
|
||||
|
||||
const DialogPortal = React.memo<React.ComponentProps<typeof BaseDialog.Portal>>(({ children, ...props }) => (
|
||||
const DialogPortal = memo<React.ComponentProps<typeof BaseDialog.Portal>>(({ children, ...props }) => (
|
||||
<BaseDialog.Portal data-slot="dialog-portal" {...props}>
|
||||
{children}
|
||||
</BaseDialog.Portal>
|
||||
));
|
||||
DialogPortal.displayName = "DialogPortal";
|
||||
|
||||
const DialogOverlay = React.memo<React.ComponentProps<typeof BaseDialog.Backdrop>>(({ className, ...props }) => (
|
||||
const DialogOverlay = memo<React.ComponentProps<typeof BaseDialog.Backdrop>>(({ className, ...props }) => (
|
||||
<BaseDialog.Backdrop data-slot="dialog-overlay" className={cn(OVERLAY_CLASSNAME, className)} {...props} />
|
||||
));
|
||||
DialogOverlay.displayName = "DialogOverlay";
|
||||
|
||||
const DialogComponent = React.memo<DialogProps>(({ children, ...props }) => (
|
||||
const DialogComponent = memo<DialogProps>(({ children, ...props }) => (
|
||||
<BaseDialog.Root data-slot="dialog" {...props}>
|
||||
{children}
|
||||
</BaseDialog.Root>
|
||||
));
|
||||
DialogComponent.displayName = "Dialog";
|
||||
|
||||
const DialogTrigger = React.memo<React.ComponentProps<typeof BaseDialog.Trigger>>(({ children, ...props }) => (
|
||||
const DialogTrigger = memo<React.ComponentProps<typeof BaseDialog.Trigger>>(({ children, ...props }) => (
|
||||
<BaseDialog.Trigger data-slot="dialog-trigger" {...props}>
|
||||
{children}
|
||||
</BaseDialog.Trigger>
|
||||
));
|
||||
DialogTrigger.displayName = "DialogTrigger";
|
||||
|
||||
const DialogPanel = React.forwardRef<React.ElementRef<typeof BaseDialog.Popup>, DialogPanelProps>(
|
||||
const DialogPanel = forwardRef<React.ElementRef<typeof BaseDialog.Popup>, DialogPanelProps>(
|
||||
({ className, width = EDialogWidth.XXL, children, position = "center", ...props }, ref) => {
|
||||
const positionClassNames = React.useMemo(() => getPositionClassNames(position), [position]);
|
||||
const positionClassNames = useMemo(() => getPositionClassNames(position), [position]);
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
@@ -98,7 +95,7 @@ const DialogPanel = React.forwardRef<React.ElementRef<typeof BaseDialog.Popup>,
|
||||
);
|
||||
DialogPanel.displayName = "DialogPanel";
|
||||
|
||||
const DialogTitle = React.memo<DialogTitleProps>(({ className, children, ...props }) => (
|
||||
const DialogTitle = memo<DialogTitleProps>(({ className, children, ...props }) => (
|
||||
<BaseDialog.Title data-slot="dialog-title" className={cn("text-lg leading-none font-semibold", className)} {...props}>
|
||||
{children}
|
||||
</BaseDialog.Title>
|
||||
|
||||
@@ -3,44 +3,367 @@ import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { EmojiPicker } from "./emoji-picker";
|
||||
import { EmojiIconPickerTypes, TChangeHandlerProps } from "./helper";
|
||||
|
||||
const meta: Meta<typeof EmojiPicker> = {
|
||||
const meta = {
|
||||
title: "Components/Emoji/EmojiPicker",
|
||||
component: EmojiPicker,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
} satisfies Meta<typeof EmojiPicker>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EmojiPicker>;
|
||||
|
||||
const EmojiPickerWithState = (args: React.ComponentProps<typeof EmojiPicker>) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<EmojiPicker
|
||||
{...args}
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={(value) => {
|
||||
setSelectedValue(value);
|
||||
}}
|
||||
/>
|
||||
{selectedValue && <div className="text-sm text-gray-600">Selected: {JSON.stringify(selectedValue, null, 2)}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args: React.ComponentProps<typeof EmojiPicker>) => <EmojiPickerWithState {...args} />,
|
||||
args: {
|
||||
label: "😊 Pick an emoji or icon",
|
||||
defaultOpen: EmojiIconPickerTypes.EMOJI,
|
||||
closeOnSelect: true,
|
||||
searchPlaceholder: "Search emojis...",
|
||||
iconType: "lucide",
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="😊 Pick an emoji or icon"
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<div className="font-medium mb-2">Selected:</div>
|
||||
<pre className="text-xs">{JSON.stringify(selectedValue, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OpenToEmojiTab: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="😊 Choose Emoji"
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm">Selected: {selectedValue.type === "emoji" ? selectedValue.value : "Icon"}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OpenToIconTab: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="🎨 Choose Icon"
|
||||
defaultOpen={EmojiIconPickerTypes.ICON}
|
||||
closeOnSelect
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm">
|
||||
Selected:{" "}
|
||||
{selectedValue.type === "icon" && typeof selectedValue.value === "object"
|
||||
? selectedValue.value.name
|
||||
: "Emoji"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LucideIcons: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="Lucide Icons"
|
||||
defaultOpen={EmojiIconPickerTypes.ICON}
|
||||
closeOnSelect
|
||||
iconType="lucide"
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<div className="font-medium mb-2">Selected Icon:</div>
|
||||
<pre className="text-xs">{JSON.stringify(selectedValue, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MaterialIcons: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="Material Icons"
|
||||
defaultOpen={EmojiIconPickerTypes.ICON}
|
||||
closeOnSelect
|
||||
iconType="material"
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<div className="font-medium mb-2">Selected Icon:</div>
|
||||
<pre className="text-xs">{JSON.stringify(selectedValue, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CloseOnSelectDisabled: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValues, setSelectedValues] = useState<TChangeHandlerProps[]>([]);
|
||||
|
||||
const handleChange = (value: TChangeHandlerProps) => {
|
||||
setSelectedValues((prev) => [...prev, value]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="flex gap-2 items-center">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={handleChange}
|
||||
label="Select Multiple (Stays Open)"
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
<button
|
||||
className="px-3 py-1.5 text-sm bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setSelectedValues([])}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
{selectedValues.length > 0 && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<div className="font-medium mb-2">Selected ({selectedValues.length}):</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{selectedValues.map((val, idx) => (
|
||||
<span key={idx} className="text-lg">
|
||||
{val.type === "emoji" ? val.value : "🎨"}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomSearchPlaceholder: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="Custom Search"
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect
|
||||
searchPlaceholder="Type to find emojis..."
|
||||
/>
|
||||
{selectedValue && <div className="text-sm">Selected: {JSON.stringify(selectedValue)}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SearchDisabled: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="No Search"
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect
|
||||
searchDisabled
|
||||
/>
|
||||
{selectedValue && <div className="text-sm">Selected: {JSON.stringify(selectedValue)}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomIconColor: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState<TChangeHandlerProps | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedValue}
|
||||
label="Custom Icon Color"
|
||||
defaultOpen={EmojiIconPickerTypes.ICON}
|
||||
closeOnSelect
|
||||
defaultIconColor="#FF5733"
|
||||
/>
|
||||
{selectedValue && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<pre className="text-xs">{JSON.stringify(selectedValue, null, 2)}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DifferentPlacements: Story = {
|
||||
render() {
|
||||
const [isOpen1, setIsOpen1] = useState(false);
|
||||
const [isOpen2, setIsOpen2] = useState(false);
|
||||
const [isOpen3, setIsOpen3] = useState(false);
|
||||
const [isOpen4, setIsOpen4] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Bottom Start:</span>
|
||||
<EmojiPicker
|
||||
isOpen={isOpen1}
|
||||
handleToggle={setIsOpen1}
|
||||
onChange={() => {}}
|
||||
label="Bottom Start"
|
||||
placement="bottom-start"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Bottom End:</span>
|
||||
<EmojiPicker
|
||||
isOpen={isOpen2}
|
||||
handleToggle={setIsOpen2}
|
||||
onChange={() => {}}
|
||||
label="Bottom End"
|
||||
placement="bottom-end"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Top Start:</span>
|
||||
<EmojiPicker
|
||||
isOpen={isOpen3}
|
||||
handleToggle={setIsOpen3}
|
||||
onChange={() => {}}
|
||||
label="Top Start"
|
||||
placement="top-start"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Top End:</span>
|
||||
<EmojiPicker
|
||||
isOpen={isOpen4}
|
||||
handleToggle={setIsOpen4}
|
||||
onChange={() => {}}
|
||||
label="Top End"
|
||||
placement="top-end"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InFormContext: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [formData, setFormData] = useState({
|
||||
title: "",
|
||||
emoji: null as TChangeHandlerProps | null,
|
||||
});
|
||||
|
||||
const handleEmojiChange = (value: TChangeHandlerProps) => {
|
||||
setFormData((prev) => ({ ...prev, emoji: value }));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
alert(`Form submitted:\n${JSON.stringify(formData, null, 2)}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md p-4">
|
||||
<form onSubmit={handleSubmit} className="space-y-4 p-6 border border-custom-border-200 rounded-lg">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Project Title</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.title}
|
||||
onChange={(e) => setFormData((prev) => ({ ...prev, title: e.target.value }))}
|
||||
className="w-full px-3 py-2 bg-custom-background-80 border border-custom-border-200 rounded"
|
||||
placeholder="Enter project title"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-2">Project Icon</label>
|
||||
<EmojiPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={handleEmojiChange}
|
||||
label={formData.emoji && formData.emoji.type === "emoji" ? formData.emoji.value : "Click to select icon"}
|
||||
defaultOpen={EmojiIconPickerTypes.EMOJI}
|
||||
closeOnSelect
|
||||
buttonClassName="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded hover:bg-custom-background-90 w-full text-left"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full px-4 py-2 bg-custom-primary-100 text-white rounded hover:bg-custom-primary-200"
|
||||
>
|
||||
Create Project
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,48 +2,342 @@ import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { SmilePlus } from "lucide-react";
|
||||
import { stringToEmoji } from "../emoji-icon-picker";
|
||||
import { EmojiReactionGroup, EmojiReactionType } from "./emoji-reaction";
|
||||
import { EmojiReactionPicker } from "./emoji-reaction-picker";
|
||||
|
||||
const meta: Meta<typeof EmojiReactionPicker> = {
|
||||
const meta = {
|
||||
title: "Components/Emoji/EmojiReactionPicker",
|
||||
component: EmojiReactionPicker,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
} satisfies Meta<typeof EmojiReactionPicker>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EmojiReactionPicker>;
|
||||
|
||||
const EmojiPickerDemo = (args: React.ComponentProps<typeof EmojiReactionPicker>) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiReactionPicker
|
||||
{...args}
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={(emoji) => {
|
||||
setSelectedEmoji(emoji);
|
||||
console.log("Selected emoji:", emoji);
|
||||
}}
|
||||
label={
|
||||
<span className={`flex items-center justify-center rounded-md px-2 size-8 text-xl`}>
|
||||
{selectedEmoji ? stringToEmoji(selectedEmoji) : <SmilePlus className="h-6 text-custom-text-100" />}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => <EmojiPickerDemo {...args} />,
|
||||
args: {
|
||||
closeOnSelect: true,
|
||||
searchPlaceholder: "Search emojis...",
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedEmoji}
|
||||
closeOnSelect
|
||||
label={
|
||||
<span className="flex items-center justify-center rounded-md px-2 size-8 text-xl">
|
||||
{selectedEmoji ? stringToEmoji(selectedEmoji) : <SmilePlus className="h-6 text-custom-text-100" />}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
{selectedEmoji && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
Selected: {selectedEmoji}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomLabel: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedEmoji}
|
||||
closeOnSelect
|
||||
label={
|
||||
<button className="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded hover:bg-custom-background-90 flex items-center gap-2">
|
||||
{selectedEmoji ? stringToEmoji(selectedEmoji) : <SmilePlus className="h-4 w-4" />}
|
||||
<span className="text-sm">Add Reaction</span>
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
{selectedEmoji && <div className="text-sm">Selected: {selectedEmoji}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InlineReactions: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [reactions, setReactions] = useState<EmojiReactionType[]>([
|
||||
{ emoji: "👍", count: 3, reacted: false, users: ["Alice", "Bob", "Charlie"] },
|
||||
{ emoji: "❤️", count: 2, reacted: true, users: ["You", "David"] },
|
||||
]);
|
||||
|
||||
const handleReactionAdd = (emoji: string) => {
|
||||
setReactions((prev) => {
|
||||
const existing = prev.find((r) => r.emoji === emoji);
|
||||
if (existing) {
|
||||
return prev.map((r) => (r.emoji === emoji ? { ...r, count: r.count + 1, reacted: true } : r));
|
||||
}
|
||||
return [...prev, { emoji, count: 1, reacted: true, users: ["You"] }];
|
||||
});
|
||||
};
|
||||
|
||||
const handleReactionClick = (emoji: string) => {
|
||||
setReactions((prev) =>
|
||||
prev.map((r) => {
|
||||
if (r.emoji === emoji) {
|
||||
return {
|
||||
...r,
|
||||
reacted: !r.reacted,
|
||||
count: r.reacted ? r.count - 1 : r.count + 1,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<EmojiReactionGroup
|
||||
reactions={reactions}
|
||||
onReactionClick={handleReactionClick}
|
||||
onAddReaction={() => setIsOpen(true)}
|
||||
showAddButton={false}
|
||||
/>
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={handleReactionAdd}
|
||||
closeOnSelect
|
||||
label={
|
||||
<button className="inline-flex items-center justify-center rounded-full border border-dashed border-custom-border-300 bg-custom-background-100 text-custom-text-400 transition-all duration-200 hover:border-custom-primary-100 hover:text-custom-primary-100 hover:bg-custom-primary-100/5 h-7 w-7">
|
||||
<SmilePlus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DifferentPlacements: Story = {
|
||||
render() {
|
||||
const [isOpen1, setIsOpen1] = useState(false);
|
||||
const [isOpen2, setIsOpen2] = useState(false);
|
||||
const [isOpen3, setIsOpen3] = useState(false);
|
||||
const [isOpen4, setIsOpen4] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="p-8 space-y-8">
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Bottom Start:</span>
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen1}
|
||||
handleToggle={setIsOpen1}
|
||||
onChange={() => {}}
|
||||
placement="bottom-start"
|
||||
label={<SmilePlus className="h-6 w-6" />}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Bottom End:</span>
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen2}
|
||||
handleToggle={setIsOpen2}
|
||||
onChange={() => {}}
|
||||
placement="bottom-end"
|
||||
label={<SmilePlus className="h-6 w-6" />}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Top Start:</span>
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen3}
|
||||
handleToggle={setIsOpen3}
|
||||
onChange={() => {}}
|
||||
placement="top-start"
|
||||
label={<SmilePlus className="h-6 w-6" />}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-4 items-center">
|
||||
<span className="text-sm w-32">Top End:</span>
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen4}
|
||||
handleToggle={setIsOpen4}
|
||||
onChange={() => {}}
|
||||
placement="top-end"
|
||||
label={<SmilePlus className="h-6 w-6" />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SearchDisabled: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedEmoji}
|
||||
closeOnSelect
|
||||
searchDisabled
|
||||
label={
|
||||
<button className="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded hover:bg-custom-background-90">
|
||||
No Search
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
{selectedEmoji && <div className="text-sm">Selected: {selectedEmoji}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomSearchPlaceholder: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmoji, setSelectedEmoji] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={setSelectedEmoji}
|
||||
closeOnSelect
|
||||
searchPlaceholder="Find your emoji..."
|
||||
label={
|
||||
<button className="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded hover:bg-custom-background-90">
|
||||
Custom Search
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
{selectedEmoji && <div className="text-sm">Selected: {selectedEmoji}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CloseOnSelectDisabled: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [selectedEmojis, setSelectedEmojis] = useState<string[]>([]);
|
||||
|
||||
const handleChange = (emoji: string) => {
|
||||
setSelectedEmojis((prev) => [...prev, emoji]);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4 p-4">
|
||||
<div className="flex gap-2 items-center">
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={handleChange}
|
||||
closeOnSelect={false}
|
||||
label={
|
||||
<button className="px-4 py-2 bg-custom-background-80 border border-custom-border-200 rounded hover:bg-custom-background-90">
|
||||
Select Multiple (Stays Open)
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
<button
|
||||
className="px-3 py-1.5 text-sm bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
onClick={() => setSelectedEmojis([])}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
{selectedEmojis.length > 0 && (
|
||||
<div className="text-sm p-4 bg-custom-background-80 rounded border border-custom-border-200">
|
||||
<div className="font-medium mb-2">Selected ({selectedEmojis.length}):</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{selectedEmojis.map((emoji, idx) => (
|
||||
<span key={idx} className="text-xl">
|
||||
{emoji}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InMessageContext: Story = {
|
||||
render() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [reactions, setReactions] = useState<EmojiReactionType[]>([
|
||||
{ emoji: "👍", count: 5, reacted: false, users: ["Alice", "Bob", "Charlie"] },
|
||||
]);
|
||||
|
||||
const handleReactionAdd = (emoji: string) => {
|
||||
setReactions((prev) => {
|
||||
const existing = prev.find((r) => r.emoji === emoji);
|
||||
if (existing) {
|
||||
return prev.map((r) => (r.emoji === emoji ? { ...r, count: r.count + 1, reacted: true } : r));
|
||||
}
|
||||
return [...prev, { emoji, count: 1, reacted: true, users: ["You"] }];
|
||||
});
|
||||
};
|
||||
|
||||
const handleReactionClick = (emoji: string) => {
|
||||
setReactions((prev) =>
|
||||
prev.map((r) => {
|
||||
if (r.emoji === emoji) {
|
||||
return {
|
||||
...r,
|
||||
reacted: !r.reacted,
|
||||
count: r.reacted ? r.count - 1 : r.count + 1,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md border border-custom-border-200 rounded-lg p-4 space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-custom-primary-100 flex items-center justify-center text-white text-sm">
|
||||
AB
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">Alice Brown</div>
|
||||
<div className="text-sm text-custom-text-300 mt-1">
|
||||
Just finished the design for the new dashboard! Would love to hear your thoughts.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<EmojiReactionGroup reactions={reactions} onReactionClick={handleReactionClick} showAddButton={false} />
|
||||
<EmojiReactionPicker
|
||||
isOpen={isOpen}
|
||||
handleToggle={setIsOpen}
|
||||
onChange={handleReactionAdd}
|
||||
closeOnSelect
|
||||
label={
|
||||
<button className="inline-flex items-center justify-center rounded-full border border-dashed border-custom-border-300 bg-custom-background-100 text-custom-text-400 transition-all duration-200 hover:border-custom-primary-100 hover:text-custom-primary-100 hover:bg-custom-primary-100/5 h-7 w-7">
|
||||
<SmilePlus className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { EmojiReaction } from "./emoji-reaction";
|
||||
import { EmojiReaction, EmojiReactionGroup, EmojiReactionButton, EmojiReactionType } from "./emoji-reaction";
|
||||
|
||||
const meta: Meta<typeof EmojiReaction> = {
|
||||
const meta = {
|
||||
title: "Components/Emoji/EmojiReaction",
|
||||
component: EmojiReaction,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
} satisfies Meta<typeof EmojiReaction>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof EmojiReaction>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Single: Story = {
|
||||
args: {
|
||||
emoji: "👍",
|
||||
count: 5,
|
||||
reacted: false,
|
||||
users: ["User 1", "User 2", "User 3"],
|
||||
users: ["Alice", "Bob", "Charlie"],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -27,6 +28,256 @@ export const Reacted: Story = {
|
||||
emoji: "❤️",
|
||||
count: 12,
|
||||
reacted: true,
|
||||
users: ["User 1", "User 2", "User 3", "User 4", "User 5", "User 6"],
|
||||
users: ["Alice", "Bob", "Charlie", "David", "Emma", "Frank"],
|
||||
},
|
||||
};
|
||||
|
||||
export const Interactive: Story = {
|
||||
render() {
|
||||
const [reacted, setReacted] = useState(false);
|
||||
const [count, setCount] = useState(5);
|
||||
|
||||
const handleClick = () => {
|
||||
setReacted(!reacted);
|
||||
setCount(reacted ? count - 1 : count + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<EmojiReaction
|
||||
emoji="👍"
|
||||
count={count}
|
||||
reacted={reacted}
|
||||
users={["Alice", "Bob", "Charlie"]}
|
||||
onReactionClick={handleClick}
|
||||
/>
|
||||
<p className="text-sm text-custom-text-400">Click to toggle reaction</p>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithTooltip: Story = {
|
||||
args: {
|
||||
emoji: "🎉",
|
||||
count: 8,
|
||||
reacted: false,
|
||||
users: ["Alice", "Bob", "Charlie", "David", "Emma", "Frank", "Grace", "Henry"],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithoutCount: Story = {
|
||||
args: {
|
||||
emoji: "🔥",
|
||||
count: 0,
|
||||
reacted: false,
|
||||
showCount: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-center">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Small</span>
|
||||
<EmojiReaction emoji="👍" count={5} size="sm" users={["Alice", "Bob"]} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Medium (default)</span>
|
||||
<EmojiReaction emoji="👍" count={5} size="md" users={["Alice", "Bob"]} />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Large</span>
|
||||
<EmojiReaction emoji="👍" count={5} size="lg" users={["Alice", "Bob"]} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleReactions: Story = {
|
||||
render() {
|
||||
const [reactions, setReactions] = useState<EmojiReactionType[]>([
|
||||
{ emoji: "👍", count: 5, reacted: false, users: ["Alice", "Bob", "Charlie"] },
|
||||
{ emoji: "❤️", count: 12, reacted: true, users: ["David", "Emma", "Frank"] },
|
||||
{ emoji: "🎉", count: 3, reacted: false, users: ["Grace"] },
|
||||
]);
|
||||
|
||||
const handleReactionClick = (emoji: string) => {
|
||||
setReactions((prev) =>
|
||||
prev.map((r) => {
|
||||
if (r.emoji === emoji) {
|
||||
return {
|
||||
...r,
|
||||
reacted: !r.reacted,
|
||||
count: r.reacted ? r.count - 1 : r.count + 1,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
{reactions.map((reaction) => (
|
||||
<EmojiReaction
|
||||
key={reaction.emoji}
|
||||
emoji={reaction.emoji}
|
||||
count={reaction.count}
|
||||
reacted={reaction.reacted}
|
||||
users={reaction.users}
|
||||
onReactionClick={handleReactionClick}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AddButton: Story = {
|
||||
render() {
|
||||
const handleAdd = () => {
|
||||
alert("Add reaction clicked");
|
||||
};
|
||||
|
||||
return <EmojiReactionButton onAddReaction={handleAdd} />;
|
||||
},
|
||||
};
|
||||
|
||||
export const AddButtonSizes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<span className="text-xs text-custom-text-400">Small</span>
|
||||
<EmojiReactionButton size="sm" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<span className="text-xs text-custom-text-400">Medium</span>
|
||||
<EmojiReactionButton size="md" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<span className="text-xs text-custom-text-400">Large</span>
|
||||
<EmojiReactionButton size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ReactionGroup: Story = {
|
||||
render() {
|
||||
const [reactions, setReactions] = useState<EmojiReactionType[]>([
|
||||
{ emoji: "👍", count: 5, reacted: false, users: ["Alice", "Bob", "Charlie"] },
|
||||
{ emoji: "❤️", count: 12, reacted: true, users: ["David", "Emma", "Frank"] },
|
||||
{ emoji: "🎉", count: 3, reacted: false, users: ["Grace"] },
|
||||
{ emoji: "🔥", count: 8, reacted: false, users: ["Henry", "Ivy"] },
|
||||
]);
|
||||
|
||||
const handleReactionClick = (emoji: string) => {
|
||||
setReactions((prev) =>
|
||||
prev.map((r) => {
|
||||
if (r.emoji === emoji) {
|
||||
return {
|
||||
...r,
|
||||
reacted: !r.reacted,
|
||||
count: r.reacted ? r.count - 1 : r.count + 1,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const handleAddReaction = () => {
|
||||
alert("Add reaction clicked");
|
||||
};
|
||||
|
||||
return (
|
||||
<EmojiReactionGroup
|
||||
reactions={reactions}
|
||||
onReactionClick={handleReactionClick}
|
||||
onAddReaction={handleAddReaction}
|
||||
/>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ReactionGroupSizes: Story = {
|
||||
render() {
|
||||
const reactions: EmojiReactionType[] = [
|
||||
{ emoji: "👍", count: 5, reacted: false, users: ["Alice", "Bob"] },
|
||||
{ emoji: "❤️", count: 12, reacted: true, users: ["Charlie", "David"] },
|
||||
{ emoji: "🎉", count: 3, reacted: false, users: ["Emma"] },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Small</span>
|
||||
<EmojiReactionGroup reactions={reactions} size="sm" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Medium</span>
|
||||
<EmojiReactionGroup reactions={reactions} size="md" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs text-custom-text-400">Large</span>
|
||||
<EmojiReactionGroup reactions={reactions} size="lg" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InMessageContext: Story = {
|
||||
render() {
|
||||
const [reactions, setReactions] = useState<EmojiReactionType[]>([
|
||||
{ emoji: "👍", count: 5, reacted: false, users: ["Alice", "Bob", "Charlie"] },
|
||||
{ emoji: "❤️", count: 2, reacted: true, users: ["You", "David"] },
|
||||
]);
|
||||
|
||||
const handleReactionClick = (emoji: string) => {
|
||||
setReactions((prev) =>
|
||||
prev.map((r) => {
|
||||
if (r.emoji === emoji) {
|
||||
return {
|
||||
...r,
|
||||
reacted: !r.reacted,
|
||||
count: r.reacted ? r.count - 1 : r.count + 1,
|
||||
};
|
||||
}
|
||||
return r;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="max-w-md border border-custom-border-200 rounded-lg p-4 space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-8 h-8 rounded-full bg-custom-primary-100 flex items-center justify-center text-white text-sm">
|
||||
AB
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium text-sm">Alice Brown</div>
|
||||
<div className="text-sm text-custom-text-300 mt-1">
|
||||
Hey everyone! Just wanted to share some exciting news about our project launch next week!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<EmojiReactionGroup reactions={reactions} onReactionClick={handleReactionClick} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ManyUsers: Story = {
|
||||
args: {
|
||||
emoji: "🎉",
|
||||
count: 47,
|
||||
reacted: true,
|
||||
users: ["Alice", "Bob", "Charlie", "David", "Emma", "Frank", "Grace", "Henry", "Ivy", "Jack", "Kate", "Liam"],
|
||||
},
|
||||
};
|
||||
|
||||
261
packages/propel/src/menu/menu.stories.tsx
Normal file
261
packages/propel/src/menu/menu.stories.tsx
Normal file
@@ -0,0 +1,261 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Settings, User, LogOut, Mail, Bell, HelpCircle } from "lucide-react";
|
||||
import { Menu } from "./menu";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Menu",
|
||||
component: Menu,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
subcomponents: {
|
||||
MenuItem: Menu.MenuItem,
|
||||
SubMenu: Menu.SubMenu,
|
||||
},
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
} satisfies Meta<typeof Menu>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Options">
|
||||
<Menu.MenuItem onClick={() => alert("Option 1 clicked")}>Option 1</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Option 2 clicked")}>Option 2</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Option 3 clicked")}>Option 3</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Account">
|
||||
<Menu.MenuItem onClick={() => alert("Profile")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<User className="h-4 w-4" />
|
||||
<span>Profile</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Settings")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Settings className="h-4 w-4" />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Messages")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Mail className="h-4 w-4" />
|
||||
<span>Messages</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Logout")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<LogOut className="h-4 w-4" />
|
||||
<span>Logout</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Ellipsis: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu ellipsis>
|
||||
<Menu.MenuItem onClick={() => alert("Edit")}>Edit</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Duplicate")}>Duplicate</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Delete")}>Delete</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const VerticalEllipsis: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu verticalEllipsis>
|
||||
<Menu.MenuItem onClick={() => alert("Edit")}>Edit</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Duplicate")}>Duplicate</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Delete")}>Delete</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const NoBorder: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Actions" noBorder>
|
||||
<Menu.MenuItem onClick={() => alert("Action 1")}>Action 1</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Action 2")}>Action 2</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Action 3")}>Action 3</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const NoChevron: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Menu" noChevron>
|
||||
<Menu.MenuItem onClick={() => alert("Item 1")}>Item 1</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Item 2")}>Item 2</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Item 3")}>Item 3</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Disabled Menu" disabled>
|
||||
<Menu.MenuItem onClick={() => alert("Item 1")}>Item 1</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Item 2")}>Item 2</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledItems: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Options">
|
||||
<Menu.MenuItem onClick={() => alert("Enabled")}>Enabled Item</Menu.MenuItem>
|
||||
<Menu.MenuItem disabled>Disabled Item</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Enabled")}>Another Enabled Item</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomButton: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu
|
||||
customButton={
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">Custom Button</button>
|
||||
}
|
||||
>
|
||||
<Menu.MenuItem onClick={() => alert("Option 1")}>Option 1</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Option 2")}>Option 2</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Option 3")}>Option 3</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithSubmenu: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="File">
|
||||
<Menu.MenuItem onClick={() => alert("New File")}>New File</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Open")}>Open</Menu.MenuItem>
|
||||
<Menu.SubMenu
|
||||
trigger="Export"
|
||||
className="min-w-[12rem] rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg"
|
||||
>
|
||||
<Menu.MenuItem onClick={() => alert("Export as PDF")}>Export as PDF</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Export as CSV")}>Export as CSV</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Export as JSON")}>Export as JSON</Menu.MenuItem>
|
||||
</Menu.SubMenu>
|
||||
<Menu.MenuItem onClick={() => alert("Close")}>Close</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MaxHeightSmall: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Small Height" maxHeight="sm">
|
||||
{Array.from({ length: 10 }, (_, i) => (
|
||||
<Menu.MenuItem key={i} onClick={() => alert(`Item ${i + 1}`)}>
|
||||
Item {i + 1}
|
||||
</Menu.MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MaxHeightLarge: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="Large Height" maxHeight="lg">
|
||||
{Array.from({ length: 15 }, (_, i) => (
|
||||
<Menu.MenuItem key={i} onClick={() => alert(`Item ${i + 1}`)}>
|
||||
Item {i + 1}
|
||||
</Menu.MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ComplexMenu: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu label="More Actions" buttonClassName="bg-gray-100">
|
||||
<Menu.MenuItem onClick={() => alert("Notifications")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell className="h-4 w-4" />
|
||||
<span>Notifications</span>
|
||||
<span className="ml-auto rounded bg-red-500 px-2 py-0.5 text-xs text-white">3</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Help")}>
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="h-4 w-4" />
|
||||
<span>Help Center</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
<Menu.SubMenu
|
||||
trigger="Settings"
|
||||
className="min-w-[12rem] rounded-md border-[0.5px] border-custom-border-300 bg-custom-background-100 px-2 py-2.5 text-xs shadow-custom-shadow-rg"
|
||||
>
|
||||
<Menu.MenuItem onClick={() => alert("General Settings")}>General</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Privacy Settings")}>Privacy</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Security Settings")}>Security</Menu.MenuItem>
|
||||
</Menu.SubMenu>
|
||||
<div className="my-1 border-t border-gray-200" />
|
||||
<Menu.MenuItem onClick={() => alert("Logout")}>
|
||||
<div className="flex items-center gap-2 text-red-500">
|
||||
<LogOut className="h-4 w-4" />
|
||||
<span>Logout</span>
|
||||
</div>
|
||||
</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyles: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Menu
|
||||
label="Styled Menu"
|
||||
buttonClassName="bg-purple-500 text-white hover:bg-purple-600"
|
||||
optionsClassName="bg-purple-50 border-purple-300"
|
||||
>
|
||||
<Menu.MenuItem onClick={() => alert("Item 1")} className="hover:bg-purple-200">
|
||||
Item 1
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Item 2")} className="hover:bg-purple-200">
|
||||
Item 2
|
||||
</Menu.MenuItem>
|
||||
<Menu.MenuItem onClick={() => alert("Item 3")} className="hover:bg-purple-200">
|
||||
Item 3
|
||||
</Menu.MenuItem>
|
||||
</Menu>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,33 +1,22 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Pill, EPillVariant, EPillSize } from "./pill";
|
||||
|
||||
const meta: Meta<typeof Pill> = {
|
||||
const meta = {
|
||||
title: "Components/Pill",
|
||||
component: Pill,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: Object.values(EPillVariant),
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: Object.values(EPillSize),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Pill>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: "Default",
|
||||
},
|
||||
};
|
||||
} satisfies Meta<typeof Pill>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
@@ -86,58 +75,66 @@ export const Large: Story = {
|
||||
};
|
||||
|
||||
export const AllVariants: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.DEFAULT}>Default</Pill>
|
||||
<Pill variant={EPillVariant.PRIMARY}>Primary</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>Success</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>Warning</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>Error</Pill>
|
||||
<Pill variant={EPillVariant.INFO}>Info</Pill>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.DEFAULT}>Default</Pill>
|
||||
<Pill variant={EPillVariant.PRIMARY}>Primary</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>Success</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>Warning</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>Error</Pill>
|
||||
<Pill variant={EPillVariant.INFO}>Info</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Pill size={EPillSize.SM}>Small</Pill>
|
||||
<Pill size={EPillSize.MD}>Medium</Pill>
|
||||
<Pill size={EPillSize.LG}>Large</Pill>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Pill size={EPillSize.SM}>Small</Pill>
|
||||
<Pill size={EPillSize.MD}>Medium</Pill>
|
||||
<Pill size={EPillSize.LG}>Large</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithNumbers: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.PRIMARY}>3</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>12</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>99+</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>!</Pill>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.PRIMARY}>3</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>12</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>99+</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>!</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const StatusExamples: Story = {
|
||||
render: () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Task Status</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.DEFAULT}>Draft</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>In Progress</Pill>
|
||||
<Pill variant={EPillVariant.INFO}>In Review</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>Completed</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>Blocked</Pill>
|
||||
render() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium">Task Status</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Pill variant={EPillVariant.DEFAULT}>Draft</Pill>
|
||||
<Pill variant={EPillVariant.WARNING}>In Progress</Pill>
|
||||
<Pill variant={EPillVariant.INFO}>In Review</Pill>
|
||||
<Pill variant={EPillVariant.SUCCESS}>Completed</Pill>
|
||||
<Pill variant={EPillVariant.ERROR}>Blocked</Pill>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
311
packages/propel/src/popover/popover.stories.tsx
Normal file
311
packages/propel/src/popover/popover.stories.tsx
Normal file
@@ -0,0 +1,311 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { X } from "lucide-react";
|
||||
import { useArgs } from "storybook/preview-api";
|
||||
import { Popover } from "./root";
|
||||
|
||||
// cannot use satifies here because base-ui does not have portable types.
|
||||
const meta: Meta<typeof Popover> = {
|
||||
title: "Components/Popover",
|
||||
component: Popover,
|
||||
subcomponents: {
|
||||
PopoverButton: Popover.Button,
|
||||
PopoverPanel: Popover.Panel,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
children: null,
|
||||
open: undefined,
|
||||
onOpenChange: () => {},
|
||||
},
|
||||
render(args) {
|
||||
const [{ open }, updateArgs] = useArgs();
|
||||
const setOpen = (value: boolean | undefined) => updateArgs({ open: value });
|
||||
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Popover
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Popover Title</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This is the popover content. You can put any content here.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
};
|
||||
|
||||
export const Controlled: Story = {
|
||||
render() {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setOpen(true)} className="rounded bg-blue-500 px-3 py-1.5 text-sm text-white">
|
||||
Open
|
||||
</button>
|
||||
<button onClick={() => setOpen(false)} className="rounded bg-gray-500 px-3 py-1.5 text-sm text-white">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Controlled Popover
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<div className="flex items-start justify-between">
|
||||
<h3 className="text-sm font-semibold">Controlled State</h3>
|
||||
<button onClick={() => setOpen(false)} className="rounded-full p-1 hover:bg-gray-100">
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-gray-600">Current state: {open ? "Open" : "Closed"}</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SideTop: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Above
|
||||
</Popover.Button>
|
||||
<Popover.Panel side="top" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Top Positioned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover appears above the button.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SideBottom: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Below
|
||||
</Popover.Button>
|
||||
<Popover.Panel side="bottom" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Bottom Positioned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover appears below the button.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SideLeft: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Left
|
||||
</Popover.Button>
|
||||
<Popover.Panel side="left" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Left Positioned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover appears to the left of the button.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const SideRight: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Right
|
||||
</Popover.Button>
|
||||
<Popover.Panel side="right" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Right Positioned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover appears to the right of the button.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignStart: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Align Start
|
||||
</Popover.Button>
|
||||
<Popover.Panel align="start" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Start Aligned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover is aligned to the start.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignEnd: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Align End
|
||||
</Popover.Button>
|
||||
<Popover.Panel align="end" className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">End Aligned</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover is aligned to the end.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomOffset: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Custom Offset
|
||||
</Popover.Button>
|
||||
<Popover.Panel sideOffset={20} className="w-64 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Custom Side Offset</h3>
|
||||
<p className="mt-2 text-sm text-gray-600">This popover has a custom side offset of 20px.</p>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithForm: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open ?? false);
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
alert("Form submitted!");
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Open Form
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="w-72 rounded-lg border border-gray-200 bg-white p-4 shadow-lg">
|
||||
<h3 className="text-sm font-semibold">Quick Form</h3>
|
||||
<form onSubmit={handleSubmit} className="mt-3 space-y-3">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-xs font-medium text-gray-700">
|
||||
Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="name"
|
||||
className="mt-1 w-full rounded border border-gray-300 px-2 py-1.5 text-sm"
|
||||
placeholder="Enter name"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-xs font-medium text-gray-700">
|
||||
Email
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
className="mt-1 w-full rounded border border-gray-300 px-2 py-1.5 text-sm"
|
||||
placeholder="Enter email"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen(false)}
|
||||
className="rounded bg-gray-200 px-3 py-1.5 text-xs hover:bg-gray-300"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="rounded bg-blue-500 px-3 py-1.5 text-xs text-white hover:bg-blue-600">
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithList: Story = {
|
||||
render(args) {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<Popover {...args} open={open} onOpenChange={setOpen}>
|
||||
<Popover.Button className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Show Options
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="w-56 rounded-lg border border-gray-200 bg-white shadow-lg">
|
||||
<div className="p-2">
|
||||
<h3 className="px-2 py-1.5 text-xs font-semibold text-gray-500">Options</h3>
|
||||
<button className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-gray-100">Option 1</button>
|
||||
<button className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-gray-100">Option 2</button>
|
||||
<button className="w-full rounded px-2 py-1.5 text-left text-sm hover:bg-gray-100">Option 3</button>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ColorPicker: Story = {
|
||||
render() {
|
||||
const [selectedColor, setSelectedColor] = useState("#3b82f6");
|
||||
const colors = ["#ef4444", "#f59e0b", "#10b981", "#3b82f6", "#8b5cf6", "#ec4899", "#6b7280", "#000000", "#ffffff"];
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<Popover.Button className="flex items-center gap-2 rounded border border-gray-300 bg-white px-4 py-2 hover:bg-gray-50">
|
||||
<div className="h-4 w-4 rounded" style={{ backgroundColor: selectedColor }} />
|
||||
<span className="text-sm">Pick Color</span>
|
||||
</Popover.Button>
|
||||
<Popover.Panel className="w-48 rounded-lg border border-gray-200 bg-white p-3 shadow-lg">
|
||||
<h3 className="mb-2 text-xs font-semibold">Select Color</h3>
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
{colors.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
onClick={() => setSelectedColor(color)}
|
||||
className="h-8 w-8 rounded border-2 transition-transform hover:scale-110"
|
||||
style={{
|
||||
backgroundColor: color,
|
||||
borderColor: selectedColor === color ? "#000" : "transparent",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Button, EButtonVariant, EButtonSize } from "../button/button";
|
||||
import { Button } from "../button/button";
|
||||
import type { TButtonVariant } from "../button/helper";
|
||||
import { EPortalWidth, EPortalPosition } from "./constants";
|
||||
import { ModalPortal, PortalWrapper } from "./";
|
||||
|
||||
const meta: Meta<typeof ModalPortal> = {
|
||||
const meta = {
|
||||
title: "Components/Portal/ModalPortal",
|
||||
component: ModalPortal,
|
||||
parameters: {
|
||||
@@ -19,48 +20,34 @@ Perfect for modals, drawers, overlays, and any UI that needs to appear above oth
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
width: {
|
||||
control: "select",
|
||||
options: Object.values(EPortalWidth),
|
||||
description: "Modal width preset",
|
||||
},
|
||||
position: {
|
||||
control: "select",
|
||||
options: Object.values(EPortalPosition),
|
||||
description: "Modal position on screen",
|
||||
},
|
||||
fullScreen: {
|
||||
control: "boolean",
|
||||
description: "Render modal in fullscreen mode",
|
||||
},
|
||||
showOverlay: {
|
||||
control: "boolean",
|
||||
description: "Show/hide background overlay",
|
||||
},
|
||||
closeOnOverlayClick: {
|
||||
control: "boolean",
|
||||
description: "Close modal when clicking overlay",
|
||||
},
|
||||
closeOnEscape: {
|
||||
control: "boolean",
|
||||
description: "Close modal when pressing Escape",
|
||||
},
|
||||
args: {
|
||||
isOpen: false,
|
||||
children: null,
|
||||
},
|
||||
};
|
||||
render(args) {
|
||||
return (
|
||||
<ModalDemo {...args} buttonText="Open Modal">
|
||||
<ModalContent
|
||||
title="Default Modal"
|
||||
description="A standard modal with all default settings. Demonstrates focus management, keyboard navigation, and accessibility features."
|
||||
/>
|
||||
</ModalDemo>
|
||||
);
|
||||
},
|
||||
} satisfies Meta<typeof ModalPortal>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ModalPortal>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
// Helper component for interactive stories
|
||||
const ModalDemo = ({
|
||||
children,
|
||||
buttonText = "Open Modal",
|
||||
buttonVariant = EButtonVariant.PRIMARY,
|
||||
buttonVariant = "primary",
|
||||
...modalProps
|
||||
}: Omit<Parameters<typeof ModalPortal>[0], "isOpen" | "onClose"> & {
|
||||
buttonText?: string;
|
||||
buttonVariant?: Parameters<typeof Button>[0]["variant"];
|
||||
buttonVariant?: TButtonVariant;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
@@ -94,7 +81,7 @@ const ModalContent = ({
|
||||
<p className="text-sm text-gray-500 mt-1">Modal demonstration</p>
|
||||
</div>
|
||||
{showCloseButton && onClose && (
|
||||
<Button variant={EButtonVariant.GHOST} size={EButtonSize.SM} onClick={onClose} aria-label="Close modal">
|
||||
<Button variant="link-neutral" size="sm" onClick={onClose} aria-label="Close modal">
|
||||
✕
|
||||
</Button>
|
||||
)}
|
||||
@@ -115,27 +102,18 @@ const ModalContent = ({
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<ModalDemo buttonText="Open Modal">
|
||||
<ModalContent
|
||||
title="Default Modal"
|
||||
description="A standard modal with all default settings. Demonstrates focus management, keyboard navigation, and accessibility features."
|
||||
/>
|
||||
</ModalDemo>
|
||||
),
|
||||
};
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Positions: Story = {
|
||||
name: "Different Positions",
|
||||
render: () => {
|
||||
render() {
|
||||
const [activeModal, setActiveModal] = useState<EPortalPosition | null>(null);
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
{Object.values(EPortalPosition).map((position) => (
|
||||
<React.Fragment key={position}>
|
||||
<Button variant={EButtonVariant.OUTLINE} onClick={() => setActiveModal(position)}>
|
||||
<Button variant="outline-primary" onClick={() => setActiveModal(position)}>
|
||||
{position.charAt(0).toUpperCase() + position.slice(1)}
|
||||
</Button>
|
||||
<ModalPortal
|
||||
@@ -159,14 +137,14 @@ export const Positions: Story = {
|
||||
|
||||
export const Widths: Story = {
|
||||
name: "Different Widths",
|
||||
render: () => {
|
||||
render() {
|
||||
const [activeModal, setActiveModal] = useState<EPortalWidth | null>(null);
|
||||
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
{Object.values(EPortalWidth).map((width) => (
|
||||
<React.Fragment key={width}>
|
||||
<Button variant={EButtonVariant.SECONDARY} onClick={() => setActiveModal(width)}>
|
||||
<Button variant="neutral-primary" onClick={() => setActiveModal(width)}>
|
||||
{width.replace("_", " ").replace(/\b\w/g, (l) => l.toUpperCase())}
|
||||
</Button>
|
||||
<ModalPortal
|
||||
@@ -188,10 +166,19 @@ export const Widths: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
// PortalWrapper Stories
|
||||
const PortalWrapperMeta: Meta<typeof PortalWrapper> = {
|
||||
title: "Components/Portal/PortalWrapper",
|
||||
component: PortalWrapper,
|
||||
export const BasicPortal: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<p>This content renders in the normal document flow.</p>
|
||||
<PortalWrapper portalId="storybook-portal">
|
||||
<div className="fixed top-4 right-4 p-4 bg-blue-500 text-white rounded shadow-lg z-50">
|
||||
This content is rendered in a portal!
|
||||
</div>
|
||||
</PortalWrapper>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
docs: {
|
||||
@@ -202,21 +189,4 @@ It's used internally by ModalPortal but can also be used directly for custom por
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
export const BasicPortal: StoryObj<typeof PortalWrapper> = {
|
||||
render: () => (
|
||||
<div className="relative">
|
||||
<p>This content renders in the normal document flow.</p>
|
||||
<PortalWrapper portalId="storybook-portal">
|
||||
<div className="fixed top-4 right-4 p-4 bg-blue-500 text-white rounded shadow-lg z-50">
|
||||
This content is rendered in a portal!
|
||||
</div>
|
||||
</PortalWrapper>
|
||||
</div>
|
||||
),
|
||||
parameters: {
|
||||
...PortalWrapperMeta.parameters,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { ScrollArea } from "./scrollarea";
|
||||
|
||||
const meta: Meta<typeof ScrollArea> = {
|
||||
const meta = {
|
||||
title: "Components/ScrollArea",
|
||||
component: ScrollArea,
|
||||
parameters: {
|
||||
@@ -13,88 +13,291 @@ const meta: Meta<typeof ScrollArea> = {
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
orientation: {
|
||||
control: { type: "select" },
|
||||
options: ["vertical", "horizontal"],
|
||||
description: "Orientation of the scrollbar",
|
||||
table: {
|
||||
type: { summary: "ScrollAreaOrientation" },
|
||||
defaultValue: { summary: "vertical" },
|
||||
},
|
||||
},
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["sm", "md", "lg"],
|
||||
description: "Size variant of the scrollbar",
|
||||
table: {
|
||||
type: { summary: "ScrollAreaSize" },
|
||||
defaultValue: { summary: "md" },
|
||||
},
|
||||
},
|
||||
scrollType: {
|
||||
control: { type: "select" },
|
||||
options: ["always", "scroll", "hover"],
|
||||
description: "When to show the scrollbar",
|
||||
table: {
|
||||
type: { summary: "ScrollAreaScrollType" },
|
||||
defaultValue: { summary: "always" },
|
||||
},
|
||||
},
|
||||
className: {
|
||||
control: { type: "text" },
|
||||
description: "Additional CSS classes",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ScrollArea>;
|
||||
|
||||
// Sample content components for stories
|
||||
const LongTextContent = () => (
|
||||
<div className="p-4 space-y-4">
|
||||
<h3 className="text-lg font-semibold">Long Text Content</h3>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore
|
||||
magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
|
||||
consequat.
|
||||
</p>
|
||||
<p>
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur
|
||||
sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
<p>
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem
|
||||
aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
|
||||
</p>
|
||||
<p>
|
||||
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores
|
||||
eos qui ratione voluptatem sequi nesciunt.
|
||||
</p>
|
||||
<p>
|
||||
Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam
|
||||
eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem.
|
||||
</p>
|
||||
<p>
|
||||
Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea
|
||||
commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae
|
||||
consequatur.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Default story
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
className: "h-64 w-80 border rounded-lg",
|
||||
size: "md",
|
||||
scrollType: "always",
|
||||
orientation: "vertical",
|
||||
},
|
||||
render: (args) => (
|
||||
<ScrollArea {...args}>
|
||||
<LongTextContent />
|
||||
</ScrollArea>
|
||||
),
|
||||
} satisfies Meta<typeof ScrollArea>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render(args) {
|
||||
return (
|
||||
<ScrollArea {...args} className="h-64 w-80 border rounded-lg">
|
||||
<div className="p-4 space-y-4">
|
||||
<h3 className="text-lg font-semibold">Long Text Content</h3>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et
|
||||
dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex
|
||||
ea commodo consequat.
|
||||
</p>
|
||||
<p>
|
||||
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
|
||||
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est
|
||||
laborum.
|
||||
</p>
|
||||
<p>
|
||||
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem
|
||||
aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
|
||||
</p>
|
||||
<p>
|
||||
Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni
|
||||
dolores eos qui ratione voluptatem sequi nesciunt.
|
||||
</p>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render() {
|
||||
const content = (
|
||||
<div className="p-4 space-y-2">
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<p key={i}>Line {i + 1}: This is some scrollable content to demonstrate different sizes.</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Small</div>
|
||||
<ScrollArea className="h-48 w-80 border rounded-lg" size="sm">
|
||||
{content}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Medium</div>
|
||||
<ScrollArea className="h-48 w-80 border rounded-lg" size="md">
|
||||
{content}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="text-sm font-medium">Large</div>
|
||||
<ScrollArea className="h-48 w-80 border rounded-lg" size="lg">
|
||||
{content}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollTypeAlways: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-64 w-80 border rounded-lg" scrollType="always">
|
||||
<div className="p-4 space-y-2">
|
||||
<h3 className="text-lg font-semibold">Always Visible Scrollbar</h3>
|
||||
{[...Array(15)].map((_, i) => (
|
||||
<p key={i}>Line {i + 1}: The scrollbar is always visible.</p>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollTypeScroll: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-64 w-80 border rounded-lg" scrollType="scroll">
|
||||
<div className="p-4 space-y-2">
|
||||
<h3 className="text-lg font-semibold">Scroll to Show</h3>
|
||||
<p className="text-sm text-custom-text-400">Scrollbar appears when scrolling</p>
|
||||
{[...Array(15)].map((_, i) => (
|
||||
<p key={i}>Line {i + 1}: Try scrolling to see the scrollbar appear.</p>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ScrollTypeHover: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-64 w-80 border rounded-lg" scrollType="hover">
|
||||
<div className="p-4 space-y-2">
|
||||
<h3 className="text-lg font-semibold">Hover to Show</h3>
|
||||
<p className="text-sm text-custom-text-400">Scrollbar appears on hover</p>
|
||||
{[...Array(15)].map((_, i) => (
|
||||
<p key={i}>Line {i + 1}: Hover over the area to see the scrollbar.</p>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const HorizontalScroll: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-32 w-96 border rounded-lg" orientation="horizontal">
|
||||
<div className="flex gap-4 p-4 w-[1200px]">
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-shrink-0 w-32 h-20 bg-custom-background-80 rounded flex items-center justify-center"
|
||||
>
|
||||
Item {i + 1}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const BothDirections: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-64 w-96 border rounded-lg">
|
||||
<div className="w-[800px] p-4 space-y-2">
|
||||
<h3 className="text-lg font-semibold">Both Directions</h3>
|
||||
<p className="text-sm text-custom-text-400">Content scrolls both vertically and horizontally</p>
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<p key={i}>
|
||||
Line {i + 1}: This line is very long and extends beyond the container width to demonstrate horizontal
|
||||
scrolling along with vertical scrolling.
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ListExample: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-80 w-96 border rounded-lg">
|
||||
<div className="p-4">
|
||||
<h3 className="text-lg font-semibold mb-4">User List</h3>
|
||||
<div className="space-y-2">
|
||||
{[...Array(25)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center gap-3 p-3 bg-custom-background-80 rounded hover:bg-custom-background-90 cursor-pointer"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-custom-primary-100 flex items-center justify-center text-white font-medium">
|
||||
{String.fromCharCode(65 + (i % 26))}
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">User {i + 1}</div>
|
||||
<div className="text-sm text-custom-text-400">user{i + 1}@example.com</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CodeBlock: Story = {
|
||||
render() {
|
||||
const code = `function fibonacci(n) {
|
||||
if (n <= 1) return n;
|
||||
|
||||
let a = 0, b = 1;
|
||||
for (let i = 2; i <= n; i++) {
|
||||
const temp = a + b;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
// Example usage
|
||||
console.log(fibonacci(10)); // 55
|
||||
console.log(fibonacci(20)); // 6765
|
||||
|
||||
const numbers = [1, 2, 3, 4, 5];
|
||||
const doubled = numbers.map(n => n * 2);
|
||||
console.log(doubled); // [2, 4, 6, 8, 10]
|
||||
|
||||
async function fetchData() {
|
||||
try {
|
||||
const response = await fetch('https://api.example.com/data');
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching data:', error);
|
||||
}
|
||||
}`;
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-96 w-full max-w-2xl border rounded-lg bg-custom-background-100">
|
||||
<pre className="p-4 text-sm">
|
||||
<code>{code}</code>
|
||||
</pre>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ChatMessages: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-96 w-full max-w-md border rounded-lg">
|
||||
<div className="p-4 space-y-4">
|
||||
{[...Array(20)].map((_, i) => (
|
||||
<div key={i} className={`flex ${i % 3 === 0 ? "justify-end" : "justify-start"}`}>
|
||||
<div
|
||||
className={`max-w-[70%] p-3 rounded-lg ${
|
||||
i % 3 === 0 ? "bg-custom-primary-100 text-white" : "bg-custom-background-80"
|
||||
}`}
|
||||
>
|
||||
<div className="text-sm">{i % 3 === 0 ? "You" : `User ${i + 1}`}</div>
|
||||
<div className="mt-1">Message content for message number {i + 1}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DataTable: Story = {
|
||||
render() {
|
||||
return (
|
||||
<ScrollArea className="h-96 w-full max-w-3xl border rounded-lg">
|
||||
<table className="w-full">
|
||||
<thead className="bg-custom-background-80 sticky top-0">
|
||||
<tr>
|
||||
<th className="px-4 py-2 text-left">ID</th>
|
||||
<th className="px-4 py-2 text-left">Name</th>
|
||||
<th className="px-4 py-2 text-left">Email</th>
|
||||
<th className="px-4 py-2 text-left">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{[...Array(50)].map((_, i) => (
|
||||
<tr key={i} className="border-t border-custom-border-200 hover:bg-custom-background-80">
|
||||
<td className="px-4 py-2">#{i + 1}</td>
|
||||
<td className="px-4 py-2">User {i + 1}</td>
|
||||
<td className="px-4 py-2">user{i + 1}@example.com</td>
|
||||
<td className="px-4 py-2">
|
||||
<span
|
||||
className={`px-2 py-1 rounded text-xs ${i % 3 === 0 ? "bg-green-500/20 text-green-500" : "bg-gray-500/20 text-gray-500"}`}
|
||||
>
|
||||
{i % 3 === 0 ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</ScrollArea>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,50 +1,56 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Separator } from "./separator";
|
||||
|
||||
const meta: Meta<typeof Separator> = {
|
||||
const meta = {
|
||||
title: "Components/Separator",
|
||||
component: Separator,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
} satisfies Meta<typeof Separator>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Separator>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] space-y-4">
|
||||
<div>Content Above</div>
|
||||
<Separator />
|
||||
<div>Content Below</div>
|
||||
</div>
|
||||
),
|
||||
render() {
|
||||
return (
|
||||
<div className="w-[300px] space-y-4">
|
||||
<div>Content Above</div>
|
||||
<Separator />
|
||||
<div>Content Below</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Vertical: Story = {
|
||||
render: () => (
|
||||
<div className="flex h-[100px] items-center space-x-4">
|
||||
<div>Left Content</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Right Content</div>
|
||||
</div>
|
||||
),
|
||||
render() {
|
||||
return (
|
||||
<div className="flex h-[100px] items-center space-x-4">
|
||||
<div>Left Content</div>
|
||||
<Separator orientation="vertical" />
|
||||
<div>Right Content</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithinContainer: Story = {
|
||||
render: () => (
|
||||
<div className="w-[300px] rounded-lg border p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Section 1</h4>
|
||||
<p className="text-sm text-muted-foreground">Description for section 1</p>
|
||||
render() {
|
||||
return (
|
||||
<div className="w-[300px] rounded-lg border p-6 space-y-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Section 1</h4>
|
||||
<p className="text-sm text-muted-foreground">Description for section 1</p>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Section 2</h4>
|
||||
<p className="text-sm text-muted-foreground">Description for section 2</p>
|
||||
</div>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Section 2</h4>
|
||||
<p className="text-sm text-muted-foreground">Description for section 2</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,33 +1,194 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Skeleton } from "./index";
|
||||
|
||||
const meta: Meta<typeof Skeleton> = {
|
||||
const meta = {
|
||||
title: "Components/Skeleton",
|
||||
component: Skeleton,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
};
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
} satisfies Meta<typeof Skeleton>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<Skeleton className="w-80 flex flex-col gap-2">
|
||||
<Skeleton.Item height="40px" width="100%" />
|
||||
</Skeleton>
|
||||
),
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-80 flex flex-col gap-2">
|
||||
<Skeleton.Item height="40px" width="100%" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Card: Story = {
|
||||
render: () => (
|
||||
<Skeleton className="w-80 flex flex-col gap-4">
|
||||
<Skeleton.Item height="200px" width="100%" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="20px" width="50%" />
|
||||
<Skeleton.Item height="20px" width="30%" />
|
||||
</div>
|
||||
</Skeleton>
|
||||
),
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-80 flex flex-col gap-4">
|
||||
<Skeleton.Item height="200px" width="100%" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="20px" width="60%" />
|
||||
<Skeleton.Item height="16px" width="40%" />
|
||||
</div>
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const List: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-96 flex flex-col gap-3">
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="flex gap-3">
|
||||
<Skeleton.Item height="40px" width="40px" className="rounded-full" />
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<Skeleton.Item height="16px" width="70%" />
|
||||
<Skeleton.Item height="12px" width="50%" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Table: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-full flex flex-col gap-3">
|
||||
<div className="flex gap-4">
|
||||
<Skeleton.Item height="20px" width="150px" />
|
||||
<Skeleton.Item height="20px" width="200px" />
|
||||
<Skeleton.Item height="20px" width="120px" />
|
||||
</div>
|
||||
{[...Array(5)].map((_, i) => (
|
||||
<div key={i} className="flex gap-4">
|
||||
<Skeleton.Item height="40px" width="150px" />
|
||||
<Skeleton.Item height="40px" width="200px" />
|
||||
<Skeleton.Item height="40px" width="120px" />
|
||||
</div>
|
||||
))}
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Profile: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-80 flex flex-col gap-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Skeleton.Item height="80px" width="80px" className="rounded-full" />
|
||||
<div className="flex-1 flex flex-col gap-2">
|
||||
<Skeleton.Item height="20px" width="60%" />
|
||||
<Skeleton.Item height="16px" width="40%" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="16px" width="100%" />
|
||||
<Skeleton.Item height="16px" width="90%" />
|
||||
<Skeleton.Item height="16px" width="70%" />
|
||||
</div>
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Avatar: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="flex gap-2">
|
||||
<Skeleton.Item height="40px" width="40px" className="rounded-full" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AvatarGroup: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="flex -space-x-2">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<Skeleton.Item key={i} height="40px" width="40px" className="rounded-full border-2 border-white" />
|
||||
))}
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Text: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-96 flex flex-col gap-2">
|
||||
<Skeleton.Item height="16px" width="100%" />
|
||||
<Skeleton.Item height="16px" width="95%" />
|
||||
<Skeleton.Item height="16px" width="90%" />
|
||||
<Skeleton.Item height="16px" width="60%" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Button: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="inline-flex">
|
||||
<Skeleton.Item height="40px" width="120px" className="rounded-md" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Input: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-80 flex flex-col gap-2">
|
||||
<Skeleton.Item height="14px" width="80px" />
|
||||
<Skeleton.Item height="40px" width="100%" className="rounded-md" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Form: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-96 flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="14px" width="80px" />
|
||||
<Skeleton.Item height="40px" width="100%" className="rounded-md" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="14px" width="100px" />
|
||||
<Skeleton.Item height="40px" width="100%" className="rounded-md" />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="14px" width="60px" />
|
||||
<Skeleton.Item height="80px" width="100%" className="rounded-md" />
|
||||
</div>
|
||||
<Skeleton.Item height="40px" width="120px" className="rounded-md" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ProductCard: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Skeleton className="w-72 flex flex-col gap-3 p-4 border rounded-lg">
|
||||
<Skeleton.Item height="200px" width="100%" className="rounded-md" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton.Item height="20px" width="80%" />
|
||||
<Skeleton.Item height="16px" width="60%" />
|
||||
<Skeleton.Item height="24px" width="40%" />
|
||||
</div>
|
||||
<Skeleton.Item height="40px" width="100%" className="rounded-md" />
|
||||
</Skeleton>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
136
packages/propel/src/spinners/circular-bar-spinner.stories.tsx
Normal file
136
packages/propel/src/spinners/circular-bar-spinner.stories.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { CircularBarSpinner } from "./circular-bar-spinner";
|
||||
|
||||
const meta = {
|
||||
title: "Components/CircularBarSpinner",
|
||||
component: CircularBarSpinner,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
height: "16px",
|
||||
width: "16px",
|
||||
},
|
||||
} satisfies Meta<typeof CircularBarSpinner>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
height: "12px",
|
||||
width: "12px",
|
||||
},
|
||||
};
|
||||
|
||||
export const Medium: Story = {
|
||||
args: {
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
height: "32px",
|
||||
width: "32px",
|
||||
},
|
||||
};
|
||||
|
||||
export const ExtraLarge: Story = {
|
||||
args: {
|
||||
height: "48px",
|
||||
width: "48px",
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomColor: Story = {
|
||||
args: {
|
||||
className: "text-green-500",
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner height="12px" width="12px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Small</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner height="16px" width="16px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Default</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Medium</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner height="32px" width="32px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Large</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner height="48px" width="48px" />
|
||||
<p className="mt-2 text-xs text-gray-600">XL</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ColorVariations: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner className="text-blue-500" height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Blue</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner className="text-green-500" height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Green</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner className="text-red-500" height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Red</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner className="text-purple-500" height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Purple</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CircularBarSpinner className="text-orange-500" height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Orange</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InButton: Story = {
|
||||
render() {
|
||||
return (
|
||||
<button className="flex items-center gap-2 rounded bg-green-500 px-4 py-2 text-white">
|
||||
<CircularBarSpinner height="16px" width="16px" />
|
||||
<span>Processing...</span>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CenteredInCard: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="w-96 rounded-lg border border-gray-200 bg-white p-8 shadow-md">
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<CircularBarSpinner height="48px" width="48px" />
|
||||
<p className="text-sm text-gray-600">Processing data...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
136
packages/propel/src/spinners/circular-spinner.stories.tsx
Normal file
136
packages/propel/src/spinners/circular-spinner.stories.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Spinner } from "./circular-spinner";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Spinner",
|
||||
component: Spinner,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
height: "32px",
|
||||
width: "32px",
|
||||
},
|
||||
} satisfies Meta<typeof Spinner>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
height: "16px",
|
||||
width: "16px",
|
||||
},
|
||||
};
|
||||
|
||||
export const Medium: Story = {
|
||||
args: {
|
||||
height: "24px",
|
||||
width: "24px",
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
height: "48px",
|
||||
width: "48px",
|
||||
},
|
||||
};
|
||||
|
||||
export const ExtraLarge: Story = {
|
||||
args: {
|
||||
height: "64px",
|
||||
width: "64px",
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomColor: Story = {
|
||||
args: {
|
||||
className: "text-blue-500",
|
||||
},
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-center">
|
||||
<Spinner height="16px" width="16px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Small</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner height="24px" width="24px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Medium</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner height="32px" width="32px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Default</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner height="48px" width="48px" />
|
||||
<p className="mt-2 text-xs text-gray-600">Large</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner height="64px" width="64px" />
|
||||
<p className="mt-2 text-xs text-gray-600">XL</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ColorVariations: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-center">
|
||||
<Spinner className="text-blue-500" />
|
||||
<p className="mt-2 text-xs text-gray-600">Blue</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner className="text-green-500" />
|
||||
<p className="mt-2 text-xs text-gray-600">Green</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner className="text-red-500" />
|
||||
<p className="mt-2 text-xs text-gray-600">Red</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner className="text-purple-500" />
|
||||
<p className="mt-2 text-xs text-gray-600">Purple</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Spinner className="text-orange-500" />
|
||||
<p className="mt-2 text-xs text-gray-600">Orange</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InButton: Story = {
|
||||
render() {
|
||||
return (
|
||||
<button className="flex items-center gap-2 rounded bg-blue-500 px-4 py-2 text-white">
|
||||
<Spinner height="16px" width="16px" />
|
||||
<span>Loading...</span>
|
||||
</button>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CenteredInCard: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="w-96 rounded-lg border border-gray-200 bg-white p-8 shadow-md">
|
||||
<div className="flex flex-col items-center justify-center space-y-4">
|
||||
<Spinner height="48px" width="48px" />
|
||||
<p className="text-sm text-gray-600">Loading content...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import { Switch as BaseSwitch } from "@base-ui-components/react/switch";
|
||||
import { cn } from "../utils/classname";
|
||||
|
||||
interface IToggleSwitchProps {
|
||||
export interface IToggleSwitchProps {
|
||||
value: boolean;
|
||||
onChange: (value: boolean) => void;
|
||||
label?: string;
|
||||
|
||||
241
packages/propel/src/switch/switch.stories.tsx
Normal file
241
packages/propel/src/switch/switch.stories.tsx
Normal file
@@ -0,0 +1,241 @@
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { useArgs } from "storybook/preview-api";
|
||||
import { Switch } from "./root";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Switch",
|
||||
component: Switch,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: { value: false, onChange: () => {} },
|
||||
render(args) {
|
||||
const [{ value }, updateArgs] = useArgs();
|
||||
const setValue = (newValue: boolean) => updateArgs({ value: newValue });
|
||||
return <Switch {...args} value={value} onChange={setValue} />;
|
||||
},
|
||||
} satisfies Meta<typeof Switch>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Checked: Story = {
|
||||
args: { value: true },
|
||||
};
|
||||
|
||||
export const WithLabel: Story = {
|
||||
render(args) {
|
||||
const [value, setValue] = useState(args.value);
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Switch {...args} value={value} onChange={setValue} label="Enable notifications" />
|
||||
<label className="text-sm">Enable notifications</label>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: { size: "sm" },
|
||||
};
|
||||
|
||||
export const Medium: Story = {
|
||||
args: { size: "md" },
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: { size: "lg" },
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: { disabled: true },
|
||||
};
|
||||
|
||||
export const DisabledChecked: Story = {
|
||||
args: { value: true, disabled: true },
|
||||
};
|
||||
|
||||
export const AllSizes: Story = {
|
||||
render() {
|
||||
const [small, setSmall] = useState(false);
|
||||
const [medium, setMedium] = useState(false);
|
||||
const [large, setLarge] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="text-center">
|
||||
<Switch value={small} onChange={setSmall} size="sm" />
|
||||
<p className="mt-2 text-xs text-gray-600">Small</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Switch value={medium} onChange={setMedium} size="md" />
|
||||
<p className="mt-2 text-xs text-gray-600">Medium</p>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<Switch value={large} onChange={setLarge} size="lg" />
|
||||
<p className="mt-2 text-xs text-gray-600">Large</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllStates: Story = {
|
||||
render() {
|
||||
const [unchecked, setUnchecked] = useState(false);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [disabledUnchecked] = useState(false);
|
||||
const [disabledChecked] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch value={unchecked} onChange={setUnchecked} />
|
||||
<span className="text-sm text-gray-600">Unchecked</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch value={checked} onChange={setChecked} />
|
||||
<span className="text-sm text-gray-600">Checked</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch value={disabledUnchecked} onChange={() => {}} disabled />
|
||||
<span className="text-sm text-gray-600">Disabled Unchecked</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch value={disabledChecked} onChange={() => {}} disabled />
|
||||
<span className="text-sm text-gray-600">Disabled Checked</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InForm: Story = {
|
||||
render() {
|
||||
const [notifications, setNotifications] = useState(true);
|
||||
const [marketing, setMarketing] = useState(false);
|
||||
const [updates, setUpdates] = useState(true);
|
||||
|
||||
return (
|
||||
<div className="w-80 rounded-lg border border-gray-200 bg-white p-6 shadow-md">
|
||||
<h3 className="text-lg font-semibold">Notification Settings</h3>
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Push Notifications</p>
|
||||
<p className="text-xs text-gray-500">Receive push notifications on your device</p>
|
||||
</div>
|
||||
<Switch value={notifications} onChange={setNotifications} size="md" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Marketing Emails</p>
|
||||
<p className="text-xs text-gray-500">Receive emails about new features</p>
|
||||
</div>
|
||||
<Switch value={marketing} onChange={setMarketing} size="md" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-sm font-medium">Product Updates</p>
|
||||
<p className="text-xs text-gray-500">Get notified about product updates</p>
|
||||
</div>
|
||||
<Switch value={updates} onChange={setUpdates} size="md" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDescription: Story = {
|
||||
render() {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-96 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<h4 className="text-sm font-semibold">Enable Two-Factor Authentication</h4>
|
||||
<p className="mt-1 text-xs text-gray-500">
|
||||
Add an extra layer of security to your account by enabling two-factor authentication.
|
||||
</p>
|
||||
</div>
|
||||
<Switch value={enabled} onChange={setEnabled} size="md" className="ml-4" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Interactive: Story = {
|
||||
render() {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="w-80 space-y-4 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Feature Toggle</span>
|
||||
<Switch value={enabled} onChange={setEnabled} size="md" />
|
||||
</div>
|
||||
<div className="rounded bg-gray-50 p-4">
|
||||
<p className="text-sm text-gray-700">
|
||||
Status: <span className="font-semibold">{enabled ? "Enabled" : "Disabled"}</span>
|
||||
</p>
|
||||
{enabled && <p className="mt-2 text-xs text-green-600">Feature is now active and ready to use!</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyles: Story = {
|
||||
render() {
|
||||
const [value, setValue] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-4">
|
||||
<Switch
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
size="lg"
|
||||
className="border-2 border-purple-300 data-[state=checked]:bg-purple-500"
|
||||
/>
|
||||
<span className="text-sm">Custom styled switch</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleControls: Story = {
|
||||
render() {
|
||||
const [settings, setSettings] = useState({
|
||||
feature1: true,
|
||||
feature2: false,
|
||||
feature3: true,
|
||||
feature4: false,
|
||||
feature5: true,
|
||||
});
|
||||
|
||||
const toggleSetting = (key: keyof typeof settings) => {
|
||||
setSettings((prev) => ({ ...prev, [key]: !prev[key] }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-96 rounded-lg border border-gray-200 bg-white p-6">
|
||||
<h3 className="mb-4 text-lg font-semibold">Feature Flags</h3>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(settings).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center justify-between">
|
||||
<span className="text-sm capitalize">{key.replace(/([A-Z])/g, " $1").trim()}</span>
|
||||
<Switch value={value} onChange={() => toggleSetting(key as keyof typeof settings)} size="sm" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
376
packages/propel/src/table/table.stories.tsx
Normal file
376
packages/propel/src/table/table.stories.tsx
Normal file
@@ -0,0 +1,376 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption } from "./core";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Table",
|
||||
component: Table,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Table>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>John Doe</TableCell>
|
||||
<TableCell>john@example.com</TableCell>
|
||||
<TableCell>Admin</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jane Smith</TableCell>
|
||||
<TableCell>jane@example.com</TableCell>
|
||||
<TableCell>User</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Bob Wilson</TableCell>
|
||||
<TableCell>bob@example.com</TableCell>
|
||||
<TableCell>Moderator</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCaption: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableCaption>A list of recent users</TableCaption>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Alice Johnson</TableCell>
|
||||
<TableCell>alice@example.com</TableCell>
|
||||
<TableCell>Active</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Charlie Brown</TableCell>
|
||||
<TableCell>charlie@example.com</TableCell>
|
||||
<TableCell>Inactive</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithFooter: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Product</TableHead>
|
||||
<TableHead className="text-right">Quantity</TableHead>
|
||||
<TableHead className="text-right">Price</TableHead>
|
||||
<TableHead className="text-right">Total</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Product A</TableCell>
|
||||
<TableCell className="text-right">2</TableCell>
|
||||
<TableCell className="text-right">$10.00</TableCell>
|
||||
<TableCell className="text-right">$20.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Product B</TableCell>
|
||||
<TableCell className="text-right">1</TableCell>
|
||||
<TableCell className="text-right">$25.00</TableCell>
|
||||
<TableCell className="text-right">$25.00</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Product C</TableCell>
|
||||
<TableCell className="text-right">3</TableCell>
|
||||
<TableCell className="text-right">$15.00</TableCell>
|
||||
<TableCell className="text-right">$45.00</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="font-semibold">
|
||||
Total
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-semibold">$90.00</TableCell>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithActions: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
<TableHead className="text-right">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>John Doe</TableCell>
|
||||
<TableCell>john@example.com</TableCell>
|
||||
<TableCell>Admin</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<button className="mr-2 text-blue-500 hover:underline">Edit</button>
|
||||
<button className="text-red-500 hover:underline">Delete</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jane Smith</TableCell>
|
||||
<TableCell>jane@example.com</TableCell>
|
||||
<TableCell>User</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<button className="mr-2 text-blue-500 hover:underline">Edit</button>
|
||||
<button className="text-red-500 hover:underline">Delete</button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithBadges: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Project</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead>Priority</TableHead>
|
||||
<TableHead>Assignee</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>Website Redesign</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-green-100 px-2 py-1 text-xs text-green-800">In Progress</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-red-100 px-2 py-1 text-xs text-red-800">High</span>
|
||||
</TableCell>
|
||||
<TableCell>John Doe</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Mobile App</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-blue-100 px-2 py-1 text-xs text-blue-800">Planned</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-yellow-100 px-2 py-1 text-xs text-yellow-800">Medium</span>
|
||||
</TableCell>
|
||||
<TableCell>Jane Smith</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>API Integration</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-gray-100 px-2 py-1 text-xs text-gray-800">Completed</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span className="rounded-full bg-green-100 px-2 py-1 text-xs text-green-800">Low</span>
|
||||
</TableCell>
|
||||
<TableCell>Bob Wilson</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCheckboxes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>
|
||||
<input type="checkbox" className="h-4 w-4" aria-label="Select all rows" />
|
||||
</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<input type="checkbox" className="h-4 w-4" aria-label="Select row for John Doe" />
|
||||
</TableCell>
|
||||
<TableCell>John Doe</TableCell>
|
||||
<TableCell>john@example.com</TableCell>
|
||||
<TableCell>Admin</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<input type="checkbox" className="h-4 w-4" aria-label="Select row for Jane Smith" />
|
||||
</TableCell>
|
||||
<TableCell>Jane Smith</TableCell>
|
||||
<TableCell>jane@example.com</TableCell>
|
||||
<TableCell>User</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<input type="checkbox" className="h-4 w-4" aria-label="Select row for Bob Wilson" />
|
||||
</TableCell>
|
||||
<TableCell>Bob Wilson</TableCell>
|
||||
<TableCell>bob@example.com</TableCell>
|
||||
<TableCell>Moderator</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const EmptyState: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={3} className="h-24 text-center">
|
||||
No results found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeDataset: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead>Email</TableHead>
|
||||
<TableHead>Department</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{Array.from({ length: 15 }, (_, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell>{1000 + i}</TableCell>
|
||||
<TableCell>User {i + 1}</TableCell>
|
||||
<TableCell>user{i + 1}@example.com</TableCell>
|
||||
<TableCell>{["Engineering", "Sales", "Marketing", "Support"][i % 4]}</TableCell>
|
||||
<TableCell>
|
||||
<span
|
||||
className={`rounded-full px-2 py-1 text-xs ${
|
||||
i % 2 === 0 ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"
|
||||
}`}
|
||||
>
|
||||
{i % 2 === 0 ? "Active" : "Inactive"}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomStyling: Story = {
|
||||
render() {
|
||||
return (
|
||||
<Table className="border-2 border-blue-200">
|
||||
<TableHeader>
|
||||
<TableRow className="bg-blue-100">
|
||||
<TableHead className="text-blue-900">Name</TableHead>
|
||||
<TableHead className="text-blue-900">Email</TableHead>
|
||||
<TableHead className="text-blue-900">Role</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow className="hover:bg-blue-50">
|
||||
<TableCell className="font-semibold">John Doe</TableCell>
|
||||
<TableCell>john@example.com</TableCell>
|
||||
<TableCell>Admin</TableCell>
|
||||
</TableRow>
|
||||
<TableRow className="hover:bg-blue-50">
|
||||
<TableCell className="font-semibold">Jane Smith</TableCell>
|
||||
<TableCell>jane@example.com</TableCell>
|
||||
<TableCell>User</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ResponsiveTable: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="w-full max-w-4xl">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Name</TableHead>
|
||||
<TableHead className="hidden sm:table-cell">Email</TableHead>
|
||||
<TableHead className="hidden md:table-cell">Department</TableHead>
|
||||
<TableHead className="hidden lg:table-cell">Location</TableHead>
|
||||
<TableHead>Status</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>John Doe</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">john@example.com</TableCell>
|
||||
<TableCell className="hidden md:table-cell">Engineering</TableCell>
|
||||
<TableCell className="hidden lg:table-cell">New York</TableCell>
|
||||
<TableCell>Active</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>Jane Smith</TableCell>
|
||||
<TableCell className="hidden sm:table-cell">jane@example.com</TableCell>
|
||||
<TableCell className="hidden md:table-cell">Marketing</TableCell>
|
||||
<TableCell className="hidden lg:table-cell">San Francisco</TableCell>
|
||||
<TableCell>Active</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ComponentProps } from "react";
|
||||
import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Home, Settings, User, Bell } from "lucide-react";
|
||||
import { Tabs } from "./tabs";
|
||||
|
||||
type TabOption = {
|
||||
@@ -10,21 +11,24 @@ type TabOption = {
|
||||
const tabOptions: TabOption[] = [
|
||||
{ label: "Account", value: "account" },
|
||||
{ label: "Password", value: "password" },
|
||||
{ label: "Notifications", value: "notifications" },
|
||||
];
|
||||
|
||||
interface StoryProps extends ComponentProps<typeof Tabs> {
|
||||
options: TabOption[];
|
||||
}
|
||||
|
||||
const meta: Meta<StoryProps> = {
|
||||
// cannot use satisfies here because base-ui does not have portable types.
|
||||
const meta: Meta<typeof Tabs> = {
|
||||
title: "Components/Tabs",
|
||||
component: Tabs,
|
||||
subcomponents: {
|
||||
TabsList: Tabs.List,
|
||||
TabsTrigger: Tabs.Trigger,
|
||||
TabsContent: Tabs.Content,
|
||||
TabsIndicator: Tabs.Indicator,
|
||||
},
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
args: {
|
||||
defaultValue: "account",
|
||||
options: tabOptions,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,35 +36,24 @@ export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
options: [
|
||||
{
|
||||
label: "Account",
|
||||
value: "account",
|
||||
},
|
||||
{
|
||||
label: "Password",
|
||||
value: "password",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
render: ({ defaultValue, options }) => {
|
||||
const safeDefault = options?.some((o) => o.value === defaultValue) ? defaultValue : options?.[0]?.value;
|
||||
render({ defaultValue }) {
|
||||
return (
|
||||
<div className="w-[400px]">
|
||||
<Tabs defaultValue={safeDefault}>
|
||||
<Tabs defaultValue={defaultValue}>
|
||||
<Tabs.List>
|
||||
{options.map((option) => (
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Trigger key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{options.map((option) => (
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Content key={option.value} value={option.value} className="p-4">
|
||||
{option.label} content goes here
|
||||
<div className="text-sm">
|
||||
<h3 className="font-medium mb-2">{option.label}</h3>
|
||||
<p className="text-custom-text-300">Content for the {option.label.toLowerCase()} tab.</p>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
@@ -70,7 +63,7 @@ export const Basic: Story = {
|
||||
};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: ({ defaultValue, options }) => {
|
||||
render({ defaultValue }) {
|
||||
const sizes = ["sm", "md", "lg"] as const;
|
||||
const sizeLabels: Record<(typeof sizes)[number], string> = {
|
||||
sm: "Small",
|
||||
@@ -81,14 +74,15 @@ export const Sizes: Story = {
|
||||
<div className="w-[400px] grid gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size} className="flex flex-col gap-2">
|
||||
<div className="text-lg">{sizeLabels[size]}</div>
|
||||
<div className="text-sm font-medium">{sizeLabels[size]}</div>
|
||||
<Tabs defaultValue={defaultValue}>
|
||||
<Tabs.List>
|
||||
{options.map((option) => (
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Trigger key={option.value} value={option.value} size={size}>
|
||||
{option.label}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
</Tabs>
|
||||
</div>
|
||||
@@ -97,3 +91,268 @@ export const Sizes: Story = {
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Controlled: Story = {
|
||||
render() {
|
||||
const [activeTab, setActiveTab] = useState("account");
|
||||
|
||||
return (
|
||||
<div className="w-[400px]">
|
||||
<div className="mb-4 text-sm">
|
||||
Active tab: <span className="font-medium">{activeTab}</span>
|
||||
</div>
|
||||
<Tabs value={activeTab} onValueChange={(value) => value && setActiveTab(value)}>
|
||||
<Tabs.List>
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Trigger key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Content key={option.value} value={option.value} className="p-4">
|
||||
<div className="text-sm">Content for {option.label}</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DisabledTab: Story = {
|
||||
render({ defaultValue }) {
|
||||
return (
|
||||
<div className="w-[400px]">
|
||||
<Tabs defaultValue={defaultValue}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="account">Account</Tabs.Trigger>
|
||||
<Tabs.Trigger value="password" disabled>
|
||||
Password
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="notifications">Notifications</Tabs.Trigger>
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="account" className="p-4">
|
||||
<div className="text-sm">Account content</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="password" className="p-4">
|
||||
<div className="text-sm">Password content (disabled)</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="notifications" className="p-4">
|
||||
<div className="text-sm">Notifications content</div>
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcons: Story = {
|
||||
render({ defaultValue }) {
|
||||
const tabsWithIcons = [
|
||||
{ label: "Home", value: "home", icon: Home },
|
||||
{ label: "Profile", value: "profile", icon: User },
|
||||
{ label: "Settings", value: "settings", icon: Settings },
|
||||
{ label: "Notifications", value: "notifications", icon: Bell },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-[500px]">
|
||||
<Tabs defaultValue={defaultValue || "home"}>
|
||||
<Tabs.List>
|
||||
{tabsWithIcons.map((tab) => (
|
||||
<Tabs.Trigger key={tab.value} value={tab.value}>
|
||||
<tab.icon className="w-4 h-4 mr-2" />
|
||||
{tab.label}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{tabsWithIcons.map((tab) => (
|
||||
<Tabs.Content key={tab.value} value={tab.value} className="p-4">
|
||||
<div className="text-sm">Content for {tab.label}</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const IconsOnly: Story = {
|
||||
render({ defaultValue }) {
|
||||
const iconTabs = [
|
||||
{ value: "home", icon: Home },
|
||||
{ value: "profile", icon: User },
|
||||
{ value: "settings", icon: Settings },
|
||||
{ value: "notifications", icon: Bell },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="w-[300px]">
|
||||
<Tabs defaultValue={defaultValue || "home"}>
|
||||
<Tabs.List>
|
||||
{iconTabs.map((tab) => (
|
||||
<Tabs.Trigger key={tab.value} value={tab.value}>
|
||||
<tab.icon className="w-4 h-4" />
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{iconTabs.map((tab) => (
|
||||
<Tabs.Content key={tab.value} value={tab.value} className="p-4">
|
||||
<div className="text-sm">Content for {tab.value}</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const DynamicTabs: Story = {
|
||||
render() {
|
||||
const [tabs, setTabs] = useState([
|
||||
{ label: "Tab 1", value: "tab1" },
|
||||
{ label: "Tab 2", value: "tab2" },
|
||||
]);
|
||||
const [activeTab, setActiveTab] = useState("tab1");
|
||||
|
||||
const addTab = () => {
|
||||
const newTabNum = tabs.length + 1;
|
||||
setTabs([...tabs, { label: `Tab ${newTabNum}`, value: `tab${newTabNum}` }]);
|
||||
};
|
||||
|
||||
const removeTab = (valueToRemove: string) => {
|
||||
const newTabs = tabs.filter((tab) => tab.value !== valueToRemove);
|
||||
setTabs(newTabs);
|
||||
if (activeTab === valueToRemove && newTabs.length > 0) {
|
||||
setActiveTab(newTabs[0].value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-[500px]">
|
||||
<div className="mb-4">
|
||||
<button
|
||||
onClick={addTab}
|
||||
className="px-3 py-1.5 text-sm bg-custom-background-80 rounded hover:bg-custom-background-90"
|
||||
>
|
||||
Add Tab
|
||||
</button>
|
||||
</div>
|
||||
<Tabs value={activeTab} onValueChange={(value) => value && setActiveTab(value)}>
|
||||
<Tabs.List>
|
||||
{tabs.map((tab) => (
|
||||
<Tabs.Trigger key={tab.value} value={tab.value}>
|
||||
{tab.label}
|
||||
{tabs.length > 1 && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeTab(tab.value);
|
||||
}}
|
||||
className="ml-2 hover:text-red-500"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{tabs.map((tab) => (
|
||||
<Tabs.Content key={tab.value} value={tab.value} className="p-4">
|
||||
<div className="text-sm">Content for {tab.label}</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const FullWidth: Story = {
|
||||
render({ defaultValue }) {
|
||||
return (
|
||||
<div className="w-full max-w-2xl">
|
||||
<Tabs defaultValue={defaultValue}>
|
||||
<Tabs.List>
|
||||
<Tabs.Trigger value="account" className="flex-1">
|
||||
Account
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="password" className="flex-1">
|
||||
Password
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger value="notifications" className="flex-1">
|
||||
Notifications
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Content key={option.value} value={option.value} className="p-4">
|
||||
<div className="text-sm">Content for {option.label}</div>
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithComplexContent: Story = {
|
||||
render({ defaultValue }) {
|
||||
return (
|
||||
<div className="w-[600px]">
|
||||
<Tabs defaultValue={defaultValue}>
|
||||
<Tabs.List>
|
||||
{tabOptions.map((option) => (
|
||||
<Tabs.Trigger key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<Tabs.Indicator />
|
||||
</Tabs.List>
|
||||
<Tabs.Content value="account" className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Username</label>
|
||||
<input type="text" className="mt-1 w-full px-3 py-2 bg-custom-background-80 rounded" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Email</label>
|
||||
<input type="email" className="mt-1 w-full px-3 py-2 bg-custom-background-80 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="password" className="p-4">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Current Password</label>
|
||||
<input type="password" className="mt-1 w-full px-3 py-2 bg-custom-background-80 rounded" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">New Password</label>
|
||||
<input type="password" className="mt-1 w-full px-3 py-2 bg-custom-background-80 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content value="notifications" className="p-4">
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Email notifications</span>
|
||||
<input type="checkbox" />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm">Push notifications</span>
|
||||
<input type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.Content>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
381
packages/propel/src/toast/toast.stories.tsx
Normal file
381
packages/propel/src/toast/toast.stories.tsx
Normal file
@@ -0,0 +1,381 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { Toast, setToast, updateToast, setPromiseToast, TOAST_TYPE } from "./toast";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Toast",
|
||||
component: Toast,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
theme: "light",
|
||||
},
|
||||
} satisfies Meta<typeof Toast>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Provider: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Toast theme="light" />
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Toast provider is required to display toasts. It should be added to your app root.
|
||||
</p>
|
||||
<code className="block rounded bg-gray-100 p-2 text-xs">{`<Toast theme="light" />`}</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Success: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success!",
|
||||
message: "Your changes have been saved successfully.",
|
||||
})
|
||||
}
|
||||
className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
|
||||
>
|
||||
Show Success Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Error: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error",
|
||||
message: "Something went wrong. Please try again.",
|
||||
})
|
||||
}
|
||||
className="rounded bg-red-500 px-4 py-2 text-white hover:bg-red-600"
|
||||
>
|
||||
Show Error Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Warning: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.WARNING,
|
||||
title: "Warning",
|
||||
message: "This action cannot be undone.",
|
||||
})
|
||||
}
|
||||
className="rounded bg-yellow-500 px-4 py-2 text-white hover:bg-yellow-600"
|
||||
>
|
||||
Show Warning Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Info: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Information",
|
||||
message: "Here's some helpful information for you.",
|
||||
})
|
||||
}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
>
|
||||
Show Info Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const Loading: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.LOADING,
|
||||
title: "Loading...",
|
||||
})
|
||||
}
|
||||
className="rounded bg-gray-500 px-4 py-2 text-white hover:bg-gray-600"
|
||||
>
|
||||
Show Loading Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithActionItems: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "File uploaded",
|
||||
message: "Your file has been uploaded successfully.",
|
||||
actionItems: (
|
||||
<button className="rounded bg-blue-500 px-3 py-1 text-xs text-white hover:bg-blue-600">
|
||||
View File
|
||||
</button>
|
||||
),
|
||||
})
|
||||
}
|
||||
className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
|
||||
>
|
||||
Show Toast with Action
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const UpdateToast: Story = {
|
||||
render() {
|
||||
const handleUpdate = () => {
|
||||
const id = setToast({
|
||||
type: TOAST_TYPE.LOADING,
|
||||
title: "Processing...",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
updateToast(id, {
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Complete!",
|
||||
message: "The operation has finished successfully.",
|
||||
});
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button onClick={handleUpdate} className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600">
|
||||
Update Toast After 2s
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const PromiseToast: Story = {
|
||||
render() {
|
||||
const handlePromise = () => {
|
||||
const promise = new Promise<{ name?: string; error?: string }>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
Math.random() > 0.5 ? resolve({ name: "Success data" }) : reject({ error: "Failed" });
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
setPromiseToast(promise, {
|
||||
loading: "Processing request...",
|
||||
success: {
|
||||
title: "Request completed!",
|
||||
message: (data) => `Successfully processed: ${data.name}`,
|
||||
},
|
||||
error: {
|
||||
title: "Request failed",
|
||||
message: (error) => `Error: ${error.error}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button onClick={handlePromise} className="rounded bg-purple-500 px-4 py-2 text-white hover:bg-purple-600">
|
||||
Show Promise Toast
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const AllTypes: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Success",
|
||||
message: "Operation successful",
|
||||
})
|
||||
}
|
||||
className="rounded bg-green-500 px-3 py-2 text-sm text-white hover:bg-green-600"
|
||||
>
|
||||
Success
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.ERROR,
|
||||
title: "Error",
|
||||
message: "Operation failed",
|
||||
})
|
||||
}
|
||||
className="rounded bg-red-500 px-3 py-2 text-sm text-white hover:bg-red-600"
|
||||
>
|
||||
Error
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.WARNING,
|
||||
title: "Warning",
|
||||
message: "Please be careful",
|
||||
})
|
||||
}
|
||||
className="rounded bg-yellow-500 px-3 py-2 text-sm text-white hover:bg-yellow-600"
|
||||
>
|
||||
Warning
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Info",
|
||||
message: "Here's some info",
|
||||
})
|
||||
}
|
||||
className="rounded bg-blue-500 px-3 py-2 text-sm text-white hover:bg-blue-600"
|
||||
>
|
||||
Info
|
||||
</button>
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.LOADING,
|
||||
title: "Loading",
|
||||
})
|
||||
}
|
||||
className="rounded bg-gray-500 px-3 py-2 text-sm text-white hover:bg-gray-600"
|
||||
>
|
||||
Loading
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleToasts: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "First toast",
|
||||
message: "This is the first toast",
|
||||
});
|
||||
setTimeout(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Second toast",
|
||||
message: "This is the second toast",
|
||||
});
|
||||
}, 500);
|
||||
setTimeout(() => {
|
||||
setToast({
|
||||
type: TOAST_TYPE.WARNING,
|
||||
title: "Third toast",
|
||||
message: "This is the third toast",
|
||||
});
|
||||
}, 1000);
|
||||
}}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
>
|
||||
Show Multiple Toasts
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const TitleOnly: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.SUCCESS,
|
||||
title: "Saved!",
|
||||
})
|
||||
}
|
||||
className="rounded bg-green-500 px-4 py-2 text-white hover:bg-green-600"
|
||||
>
|
||||
Show Title Only
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LongMessage: Story = {
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<Toast theme="light" />
|
||||
<button
|
||||
onClick={() =>
|
||||
setToast({
|
||||
type: TOAST_TYPE.INFO,
|
||||
title: "Important Information",
|
||||
message:
|
||||
"This is a longer message that provides more detailed information about what happened and what the user should do next.",
|
||||
})
|
||||
}
|
||||
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
|
||||
>
|
||||
Show Long Message
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -19,105 +19,114 @@ import {
|
||||
} from "lucide-react";
|
||||
import { Toolbar } from "./toolbar";
|
||||
|
||||
const meta: Meta<typeof Toolbar> = {
|
||||
const meta = {
|
||||
title: "Components/Toolbar",
|
||||
component: Toolbar,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
args: {
|
||||
children: null,
|
||||
},
|
||||
} satisfies Meta<typeof Toolbar>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Toolbar>;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="w-96 border rounded">
|
||||
render() {
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<div className="w-96 border rounded">
|
||||
<Toolbar>
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Undo} tooltip="Undo" />
|
||||
<Toolbar.Item icon={Redo} tooltip="Redo" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" />
|
||||
<Toolbar.Item icon={Strikethrough} tooltip="Strikethrough" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
<Toolbar.Item icon={Quote} tooltip="Quote" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={AlignLeft} tooltip="Align Left" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" />
|
||||
<Toolbar.Item icon={AlignRight} tooltip="Align Right" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Link} tooltip="Link" />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" />
|
||||
</Toolbar.Group>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const WithActiveStates: Story = {
|
||||
render() {
|
||||
return (
|
||||
<div className="p-4">
|
||||
<Toolbar>
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Undo} tooltip="Undo" />
|
||||
<Toolbar.Item icon={Redo} tooltip="Redo" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" />
|
||||
<Toolbar.Item icon={Strikethrough} tooltip="Strikethrough" />
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} isActive />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" shortcut={["Cmd", "U"]} isActive />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" isActive />
|
||||
<Toolbar.Item icon={Quote} tooltip="Quote" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={AlignLeft} tooltip="Align Left" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" isActive />
|
||||
<Toolbar.Item icon={AlignRight} tooltip="Align Right" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={Link} tooltip="Link" />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" />
|
||||
</Toolbar.Group>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const WithActiveStates: Story = {
|
||||
render: () => (
|
||||
<div className="p-4">
|
||||
<Toolbar>
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} isActive />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Underline} tooltip="Underline" shortcut={["Cmd", "U"]} isActive />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" isActive />
|
||||
<Toolbar.Item icon={Quote} tooltip="Quote" />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={AlignLeft} tooltip="Align Left" />
|
||||
<Toolbar.Item icon={AlignCenter} tooltip="Align Center" isActive />
|
||||
<Toolbar.Item icon={AlignRight} tooltip="Align Right" />
|
||||
</Toolbar.Group>
|
||||
</Toolbar>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CommentToolbar: Story = {
|
||||
render: () => (
|
||||
<div className="p-4 space-y-4">
|
||||
<h3 className="text-sm font-medium">Comment Toolbar with Access Control</h3>
|
||||
<div className="rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar>
|
||||
{/* Access Specifier */}
|
||||
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar.Item icon={Lock} tooltip="Private" isActive />
|
||||
<Toolbar.Item icon={Globe2} tooltip="Public" />
|
||||
</div>
|
||||
|
||||
<div className="flex w-full items-stretch justify-between gap-2 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<div className="flex items-stretch">
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" shortcut={["Cmd", "`"]} />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
</Toolbar.Group>
|
||||
render() {
|
||||
return (
|
||||
<div className="p-4 space-y-4">
|
||||
<h3 className="text-sm font-medium">Comment Toolbar with Access Control</h3>
|
||||
<div className="rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar>
|
||||
{/* Access Specifier */}
|
||||
<div className="flex flex-shrink-0 items-stretch gap-0.5 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<Toolbar.Item icon={Lock} tooltip="Private" isActive />
|
||||
<Toolbar.Item icon={Globe2} tooltip="Public" />
|
||||
</div>
|
||||
<Toolbar.SubmitButton>Comment</Toolbar.SubmitButton>
|
||||
</div>
|
||||
</Toolbar>
|
||||
|
||||
<div className="flex w-full items-stretch justify-between gap-2 rounded border-[0.5px] border-custom-border-200 p-1">
|
||||
<div className="flex items-stretch">
|
||||
<Toolbar.Group isFirst>
|
||||
<Toolbar.Item icon={Bold} tooltip="Bold" shortcut={["Cmd", "B"]} />
|
||||
<Toolbar.Item icon={Italic} tooltip="Italic" shortcut={["Cmd", "I"]} />
|
||||
<Toolbar.Item icon={Code} tooltip="Code" shortcut={["Cmd", "`"]} />
|
||||
</Toolbar.Group>
|
||||
<Toolbar.Group>
|
||||
<Toolbar.Item icon={List} tooltip="Bullet List" />
|
||||
<Toolbar.Item icon={ListOrdered} tooltip="Numbered List" />
|
||||
</Toolbar.Group>
|
||||
</div>
|
||||
<Toolbar.SubmitButton>Comment</Toolbar.SubmitButton>
|
||||
</div>
|
||||
</Toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
303
packages/propel/src/tooltip/tooltip.stories.tsx
Normal file
303
packages/propel/src/tooltip/tooltip.stories.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { Tooltip } from "./root";
|
||||
|
||||
const meta = {
|
||||
title: "Components/Tooltip",
|
||||
component: Tooltip,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
} satisfies Meta<typeof Tooltip>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
tooltipContent: "This is a tooltip",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Hover me</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithHeading: Story = {
|
||||
args: {
|
||||
tooltipHeading: "Tooltip Title",
|
||||
tooltipContent: "This is the tooltip content with a heading.",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Hover me</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const PositionTop: Story = {
|
||||
args: {
|
||||
tooltipContent: "Tooltip on top",
|
||||
position: "top",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Top</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const PositionBottom: Story = {
|
||||
args: {
|
||||
tooltipContent: "Tooltip on bottom",
|
||||
position: "bottom",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Bottom</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const PositionLeft: Story = {
|
||||
args: {
|
||||
tooltipContent: "Tooltip on left",
|
||||
position: "left",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Left</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const PositionRight: Story = {
|
||||
args: {
|
||||
tooltipContent: "Tooltip on right",
|
||||
position: "right",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Right</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
tooltipContent: "Click here for help",
|
||||
children: (
|
||||
<button className="rounded-full p-2 hover:bg-gray-100">
|
||||
<HelpCircle className="h-5 w-5 text-gray-600" />
|
||||
</button>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
tooltipContent: "This tooltip is disabled",
|
||||
disabled: true,
|
||||
children: <button className="rounded bg-gray-400 px-4 py-2 text-white">Hover me (disabled)</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const LongContent: Story = {
|
||||
args: {
|
||||
tooltipHeading: "Important Information",
|
||||
tooltipContent:
|
||||
"This is a longer tooltip with more detailed information that wraps to multiple lines. It provides comprehensive details about the element.",
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Long content</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomDelay: Story = {
|
||||
args: {
|
||||
tooltipContent: "This tooltip has a custom delay",
|
||||
openDelay: 1000,
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Custom delay (1s)</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const CustomOffset: Story = {
|
||||
args: {
|
||||
tooltipContent: "Custom offset tooltip",
|
||||
sideOffset: 20,
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">Custom offset</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const AllPositions: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<Tooltip tooltipContent="Top position" position="top">
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-sm text-white">Top</button>
|
||||
</Tooltip>
|
||||
<div className="flex gap-4">
|
||||
<Tooltip tooltipContent="Left position" position="left">
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-sm text-white">Left</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Right position" position="right">
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-sm text-white">Right</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Tooltip tooltipContent="Bottom position" position="bottom">
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-sm text-white">Bottom</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OnText: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<p className="text-sm text-gray-700">
|
||||
This is some text with a{" "}
|
||||
<Tooltip tooltipContent="Additional information about this word" position="top">
|
||||
<span className="cursor-help border-b border-dashed border-blue-500 text-blue-500">tooltip</span>
|
||||
</Tooltip>{" "}
|
||||
in it.
|
||||
</p>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const OnDisabledButton: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<Tooltip tooltipContent="This feature is currently unavailable" position="top">
|
||||
<button className="cursor-not-allowed rounded bg-gray-300 px-4 py-2 text-gray-500" disabled>
|
||||
Disabled Button
|
||||
</button>
|
||||
</Tooltip>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const ComplexContent: Story = {
|
||||
args: {
|
||||
tooltipHeading: "User Information",
|
||||
tooltipContent: (
|
||||
<div className="space-y-1">
|
||||
<p className="font-semibold">John Doe</p>
|
||||
<p className="text-xs">john@example.com</p>
|
||||
<p className="text-xs text-gray-400">Last seen: 2 hours ago</p>
|
||||
</div>
|
||||
),
|
||||
children: <button className="rounded bg-blue-500 px-4 py-2 text-white">View User</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomStyling: Story = {
|
||||
args: {
|
||||
tooltipContent: "Custom styled tooltip",
|
||||
className: "bg-purple-500 text-white",
|
||||
children: <button className="rounded bg-purple-500 px-4 py-2 text-white">Custom style</button>,
|
||||
},
|
||||
};
|
||||
|
||||
export const MultipleTooltips: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
<Tooltip tooltipContent="Save your work" position="top">
|
||||
<button className="rounded bg-green-500 px-4 py-2 text-sm text-white">Save</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Discard changes" position="top">
|
||||
<button className="rounded bg-red-500 px-4 py-2 text-sm text-white">Cancel</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Export to PDF" position="top">
|
||||
<button className="rounded bg-blue-500 px-4 py-2 text-sm text-white">Export</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Share with team" position="top">
|
||||
<button className="rounded bg-purple-500 px-4 py-2 text-sm text-white">Share</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const IconButtons: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="flex gap-2">
|
||||
<Tooltip tooltipContent="Edit" position="top">
|
||||
<button className="rounded p-2 hover:bg-gray-100">
|
||||
<svg
|
||||
className="h-5 w-5 text-gray-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Delete" position="top">
|
||||
<button className="rounded p-2 hover:bg-gray-100">
|
||||
<svg
|
||||
className="h-5 w-5 text-red-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip tooltipContent="Share" position="top">
|
||||
<button className="rounded p-2 hover:bg-gray-100">
|
||||
<svg
|
||||
className="h-5 w-5 text-blue-600"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const InFormField: Story = {
|
||||
args: {
|
||||
children: <div />,
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div className="w-80">
|
||||
<label className="mb-1 flex items-center gap-2 text-sm font-medium text-gray-700">
|
||||
Email Address
|
||||
<Tooltip
|
||||
tooltipHeading="Email Requirements"
|
||||
tooltipContent="Enter a valid email address that you have access to. We'll send a verification link."
|
||||
position="right"
|
||||
>
|
||||
<HelpCircle className="h-4 w-4 cursor-help text-gray-400" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
className="w-full rounded border border-gray-300 px-3 py-2 text-sm"
|
||||
placeholder="you@example.com"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -9,16 +9,6 @@ const meta: Meta<typeof Breadcrumbs> = {
|
||||
title: "UI/Breadcrumbs",
|
||||
component: Breadcrumbs,
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
isLoading: {
|
||||
control: "boolean",
|
||||
description: "Shows loading state of breadcrumbs",
|
||||
},
|
||||
onBack: {
|
||||
action: "onBack",
|
||||
description: "Callback function when back button is clicked",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
type TBreadcrumbBlockProps = {
|
||||
|
||||
@@ -1,44 +1,38 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import React from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { PopoverMenu } from "./popover-menu";
|
||||
|
||||
const meta: Meta<typeof PopoverMenu> = {
|
||||
title: "PopoverMenu",
|
||||
component: PopoverMenu,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
// types
|
||||
type TPopoverMenu = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Story = StoryObj<typeof PopoverMenu<TPopoverMenu>>;
|
||||
|
||||
// data
|
||||
const data: TPopoverMenu[] = [
|
||||
{ id: 1, name: "John Doe" },
|
||||
{ id: 2, name: "Jane Doe" },
|
||||
{ id: 3, name: "John Smith" },
|
||||
{ id: 4, name: "Jane Smith" },
|
||||
];
|
||||
|
||||
// components
|
||||
const PopoverMenuItemRender = (item: TPopoverMenu) => (
|
||||
<div className="text-sm text-gray-600 hover:text-gray-700 rounded-sm cursor-pointer hover:bg-gray-200 transition-all px-1.5 py-0.5 capitalize">
|
||||
{item.name}
|
||||
</div>
|
||||
);
|
||||
|
||||
// stories
|
||||
export const Default: Story = {
|
||||
const meta: Meta<typeof PopoverMenu<TPopoverMenu>> = {
|
||||
title: "Components/PopoverMenu",
|
||||
component: PopoverMenu,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
args: {
|
||||
popperPosition: "bottom-start",
|
||||
panelClassName: "rounded bg-gray-100 p-2",
|
||||
data: data,
|
||||
data: [
|
||||
{ id: 1, name: "John Doe" },
|
||||
{ id: 2, name: "Jane Doe" },
|
||||
{ id: 3, name: "John Smith" },
|
||||
{ id: 4, name: "Jane Smith" },
|
||||
],
|
||||
keyExtractor: (item, index: number) => `${item.id}-${index}`,
|
||||
render: (item) => PopoverMenuItemRender(item),
|
||||
render: (item: TPopoverMenu) => (
|
||||
<div className="text-sm text-gray-600 hover:text-gray-700 rounded-sm cursor-pointer hover:bg-gray-200 transition-all px-1.5 py-0.5 capitalize">
|
||||
{item.name}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PopoverMenu<TPopoverMenu>>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
@@ -2,26 +2,19 @@ import type { Meta, StoryObj } from "@storybook/react";
|
||||
import React from "react";
|
||||
import { Sortable } from "./sortable";
|
||||
|
||||
const meta: Meta<typeof Sortable> = {
|
||||
type StoryItem = { id: string; name: string };
|
||||
|
||||
const meta: Meta<typeof Sortable<StoryItem>> = {
|
||||
title: "Sortable",
|
||||
component: Sortable,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type StoryItem = { id: string; name: string };
|
||||
type Story = StoryObj<typeof Sortable<StoryItem>>;
|
||||
|
||||
const data = [
|
||||
{ id: "1", name: "John Doe" },
|
||||
{ id: "2", name: "Satish" },
|
||||
{ id: "3", name: "Alice" },
|
||||
{ id: "4", name: "Bob" },
|
||||
{ id: "5", name: "Charlie" },
|
||||
];
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
data,
|
||||
data: [
|
||||
{ id: "1", name: "John Doe" },
|
||||
{ id: "2", name: "Satish" },
|
||||
{ id: "3", name: "Alice" },
|
||||
{ id: "4", name: "Bob" },
|
||||
{ id: "5", name: "Charlie" },
|
||||
],
|
||||
render: (item: StoryItem) => (
|
||||
// <Draggable data={item} className="rounded-lg">
|
||||
<div className="border ">{item.name}</div>
|
||||
@@ -31,3 +24,9 @@ export const Default: Story = {
|
||||
keyExtractor: (item) => item.id,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
373
pnpm-lock.yaml
generated
373
pnpm-lock.yaml
generated
@@ -296,7 +296,7 @@ importers:
|
||||
version: 3.0.5
|
||||
'@types/node':
|
||||
specifier: ^20.14.9
|
||||
version: 20.19.11
|
||||
version: 20.19.17
|
||||
'@types/pino-http':
|
||||
specifier: ^5.8.4
|
||||
version: 5.8.4
|
||||
@@ -686,7 +686,7 @@ importers:
|
||||
version: 4.17.23
|
||||
'@types/node':
|
||||
specifier: ^20.14.9
|
||||
version: 20.19.11
|
||||
version: 20.19.17
|
||||
'@types/ws':
|
||||
specifier: ^8.5.10
|
||||
version: 8.18.1
|
||||
@@ -704,7 +704,7 @@ importers:
|
||||
dependencies:
|
||||
'@floating-ui/dom':
|
||||
specifier: ^1.7.1
|
||||
version: 1.7.2
|
||||
version: 1.7.3
|
||||
'@floating-ui/react':
|
||||
specifier: ^0.26.4
|
||||
version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -773,7 +773,7 @@ importers:
|
||||
version: 3.6.2
|
||||
'@tiptap/react':
|
||||
specifier: ^3.5.3
|
||||
version: 3.6.2(@floating-ui/dom@1.7.2)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 3.6.2(@floating-ui/dom@1.7.3)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tiptap/starter-kit':
|
||||
specifier: ^3.5.3
|
||||
version: 3.6.2
|
||||
@@ -824,7 +824,7 @@ importers:
|
||||
version: 9.0.12(yjs@13.6.27)
|
||||
y-prosemirror:
|
||||
specifier: ^1.2.15
|
||||
version: 1.3.6(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)
|
||||
version: 1.3.7(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)
|
||||
y-protocols:
|
||||
specifier: ^1.0.6
|
||||
version: 1.0.6(yjs@13.6.27)
|
||||
@@ -984,7 +984,7 @@ importers:
|
||||
version: 4.17.23
|
||||
'@types/node':
|
||||
specifier: ^20.14.9
|
||||
version: 20.19.11
|
||||
version: 20.19.17
|
||||
tsdown:
|
||||
specifier: 'catalog:'
|
||||
version: 0.14.2(typescript@5.8.3)
|
||||
@@ -1054,13 +1054,13 @@ importers:
|
||||
version: link:../typescript-config
|
||||
'@storybook/addon-designs':
|
||||
specifier: 10.0.2
|
||||
version: 10.0.2(@storybook/addon-docs@9.1.2(@types/react@18.3.11)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
version: 10.0.2(@storybook/addon-docs@9.1.10(@types/react@18.3.11)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/addon-docs':
|
||||
specifier: 9.1.2
|
||||
version: 9.1.2(@types/react@18.3.11)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
specifier: 9.1.10
|
||||
version: 9.1.10(@types/react@18.3.11)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/react-vite':
|
||||
specifier: 9.1.2
|
||||
version: 9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.0)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
specifier: 9.1.10
|
||||
version: 9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
'@types/react':
|
||||
specifier: 'catalog:'
|
||||
version: 18.3.11
|
||||
@@ -1068,11 +1068,11 @@ importers:
|
||||
specifier: 'catalog:'
|
||||
version: 18.3.1
|
||||
eslint-plugin-storybook:
|
||||
specifier: 9.1.2
|
||||
version: 9.1.2(eslint@8.57.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)
|
||||
specifier: 9.1.10
|
||||
version: 9.1.10(eslint@8.57.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)
|
||||
storybook:
|
||||
specifier: 9.1.2
|
||||
version: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
specifier: 9.1.10
|
||||
version: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
tsdown:
|
||||
specifier: 'catalog:'
|
||||
version: 0.14.2(typescript@5.8.3)
|
||||
@@ -1324,7 +1324,7 @@ importers:
|
||||
version: 4.17.12
|
||||
'@types/node':
|
||||
specifier: ^20.5.2
|
||||
version: 20.19.11
|
||||
version: 20.19.17
|
||||
'@types/react':
|
||||
specifier: 'catalog:'
|
||||
version: 18.3.11
|
||||
@@ -1633,9 +1633,6 @@ packages:
|
||||
'@emnapi/core@1.5.0':
|
||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
||||
|
||||
'@emnapi/runtime@1.4.5':
|
||||
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
||||
|
||||
@@ -1875,9 +1872,6 @@ packages:
|
||||
'@floating-ui/core@1.7.3':
|
||||
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
|
||||
|
||||
'@floating-ui/dom@1.7.3':
|
||||
resolution: {integrity: sha512-uZA413QEpNuhtb3/iIKoYMSK07keHPYeXF02Zhd6e213j+d1NamLix/mCLxBUDW/Gx52sPH2m+chlUsyaBs/Ag==}
|
||||
|
||||
@@ -2218,8 +2212,8 @@ packages:
|
||||
'@napi-rs/wasm-runtime@0.2.12':
|
||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.6':
|
||||
resolution: {integrity: sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g==}
|
||||
'@napi-rs/wasm-runtime@1.0.5':
|
||||
resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==}
|
||||
|
||||
'@next/env@14.2.32':
|
||||
resolution: {integrity: sha512-n9mQdigI6iZ/DF6pCTwMKeWgF2e8lg7qgt5M7HXMLtyhZYMnf/u905M18sSpPmHL9MKp9JHo56C6jrD2EvWxng==}
|
||||
@@ -2309,8 +2303,8 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@oxc-project/types@0.93.0':
|
||||
resolution: {integrity: sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg==}
|
||||
'@oxc-project/types@0.92.0':
|
||||
resolution: {integrity: sha512-PDLfCbwgXjGdTBxzcuDOUxJYNBl6P8dOp3eDKWw54dYvqONan9rwGDRQU0zrkdEMiItfXQQUOI17uOcMX5Zm7A==}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
@@ -2577,91 +2571,91 @@ packages:
|
||||
'@remirror/core-constants@3.0.0':
|
||||
resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ==}
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-9Ii9phC7QU6Lb+ncMfG1Xlosq0NBB1N/4sw+EGZ3y0BBWGy02TOb5ghWZalphAKv9rn1goqo5WkBjyd2YvsLmA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw==}
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-5O6d0y2tBQTL+ecQY3qXIwSnF1/Zik8q7LZMKeyF+VJ9l194d0IdMhl2zUF0cqWbYHuF4Pnxplk4OhurPQ/Z9Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ==}
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-izB9jygt3miPQbOTZfSu5K51isUplqa8ysByOKQqcJHgrBWmbTU8TM9eouv6tRmBR0kjcEcID9xhmA1CeZ1VIg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g==}
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-2fdpEpKT+wwP0vig9dqxu+toTeWmVSjo3psJQVDeLJ51rO+GXcCJ1IkCXjhMKVEevNtZS7B8T8Z2vvmRV9MAdA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA==}
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-HP2lo78OWULN+8TewpLbS9PS00jh0CaF04tA2u8z2I+6QgVgrYOYKvX+T0hlO5smgso4+qb3YchzumWJl3yCPQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg==}
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-ng00gfr9BhA2NPAOU5RWAlTiL+JcwAD+L+4yUD1sbBy6tgHdLiNBOvKtHISIF9RM9/eQeS0tAiWOYZGIH9JMew==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg==}
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-mF0R1l9kLcaag/9cLEiYYdNZ4v1uuX4jklSDZ1s6vJE4RB3LirUney0FavdVRwCJ5sDvfvsPgXgtBXWYr2M2tQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g==}
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-+wi08S7wT5iLPHRZb0USrS6n+T6m+yY++dePYedE5uvKIpWCJJioFTaRtWjpm0V6dVNLcq2OukrvfdlGtH9Wgg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q==}
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-W5qBGAemUocIBKCcOsDjlV9GUt28qhl/+M6etWBeLS5gQK0J6XDg0YVzfOQdvq57ZGjYNP0NvhYzqhOOnEx+4g==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg==}
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-vJwoDehtt+yqj2zacq1AqNc2uE/oh7mnRGqAUbuldV6pgvU01OSQUJ7Zu+35hTopnjFoDNN6mIezkYlGAv5RFA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ==}
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-Oj3YyqVUPurr1FlMpEE/bJmMC+VWAWPM/SGUfklO5KUX97bk5Q/733nPg4RykK8q8/TluJoQYvRc05vL/B74dw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg==}
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-0ZtO6yN8XjVoFfN4HDWQj4nDu3ndMybr7jIM00DJqOmc+yFhly7rdOy7fNR9Sky3leCpBtsXfepVqRmVpYKPVA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw==}
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-BPl1inoJXPpIe38Ja46E4y11vXlJyuleo+9Rmu//pYL5fIDYJkXUj/oAXqjSuwLcssrcwnuPgzvzvlz9++cr3w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg==}
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-UguA4ltbAk+nbwHRxqaUP/etpTbR0HjyNlsu4Zjbh/ytNbFsbw8CA4tEBkwDyjgI5NIPea6xY11zpl7R2/ddVA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.41':
|
||||
resolution: {integrity: sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw==}
|
||||
'@rolldown/pluginutils@1.0.0-beta.40':
|
||||
resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==}
|
||||
|
||||
'@rollup/pluginutils@5.2.0':
|
||||
resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==}
|
||||
@@ -2829,10 +2823,10 @@ packages:
|
||||
peerDependencies:
|
||||
storybook: ^8.6.14
|
||||
|
||||
'@storybook/addon-docs@9.1.2':
|
||||
resolution: {integrity: sha512-U3eHJ8lQFfEZ/OcgdKkUBbW2Y2tpAsHfy8lQOBgs5Pgj9biHEJcUmq+drOS/sJhle673eoBcUFmspXulI4KP1w==}
|
||||
'@storybook/addon-docs@9.1.10':
|
||||
resolution: {integrity: sha512-LYK3oXy/0PgY39FhkYVd9D0bzatLGTsMhaPPwSjBOmZgK0f0yBLqaePy7urUFeHYm/rjwAaRmDJNBqUnGTVoig==}
|
||||
peerDependencies:
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
|
||||
'@storybook/addon-essentials@8.6.14':
|
||||
resolution: {integrity: sha512-5ZZSHNaW9mXMOFkoPyc3QkoNGdJHETZydI62/OASR0lmPlJ1065TNigEo5dJddmZNn0/3bkE8eKMAzLnO5eIdA==}
|
||||
@@ -2904,10 +2898,10 @@ packages:
|
||||
react-dom:
|
||||
optional: true
|
||||
|
||||
'@storybook/builder-vite@9.1.2':
|
||||
resolution: {integrity: sha512-5Y7e5wnSzFxCGP63UNRRZVoxHe1znU4dYXazJBobAlEcUPBk7A0sH2716tA6bS4oz92oG9tgvn1g996hRrw4ow==}
|
||||
'@storybook/builder-vite@9.1.10':
|
||||
resolution: {integrity: sha512-0ogI+toZJYaFptcFpRcRPOZ9/NrFUYhiaI09ggeEB1FF9ygHMVsobp4eaj4HjZI6V3x7cQwkd2ZmxAMQDBQuMA==}
|
||||
peerDependencies:
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
vite: 7.0.7
|
||||
|
||||
'@storybook/builder-webpack5@8.6.14':
|
||||
@@ -2942,10 +2936,10 @@ packages:
|
||||
peerDependencies:
|
||||
storybook: ^8.6.14
|
||||
|
||||
'@storybook/csf-plugin@9.1.2':
|
||||
resolution: {integrity: sha512-bfMh6r+RieBLPWtqqYN70le2uTE4JzOYPMYSCagHykUti3uM/1vRFaZNkZtUsRy5GwEzE5jLdDXioG1lOEeT2Q==}
|
||||
'@storybook/csf-plugin@9.1.10':
|
||||
resolution: {integrity: sha512-247F/JU0Naxm/RIUnQYpqXeCL0wG8UNJkZe+/GkLjdqzsyML0lb+8OwBsWFfG8zfj6fkjmRU2mF44TnNkzoQcg==}
|
||||
peerDependencies:
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
|
||||
'@storybook/global@5.0.0':
|
||||
resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
|
||||
@@ -3002,20 +2996,20 @@ packages:
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
storybook: ^8.6.14
|
||||
|
||||
'@storybook/react-dom-shim@9.1.2':
|
||||
resolution: {integrity: sha512-nw7BLAHCJswPZGsuL0Gs2AvFUWriusCTgPBmcHppSw/AqvT4XRFRDE+5q3j04/XKuZBrAA2sC4L+HuC0uzEChQ==}
|
||||
'@storybook/react-dom-shim@9.1.10':
|
||||
resolution: {integrity: sha512-cxy8GTj73RMJIFPrgqdnMXePGX5iFohM5pDCZ63Te5m5GtzKqsILRXtBBLO6Ouexm/ZYRVznkKiwNKX/Fu24fQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
|
||||
'@storybook/react-vite@9.1.2':
|
||||
resolution: {integrity: sha512-dv3CBjOzmMoSyIotMtdmsBRjB25i19OjFP0IZqauLeUoVm6QddILW7JRcZVLrzhATyBEn+sEAdWQ4j79Z11HAg==}
|
||||
'@storybook/react-vite@9.1.10':
|
||||
resolution: {integrity: sha512-k0wWlfoWakoHL3NZ1+38oxRH3WYyprFFX+WUb/4W8axrvpKogvdnxKCul/YB1HH5FcTagIfguamsPjKwB1ZkJg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
vite: 7.0.7
|
||||
|
||||
'@storybook/react-webpack5@8.6.14':
|
||||
@@ -3045,13 +3039,13 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@storybook/react@9.1.2':
|
||||
resolution: {integrity: sha512-VVXu1HrhDExj/yj+heFYc8cgIzBruXy1UYT3LW0WiJyadgzYz3J41l/Lf/j2FCppyxwlXb19Uv51plb1F1C77w==}
|
||||
'@storybook/react@9.1.10':
|
||||
resolution: {integrity: sha512-flG3Gn3EHZnxn92C7vrA2U4aGqpOKdf85fL43+J/2k9HF5AIyOFGlcv4LGVyKZ3LOAow/nGBVSXL9961h+ICRA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
typescript: 5.8.3
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
@@ -3589,9 +3583,6 @@ packages:
|
||||
'@types/node@18.16.1':
|
||||
resolution: {integrity: sha512-DZxSZWXxFfOlx7k7Rv4LAyiMroaxa3Ly/7OOzZO8cBNho0YzAi4qlbrx8W27JGqG57IgR/6J7r+nOJWw6kcvZA==}
|
||||
|
||||
'@types/node@20.19.11':
|
||||
resolution: {integrity: sha512-uug3FEEGv0r+jrecvUUpbY8lLisvIjg6AAic6a2bSP5OEOLeJsDSnvhCDov7ipFFMXS3orMpzlmi0ZcuGkBbow==}
|
||||
|
||||
'@types/node@20.19.17':
|
||||
resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==}
|
||||
|
||||
@@ -4058,10 +4049,6 @@ packages:
|
||||
resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
ansis@4.2.0:
|
||||
resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
@@ -5063,12 +5050,12 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
|
||||
|
||||
eslint-plugin-storybook@9.1.2:
|
||||
resolution: {integrity: sha512-EQa/kChrYrekxv36q3pvW57anqxMlAP4EdPXEDyA/EDrCQJaaTbWEdsMnVZtD744RjPP0M5wzaUjHbMhNooAwQ==}
|
||||
eslint-plugin-storybook@9.1.10:
|
||||
resolution: {integrity: sha512-HAVQ9HTMydcFj5KjnzsETOwPe19eIViwRBhc47lvU04YEFTgEg2rlXN1xozxHUlQ+XkkoKYkIUYoqo7KgGhkIA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
eslint: '>=8'
|
||||
storybook: ^9.1.2
|
||||
storybook: ^9.1.10
|
||||
|
||||
eslint-plugin-turbo@1.13.4:
|
||||
resolution: {integrity: sha512-82GfMzrewI/DJB92Bbch239GWbGx4j1zvjk1lqb06lxIlMPnVwUHVwPbAnLfyLG3JuhLv9whxGkO/q1CL18JTg==}
|
||||
@@ -7207,8 +7194,8 @@ packages:
|
||||
vue-tsc:
|
||||
optional: true
|
||||
|
||||
rolldown@1.0.0-beta.41:
|
||||
resolution: {integrity: sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg==}
|
||||
rolldown@1.0.0-beta.40:
|
||||
resolution: {integrity: sha512-VqEHbKpOgTPmQrZ4fVn4eshDQS/6g/fRpNE7cFSJY+eQLDZn4B9X61J6L+hnlt1u2uRI+pF7r1USs6S5fuWCvw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
@@ -7425,8 +7412,8 @@ packages:
|
||||
prettier:
|
||||
optional: true
|
||||
|
||||
storybook@9.1.2:
|
||||
resolution: {integrity: sha512-TYcq7WmgfVCAQge/KueGkVlM/+g33sQcmbATlC3X6y/g2FEeSSLGrb6E6d3iemht8oio+aY6ld3YOdAnMwx45Q==}
|
||||
storybook@9.1.10:
|
||||
resolution: {integrity: sha512-4+U7gF9hMpGilQmdVJwQaVZZEkD7XwC4ZDmBa51mobaPYelELEMoMfNM2hLyvB2x12gk1IJui1DnwOE4t+MXhw==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
prettier: ^2 || ^3
|
||||
@@ -7638,10 +7625,6 @@ packages:
|
||||
tinyexec@1.0.1:
|
||||
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
|
||||
|
||||
tinyglobby@0.2.14:
|
||||
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
@@ -8205,16 +8188,6 @@ packages:
|
||||
peerDependencies:
|
||||
yjs: ^13.0.0
|
||||
|
||||
y-prosemirror@1.3.6:
|
||||
resolution: {integrity: sha512-vtS2rv8+ll/TBQRqwUiqflgSuN/DhfvUQX0r5O3o5i0pO6K4pSNgFtVkOKtNWPBVkS6l9BDQjbtnDNftZnxq7Q==}
|
||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||
peerDependencies:
|
||||
prosemirror-model: ^1.7.1
|
||||
prosemirror-state: ^1.2.3
|
||||
prosemirror-view: 1.40.0
|
||||
y-protocols: ^1.0.1
|
||||
yjs: ^13.5.38
|
||||
|
||||
y-prosemirror@1.3.7:
|
||||
resolution: {integrity: sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==}
|
||||
engines: {node: '>=16.0.0', npm: '>=8.0.0'}
|
||||
@@ -8568,11 +8541,6 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.4.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.5.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -8778,11 +8746,6 @@ snapshots:
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
@@ -8993,7 +8956,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.4.5
|
||||
'@emnapi/runtime': 1.5.0
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.33.5':
|
||||
@@ -9150,7 +9113,7 @@ snapshots:
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@napi-rs/wasm-runtime@1.0.6':
|
||||
'@napi-rs/wasm-runtime@1.0.5':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.5.0
|
||||
'@emnapi/runtime': 1.5.0
|
||||
@@ -9212,7 +9175,7 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@oxc-project/types@0.93.0': {}
|
||||
'@oxc-project/types@0.92.0': {}
|
||||
|
||||
'@pkgjs/parseargs@0.11.0':
|
||||
optional: true
|
||||
@@ -9539,51 +9502,51 @@ snapshots:
|
||||
|
||||
'@remirror/core-constants@3.0.0': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.41':
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.41':
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.41':
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.41':
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41':
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41':
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.41':
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.41':
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.41':
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.41':
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.41':
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.40':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.0.6
|
||||
'@napi-rs/wasm-runtime': 1.0.5
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41':
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41':
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.41':
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.40':
|
||||
optional: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.41': {}
|
||||
'@rolldown/pluginutils@1.0.0-beta.40': {}
|
||||
|
||||
'@rollup/pluginutils@5.2.0(rollup@4.52.0)':
|
||||
dependencies:
|
||||
@@ -9690,12 +9653,12 @@ snapshots:
|
||||
storybook: 8.6.14(prettier@3.6.2)
|
||||
ts-dedent: 2.2.0
|
||||
|
||||
'@storybook/addon-designs@10.0.2(@storybook/addon-docs@9.1.2(@types/react@18.3.11)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
'@storybook/addon-designs@10.0.2(@storybook/addon-docs@9.1.10(@types/react@18.3.11)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
dependencies:
|
||||
'@figspec/react': 1.0.4(react@18.3.1)
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
optionalDependencies:
|
||||
'@storybook/addon-docs': 9.1.2(@types/react@18.3.11)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/addon-docs': 9.1.10(@types/react@18.3.11)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
@@ -9712,15 +9675,15 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@storybook/addon-docs@9.1.2(@types/react@18.3.11)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
'@storybook/addon-docs@9.1.10(@types/react@18.3.11)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
dependencies:
|
||||
'@mdx-js/react': 3.1.0(@types/react@18.3.11)(react@18.3.1)
|
||||
'@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/csf-plugin': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/icons': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@storybook/react-dom-shim': 9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/react-dom-shim': 9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
ts-dedent: 2.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
@@ -9812,10 +9775,10 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@storybook/builder-vite@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
|
||||
'@storybook/builder-vite@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@storybook/csf-plugin': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
'@storybook/csf-plugin': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
ts-dedent: 2.2.0
|
||||
vite: 7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)
|
||||
|
||||
@@ -9890,9 +9853,9 @@ snapshots:
|
||||
storybook: 8.6.14(prettier@3.6.2)
|
||||
unplugin: 1.16.1
|
||||
|
||||
'@storybook/csf-plugin@9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
'@storybook/csf-plugin@9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
dependencies:
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
unplugin: 1.16.1
|
||||
|
||||
'@storybook/global@5.0.0': {}
|
||||
@@ -9966,25 +9929,25 @@ snapshots:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
storybook: 8.6.14(prettier@3.6.2)
|
||||
|
||||
'@storybook/react-dom-shim@9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
'@storybook/react-dom-shim@9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))':
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
|
||||
'@storybook/react-vite@9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.0)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
|
||||
'@storybook/react-vite@9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.0)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))':
|
||||
dependencies:
|
||||
'@joshwooding/vite-plugin-react-docgen-typescript': 0.6.1(typescript@5.8.3)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
'@rollup/pluginutils': 5.2.0(rollup@4.52.0)
|
||||
'@storybook/builder-vite': 9.1.2(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
'@storybook/react': 9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)
|
||||
'@storybook/builder-vite': 9.1.10(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
'@storybook/react': 9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)
|
||||
find-up: 7.0.0
|
||||
magic-string: 0.30.17
|
||||
react: 18.3.1
|
||||
react-docgen: 8.0.0
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
resolve: 1.22.10
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
tsconfig-paths: 4.2.0
|
||||
vite: 7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)
|
||||
transitivePeerDependencies:
|
||||
@@ -10026,13 +9989,13 @@ snapshots:
|
||||
'@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.2))
|
||||
typescript: 5.8.3
|
||||
|
||||
'@storybook/react@9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)':
|
||||
'@storybook/react@9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@storybook/react-dom-shim': 9.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
'@storybook/react-dom-shim': 9.1.10(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
@@ -10238,9 +10201,9 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- emojibase
|
||||
|
||||
'@tiptap/extension-floating-menu@3.6.2(@floating-ui/dom@1.7.2)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)':
|
||||
'@tiptap/extension-floating-menu@3.6.2(@floating-ui/dom@1.7.3)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.2
|
||||
'@floating-ui/dom': 1.7.3
|
||||
'@tiptap/core': 3.6.2(@tiptap/pm@3.6.2)
|
||||
'@tiptap/pm': 3.6.2
|
||||
optional: true
|
||||
@@ -10363,7 +10326,7 @@ snapshots:
|
||||
prosemirror-transform: 1.10.4
|
||||
prosemirror-view: 1.40.0
|
||||
|
||||
'@tiptap/react@3.6.2(@floating-ui/dom@1.7.2)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@tiptap/react@3.6.2(@floating-ui/dom@1.7.3)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)(@types/react-dom@18.3.1)(@types/react@18.3.11)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@tiptap/core': 3.6.2(@tiptap/pm@3.6.2)
|
||||
'@tiptap/pm': 3.6.2
|
||||
@@ -10376,7 +10339,7 @@ snapshots:
|
||||
use-sync-external-store: 1.5.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@tiptap/extension-bubble-menu': 3.6.2(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)
|
||||
'@tiptap/extension-floating-menu': 3.6.2(@floating-ui/dom@1.7.2)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)
|
||||
'@tiptap/extension-floating-menu': 3.6.2(@floating-ui/dom@1.7.3)(@tiptap/core@3.6.2(@tiptap/pm@3.6.2))(@tiptap/pm@3.6.2)
|
||||
transitivePeerDependencies:
|
||||
- '@floating-ui/dom'
|
||||
|
||||
@@ -10464,7 +10427,7 @@ snapshots:
|
||||
'@types/body-parser@1.19.6':
|
||||
dependencies:
|
||||
'@types/connect': 3.4.38
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@types/chai@5.2.2':
|
||||
dependencies:
|
||||
@@ -10473,15 +10436,15 @@ snapshots:
|
||||
'@types/compression@1.8.1':
|
||||
dependencies:
|
||||
'@types/express': 4.17.23
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@types/connect@3.4.38':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@types/cors@2.8.19':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
@@ -10531,14 +10494,14 @@ snapshots:
|
||||
|
||||
'@types/express-serve-static-core@4.19.6':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.5
|
||||
|
||||
'@types/express-serve-static-core@5.0.7':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
'@types/qs': 6.14.0
|
||||
'@types/range-parser': 1.2.7
|
||||
'@types/send': 0.17.5
|
||||
@@ -10612,10 +10575,6 @@ snapshots:
|
||||
|
||||
'@types/node@18.16.1': {}
|
||||
|
||||
'@types/node@20.19.11':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
'@types/node@20.19.17':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@@ -10642,7 +10601,7 @@ snapshots:
|
||||
|
||||
'@types/pino@6.3.12':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
'@types/pino-pretty': 5.0.0
|
||||
'@types/pino-std-serializers': 4.0.0
|
||||
sonic-boom: 2.8.0
|
||||
@@ -10682,12 +10641,12 @@ snapshots:
|
||||
'@types/send@0.17.5':
|
||||
dependencies:
|
||||
'@types/mime': 1.3.5
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@types/serve-static@1.15.8':
|
||||
dependencies:
|
||||
'@types/http-errors': 2.0.5
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
'@types/send': 0.17.5
|
||||
|
||||
'@types/triple-beam@1.3.5': {}
|
||||
@@ -10708,7 +10667,7 @@ snapshots:
|
||||
|
||||
'@types/ws@8.18.1':
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)':
|
||||
dependencies:
|
||||
@@ -11122,8 +11081,6 @@ snapshots:
|
||||
|
||||
ansis@4.1.0: {}
|
||||
|
||||
ansis@4.2.0: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
anymatch@3.1.3:
|
||||
@@ -12177,7 +12134,7 @@ snapshots:
|
||||
get-tsconfig: 4.10.1
|
||||
is-bun-module: 2.0.0
|
||||
stable-hash: 0.0.5
|
||||
tinyglobby: 0.2.14
|
||||
tinyglobby: 0.2.15
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.40.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1)
|
||||
@@ -12273,11 +12230,11 @@ snapshots:
|
||||
string.prototype.matchall: 4.0.12
|
||||
string.prototype.repeat: 1.0.0
|
||||
|
||||
eslint-plugin-storybook@9.1.2(eslint@8.57.1)(storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3):
|
||||
eslint-plugin-storybook@9.1.10(eslint@8.57.1)(storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)))(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.38.0(eslint@8.57.1)(typescript@5.8.3)
|
||||
eslint: 8.57.1
|
||||
storybook: 9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
storybook: 9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
@@ -13123,7 +13080,7 @@ snapshots:
|
||||
|
||||
jest-worker@27.5.1:
|
||||
dependencies:
|
||||
'@types/node': 20.19.11
|
||||
'@types/node': 20.19.17
|
||||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
@@ -13991,7 +13948,7 @@ snapshots:
|
||||
pretty-hrtime: 1.0.3
|
||||
read-cache: 1.0.0
|
||||
slash: 5.1.0
|
||||
tinyglobby: 0.2.14
|
||||
tinyglobby: 0.2.15
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
@@ -14623,7 +14580,7 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rolldown-plugin-dts@0.15.10(rolldown@1.0.0-beta.41)(typescript@5.8.3):
|
||||
rolldown-plugin-dts@0.15.10(rolldown@1.0.0-beta.40)(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@babel/generator': 7.28.3
|
||||
'@babel/parser': 7.28.3
|
||||
@@ -14633,33 +14590,33 @@ snapshots:
|
||||
debug: 4.4.1
|
||||
dts-resolver: 2.1.2
|
||||
get-tsconfig: 4.10.1
|
||||
rolldown: 1.0.0-beta.41
|
||||
rolldown: 1.0.0-beta.40
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
transitivePeerDependencies:
|
||||
- oxc-resolver
|
||||
- supports-color
|
||||
|
||||
rolldown@1.0.0-beta.41:
|
||||
rolldown@1.0.0-beta.40:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.93.0
|
||||
'@rolldown/pluginutils': 1.0.0-beta.41
|
||||
ansis: 4.2.0
|
||||
'@oxc-project/types': 0.92.0
|
||||
'@rolldown/pluginutils': 1.0.0-beta.40
|
||||
ansis: 4.1.0
|
||||
optionalDependencies:
|
||||
'@rolldown/binding-android-arm64': 1.0.0-beta.41
|
||||
'@rolldown/binding-darwin-arm64': 1.0.0-beta.41
|
||||
'@rolldown/binding-darwin-x64': 1.0.0-beta.41
|
||||
'@rolldown/binding-freebsd-x64': 1.0.0-beta.41
|
||||
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.41
|
||||
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.41
|
||||
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.41
|
||||
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.41
|
||||
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.41
|
||||
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.41
|
||||
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.41
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.41
|
||||
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.41
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.41
|
||||
'@rolldown/binding-android-arm64': 1.0.0-beta.40
|
||||
'@rolldown/binding-darwin-arm64': 1.0.0-beta.40
|
||||
'@rolldown/binding-darwin-x64': 1.0.0-beta.40
|
||||
'@rolldown/binding-freebsd-x64': 1.0.0-beta.40
|
||||
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.40
|
||||
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.40
|
||||
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.40
|
||||
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.40
|
||||
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.40
|
||||
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.40
|
||||
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.40
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.40
|
||||
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.40
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.40
|
||||
|
||||
rollup@4.52.0:
|
||||
dependencies:
|
||||
@@ -14955,7 +14912,7 @@ snapshots:
|
||||
- supports-color
|
||||
- utf-8-validate
|
||||
|
||||
storybook@9.1.2(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)):
|
||||
storybook@9.1.10(@testing-library/dom@10.4.0)(prettier@3.6.2)(vite@7.0.0(@types/node@22.17.2)(jiti@2.5.1)(terser@5.43.1)(yaml@2.8.1)):
|
||||
dependencies:
|
||||
'@storybook/global': 5.0.0
|
||||
'@testing-library/jest-dom': 6.6.3
|
||||
@@ -15208,11 +15165,6 @@ snapshots:
|
||||
|
||||
tinyexec@1.0.1: {}
|
||||
|
||||
tinyglobby@0.2.14:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
@@ -15321,11 +15273,11 @@ snapshots:
|
||||
diff: 8.0.2
|
||||
empathic: 2.0.0
|
||||
hookable: 5.5.3
|
||||
rolldown: 1.0.0-beta.41
|
||||
rolldown-plugin-dts: 0.15.10(rolldown@1.0.0-beta.41)(typescript@5.8.3)
|
||||
rolldown: 1.0.0-beta.40
|
||||
rolldown-plugin-dts: 0.15.10(rolldown@1.0.0-beta.40)(typescript@5.8.3)
|
||||
semver: 7.7.2
|
||||
tinyexec: 1.0.1
|
||||
tinyglobby: 0.2.14
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
unconfig: 7.3.3
|
||||
optionalDependencies:
|
||||
@@ -15833,15 +15785,6 @@ snapshots:
|
||||
lib0: 0.2.114
|
||||
yjs: 13.6.27
|
||||
|
||||
y-prosemirror@1.3.6(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27):
|
||||
dependencies:
|
||||
lib0: 0.2.114
|
||||
prosemirror-model: 1.25.3
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-view: 1.40.0
|
||||
y-protocols: 1.0.6(yjs@13.6.27)
|
||||
yjs: 13.6.27
|
||||
|
||||
y-prosemirror@1.3.7(prosemirror-model@1.25.3)(prosemirror-state@1.4.3)(prosemirror-view@1.40.0)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27):
|
||||
dependencies:
|
||||
lib0: 0.2.114
|
||||
|
||||
Reference in New Issue
Block a user