import { isRunningInTest } from '@repobuddy/test' import type { ReactNode } from 'react' import type { DecoratorFunction, Renderer } from 'storybook/internal/csf' import type { StoryCardProps, StoryCardStatus } from '../components/story_card.js' import { StoryCardScope } from '../contexts/_story_card_scope.js' import type { StoryCardParam } from '../parameters/define_story_card_param.js' export type WithStoryCardProps = Omit & { /** * @deprecated Use `appearance` instead. When set, behaves like `appearance` for the same value. */ status?: StoryCardStatus /** * Additional CSS classes or a function to compute classes. * * If a string is provided, it will be merged with the default classes. * If a function is provided, it receives the card state and default className, * and should return the final className string. */ className?: | ((state: Pick & { defaultClassName: string }) => string) | string | undefined /** * Content to display in the card body. * Can be any React node (string, JSX, etc.). * * If not provided, the decorator will automatically use: * 1. Story description (`parameters.docs.description.story`) * 2. Component description (`parameters.docs.description.component`) * 3. Nothing (card won't render if no content and no title) */ content?: ReactNode | undefined } /** * A decorator that adds a card section to display additional information about the story. * * The card is automatically hidden when the story is shown in docs mode. * Multiple decorators can be chained together, * and all cards will be collected and displayed above the story content. * * @returns A Storybook decorator function. * * @example * Basic usage - automatically uses component or story description: * ```tsx * export const MyStory: Story = { * parameters: defineDocsParam({ * description: { * story: 'This description will be shown in the card' * } * }), * decorators: [withStoryCard()] * } * ``` * * @example * Using defineStoryCard parameter: * ```tsx * export const MyStory: Story = { * parameters: defineStoryCard({ * title: 'Important Notice', * status: 'warn', * content:

Please review this carefully.

* }), * decorators: [withStoryCard()] * } * ``` * * @example * With custom content: * ```tsx * export const MyStory: Story = { * decorators: [ * withStoryCard({ * content:

This is a custom message displayed in the card.

* }) * ] * } * ``` * * @example * With title and status: * ```tsx * export const MyStory: Story = { * decorators: [ * withStoryCard({ * title: 'Important Notice', * status: 'warn', * content:

Please review this carefully.

* }) * ] * } * ``` * * @example * Multiple cards: * ```tsx * export const MyStory: Story = { * decorators: [ * withStoryCard({ title: 'First Card', status: 'info' }), * withStoryCard({ title: 'Second Card', status: 'warn' }) * ] * } * ``` * * @remarks * - The card will not render if both `content` and `title` are missing. * - If `content` is not provided, it will automatically use the story description, * or fall back to the component description. * - Cards are collected and displayed in the order they are defined in the decorators array. * - The `status` option is deprecated; use `appearance` instead for the same behavior and additional variants (`source`, `output`). */ export function withStoryCard({ title, status, appearance, content: contentProp, className, ...rest }: WithStoryCardProps = {}): DecoratorFunction { if (isRunningInTest()) { return (Story) => } return (Story, { parameters, viewMode, args }) => { if (viewMode === 'docs') return // Get story card config from parameters if available const storyCardParam = (parameters as Partial).storyCard // Decorator props override parameter values // Use parameters as fallback when decorator props are not provided const finalTitle = title ?? storyCardParam?.title const finalAppearance = appearance ?? storyCardParam?.appearance ?? status ?? storyCardParam?.status ?? 'info' const finalStatus = status ?? storyCardParam?.status const finalContent = contentProp ?? storyCardParam?.content const finalClassName = className ?? storyCardParam?.className // Fallback to docs description if no content provided const content = finalContent ?? parameters.docs?.description?.story ?? parameters.docs?.description?.component if (!content && !finalTitle) return return ( ) } }