diff --git a/web/.storybook/main.ts b/web/.storybook/main.ts index e1022053b..dcba9b6c0 100644 --- a/web/.storybook/main.ts +++ b/web/.storybook/main.ts @@ -3,6 +3,7 @@ import path from 'path'; const config: StorybookConfig = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + staticDirs: ['../public'], addons: [ '@storybook/addon-webpack5-compiler-swc', '@storybook/addon-docs', diff --git a/web/.storybook/preview.ts b/web/.storybook/preview.ts index 07fc69ec7..58c7d34be 100644 --- a/web/.storybook/preview.ts +++ b/web/.storybook/preview.ts @@ -1,6 +1,7 @@ import '@/locales/config'; import type { Preview } from '@storybook/react-webpack5'; import { createElement } from 'react'; +import '../public/iconfont.js'; import { TooltipProvider } from '../src/components/ui/tooltip'; import '../tailwind.css'; diff --git a/web/public/iconfont.js b/web/public/iconfont.js index f514c4cfd..4d923a373 100644 --- a/web/public/iconfont.js +++ b/web/public/iconfont.js @@ -59,6 +59,10 @@ ` ` + + ` + + +` + ''), ((h) => { var a = (l = (l = document.getElementsByTagName('script'))[ diff --git a/web/src/components/collapse.tsx b/web/src/components/collapse.tsx index bbe62412c..46b171229 100644 --- a/web/src/components/collapse.tsx +++ b/web/src/components/collapse.tsx @@ -3,9 +3,16 @@ import { CollapsibleContent, CollapsibleTrigger, } from '@/components/ui/collapsible'; +import { cn } from '@/lib/utils'; import { CollapsibleProps } from '@radix-ui/react-collapsible'; -import { ListCollapse } from 'lucide-react'; -import { PropsWithChildren, ReactNode } from 'react'; +import { + PropsWithChildren, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react'; +import { IconFontFill } from './icon-font'; type CollapseProps = Omit & { title?: ReactNode; @@ -16,22 +23,42 @@ export function Collapse({ title, children, rightContent, - open, + open = true, defaultOpen = false, onOpenChange, disabled, }: CollapseProps) { + const [currentOpen, setCurrentOpen] = useState(open); + + useEffect(() => { + setCurrentOpen(open); + }, [open]); + + const handleOpenChange = useCallback( + (open: boolean) => { + setCurrentOpen(open); + onOpenChange?.(open); + }, + [onOpenChange], + ); + return (
- {title} + + {title}
{rightContent}
diff --git a/web/src/stories/collapse.stories.tsx b/web/src/stories/collapse.stories.tsx new file mode 100644 index 000000000..d9761e53b --- /dev/null +++ b/web/src/stories/collapse.stories.tsx @@ -0,0 +1,153 @@ +import type { Meta, StoryObj } from '@storybook/react-webpack5'; + +import { fn } from 'storybook/test'; + +import { Collapse } from '@/components/collapse'; +import { Button } from '@/components/ui/button'; + +// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export +const meta = { + title: 'Example/Collapse', + component: Collapse, + parameters: { + // Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout + layout: 'centered', + docs: { + description: { + component: ` +## Component Description + +Collapse is a component that allows you to show or hide content with a smooth animation. It can be controlled or uncontrolled and supports custom titles and right-aligned content. + +The component uses a trigger element (typically with an icon) to toggle the visibility of its content. It's built on top of Radix UI's Collapsible primitive. + `, + }, + }, + }, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs + // More on argTypes: https://storybook.js.org/docs/api/argtypes + argTypes: { + title: { + control: 'text', + description: 'The title text or element to display in the trigger', + }, + open: { + control: 'boolean', + description: 'Controlled open state of the collapse', + }, + defaultOpen: { + control: 'boolean', + description: 'Initial open state of the collapse', + }, + disabled: { + control: 'boolean', + description: 'Whether the collapse is disabled', + }, + rightContent: { + control: 'text', + description: 'Content to display on the right side of the trigger', + }, + onOpenChange: { + action: 'onOpenChange', + description: 'Callback function when the open state changes', + }, + }, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + args: { onOpenChange: fn() }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args +export const Default: Story = { + args: { + title: 'Collapse Title', + children: ( +
+

This is the collapsible content. It can be any React node.

+

You can put any content here, including other components.

+
+ ), + }, + parameters: { + docs: { + description: { + story: ` +### Usage Examples + +\`\`\`tsx +import { Collapse } from '@/components/collapse'; + + +
+

This is the collapsible content.

+
+
+\`\`\` + `, + }, + }, + }, +}; + +export const WithRightContent: Story = { + args: { + title: 'Collapse with Right Content', + rightContent: , + children: ( +
+

+ This collapse has additional content on the right side of the trigger. +

+
+ ), + }, + parameters: { + docs: { + description: { + story: ` +### Usage Examples + +\`\`\`tsx +import { Collapse } from '@/components/collapse'; +import { Button } from '@/components/ui/button'; + +Action} +> +
+

Content with right-aligned action button.

+
+
+\`\`\` + `, + }, + }, + }, +}; + +export const InitiallyClosed: Story = { + args: { + title: 'Initially Closed Collapse', + defaultOpen: false, + children: ( +
+

This collapse is initially closed.

+
+ ), + }, +}; + +export const Disabled: Story = { + args: { + title: 'Disabled Collapse', + disabled: true, + children: ( +
+

This collapse is disabled and cannot be toggled.

+
+ ), + }, +};