From 975c32db37e052a905c71dbcf63cfee91ef4a35d Mon Sep 17 00:00:00 2001 From: Steve Dodier-Lazaro Date: Sat, 5 Aug 2023 10:21:35 +0200 Subject: [PATCH 1/3] feat: Update title to support 'of' prop Partially implements #22490 --- MIGRATION.md | 4 ++ code/ui/blocks/src/blocks/Title.stories.tsx | 56 +++++++++++++++++++++ code/ui/blocks/src/blocks/Title.tsx | 55 +++++++++++++++++++- docs/api/doc-block-title.md | 6 +++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 code/ui/blocks/src/blocks/Title.stories.tsx diff --git a/MIGRATION.md b/MIGRATION.md index 6fa7d0c5d300..d05d91d36d2f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1429,6 +1429,10 @@ Additionally to changing the docs information architecture, we've updated the AP The primary change of the `Meta` block is the ability to attach to CSF files with `` as described above. +##### Title block + +The `Title` block now also accepts an `of` prop as described above. It still accepts being passed `children`. + ##### Description block, `parameters.notes` and `parameters.info` In 6.5 the Description doc block accepted a range of different props, `markdown`, `type` and `children` as a way to customize the content. diff --git a/code/ui/blocks/src/blocks/Title.stories.tsx b/code/ui/blocks/src/blocks/Title.stories.tsx new file mode 100644 index 000000000000..0eca0b8d2283 --- /dev/null +++ b/code/ui/blocks/src/blocks/Title.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Title } from './Title'; +import * as DefaultButtonStories from '../examples/Button.stories'; + +const meta: Meta = { + component: Title, + title: 'Blocks/Title', + parameters: { + controls: { + include: [], + hideNoControlsWarning: true, + }, + // workaround for https://github.com/storybookjs/storybook/issues/20505 + docs: { source: { type: 'code' } }, + attached: false, + docsStyles: true, + }, +}; +export default meta; + +type Story = StoryObj; + +export const OfCSFFileInComponentTitle: Story = { + name: 'Of CSF File with title', + args: { + of: DefaultButtonStories, + }, + parameters: { relativeCsfPaths: ['../examples/Button.stories'] }, +}; + +export const OfMetaInComponentTitle: Story = { + name: 'Of meta with title', + args: { + of: DefaultButtonStories, + }, + parameters: { relativeCsfPaths: ['../examples/Button.stories'] }, +}; + +export const OfStringMetaAttached: Story = { + name: 'Of attached "meta"', + args: { + of: 'meta', + }, + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: true }, +}; + +export const Children: Story = { + args: { + children: 'Custom title', + }, +}; + +export const DefaultAttached: Story = { + args: {}, + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: true }, +}; diff --git a/code/ui/blocks/src/blocks/Title.tsx b/code/ui/blocks/src/blocks/Title.tsx index 1f52fb2cc179..a530b720a0cb 100644 --- a/code/ui/blocks/src/blocks/Title.tsx +++ b/code/ui/blocks/src/blocks/Title.tsx @@ -3,8 +3,19 @@ import type { FunctionComponent, ReactNode } from 'react'; import React, { useContext } from 'react'; import { Title as PureTitle } from '../components'; import { DocsContext } from './DocsContext'; +import type { Of } from './useOf'; +import { useOf } from './useOf'; interface TitleProps { + /** + * Specify where to get the title from. Must be a CSF file's default export. + * If not specified, the title will be read from children, or extracted from the meta of the attached CSF file. + */ + of?: Of; + + /** + * Specify content to display as the title. + */ children?: ReactNode; } @@ -15,9 +26,49 @@ export const extractTitle = (title: ComponentTitle) => { return (groups && groups[groups.length - 1]) || title; }; -export const Title: FunctionComponent = ({ children }) => { +const getTitleFromResolvedOf = (resolvedOf: ReturnType): string | null => { + switch (resolvedOf.type) { + case 'meta': { + return resolvedOf.preparedMeta.title || null; + } + case 'story': + case 'component': { + throw new Error( + `Unsupported module type. Title's \`of\` prop only supports \`meta\`, got: ${ + (resolvedOf as any).type + }` + ); + } + default: { + throw new Error( + `Unrecognized module type resolved from 'useOf', got: ${(resolvedOf as any).type}` + ); + } + } +}; + +export const Title: FunctionComponent = (props) => { + const { children, of } = props; + + if ('of' in props && of === undefined) { + throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); + } + const context = useContext(DocsContext); - const content = children || extractTitle(context.storyById().title); + + let content; + if (of) { + const resolvedOf = useOf(of || 'meta'); + content = getTitleFromResolvedOf(resolvedOf); + } + + if (!content) { + content = children; + } + + if (!content) { + content = extractTitle(context.storyById().title); + } return content ? {content} : null; }; diff --git a/docs/api/doc-block-title.md b/docs/api/doc-block-title.md index acdc1cee0f90..b9b2c3567a12 100644 --- a/docs/api/doc-block-title.md +++ b/docs/api/doc-block-title.md @@ -29,3 +29,9 @@ import { Title } from '@storybook/blocks'; Type: `JSX.Element | string` Provides the content. Falls back to value of `title` in an [attached](./doc-block-meta.md#attached-vs-unattached) CSF file (or value derived from [autotitle](../configure/sidebar-and-urls.md#csf-30-auto-titles)), trimmed to the last segment. For example, if the title value is `'path/to/components/Button'`, the default content is `'Button'`. + +### `of` + +Type: CSF file exports + +Specifies which meta's title is displayed. From ee0dd00e28c9cd1d8d07c77a4c74065dd4329c6d Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 22 Apr 2024 14:19:52 +0200 Subject: [PATCH 2/3] simplify Title extraction --- code/ui/blocks/src/blocks/Title.stories.tsx | 9 ++-- code/ui/blocks/src/blocks/Title.tsx | 48 +++++---------------- 2 files changed, 15 insertions(+), 42 deletions(-) diff --git a/code/ui/blocks/src/blocks/Title.stories.tsx b/code/ui/blocks/src/blocks/Title.stories.tsx index 0eca0b8d2283..a75b6ef72d98 100644 --- a/code/ui/blocks/src/blocks/Title.stories.tsx +++ b/code/ui/blocks/src/blocks/Title.stories.tsx @@ -20,16 +20,14 @@ export default meta; type Story = StoryObj; -export const OfCSFFileInComponentTitle: Story = { - name: 'Of CSF File with title', +export const OfCSFFile: Story = { args: { of: DefaultButtonStories, }, parameters: { relativeCsfPaths: ['../examples/Button.stories'] }, }; -export const OfMetaInComponentTitle: Story = { - name: 'Of meta with title', +export const OfMeta: Story = { args: { of: DefaultButtonStories, }, @@ -46,8 +44,9 @@ export const OfStringMetaAttached: Story = { export const Children: Story = { args: { - children: 'Custom title', + children: 'Title as children', }, + parameters: { relativeCsfPaths: ['../examples/Button.stories'], attached: false }, }; export const DefaultAttached: Story = { diff --git a/code/ui/blocks/src/blocks/Title.tsx b/code/ui/blocks/src/blocks/Title.tsx index a530b720a0cb..55b85ebad717 100644 --- a/code/ui/blocks/src/blocks/Title.tsx +++ b/code/ui/blocks/src/blocks/Title.tsx @@ -1,8 +1,7 @@ import type { ComponentTitle } from '@storybook/types'; import type { FunctionComponent, ReactNode } from 'react'; -import React, { useContext } from 'react'; +import React from 'react'; import { Title as PureTitle } from '../components'; -import { DocsContext } from './DocsContext'; import type { Of } from './useOf'; import { useOf } from './useOf'; @@ -23,28 +22,7 @@ const STORY_KIND_PATH_SEPARATOR = /\s*\/\s*/; export const extractTitle = (title: ComponentTitle) => { const groups = title.trim().split(STORY_KIND_PATH_SEPARATOR); - return (groups && groups[groups.length - 1]) || title; -}; - -const getTitleFromResolvedOf = (resolvedOf: ReturnType): string | null => { - switch (resolvedOf.type) { - case 'meta': { - return resolvedOf.preparedMeta.title || null; - } - case 'story': - case 'component': { - throw new Error( - `Unsupported module type. Title's \`of\` prop only supports \`meta\`, got: ${ - (resolvedOf as any).type - }` - ); - } - default: { - throw new Error( - `Unrecognized module type resolved from 'useOf', got: ${(resolvedOf as any).type}` - ); - } - } + return groups?.[groups?.length - 1] || title; }; export const Title: FunctionComponent = (props) => { @@ -54,21 +32,17 @@ export const Title: FunctionComponent = (props) => { throw new Error('Unexpected `of={undefined}`, did you mistype a CSF file reference?'); } - const context = useContext(DocsContext); - - let content; - if (of) { - const resolvedOf = useOf(of || 'meta'); - content = getTitleFromResolvedOf(resolvedOf); - } - - if (!content) { - content = children; + let preparedMeta; + try { + preparedMeta = useOf(of || 'meta', ['meta']).preparedMeta; + } catch (error) { + if (children && !error.message.includes('did you forget to use ?')) { + // ignore error about unattached CSF since we can still render children + throw error; + } } - if (!content) { - content = extractTitle(context.storyById().title); - } + const content = children || extractTitle(preparedMeta.title); return content ? {content} : null; }; From 688cf1823b9541cc1b41cfbfeddbfb9b8c202242 Mon Sep 17 00:00:00 2001 From: Jeppe Reinhold Date: Mon, 22 Apr 2024 14:26:18 +0200 Subject: [PATCH 3/3] Move migration note --- MIGRATION.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 2e791f511499..34c0ca87c685 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -2,6 +2,7 @@ - [From version 8.0 to 8.1.0](#from-version-80-to-810) - [Subtitle block and `parameters.componentSubtitle`](#subtitle-block-and-parameterscomponentsubtitle) + - [Title block](#title-block) - [From version 7.x to 8.0.0](#from-version-7x-to-800) - [Portable stories](#portable-stories) - [Project annotations are now merged instead of overwritten in composeStory](#project-annotations-are-now-merged-instead-of-overwritten-in-composestory) @@ -413,6 +414,12 @@ The `Subtitle` block now accepts an `of` prop, which can be a reference to a CSF `parameters.componentSubtitle` has been deprecated to be consistent with other parameters related to autodocs, instead use `parameters.docs.subtitle`. +##### Title block + +The `Title` block now accepts an `of` prop, which can be a reference to a CSF file or a default export (meta). + +It still accepts being passed `children`. + ## From version 7.x to 8.0.0 ### Portable stories @@ -2656,10 +2663,6 @@ Additionally to changing the docs information architecture, we've updated the AP The primary change of the `Meta` block is the ability to attach to CSF files with `` as described above. -##### Title block - -The `Title` block now also accepts an `of` prop as described above. It still accepts being passed `children`. - ##### Description block, `parameters.notes` and `parameters.info` In 6.5 the Description doc block accepted a range of different props, `markdown`, `type` and `children` as a way to customize the content.