diff --git a/.storybook/custom-docs/blocks/Import.tsx b/.storybook/custom-docs/blocks/Import.tsx new file mode 100644 index 00000000000..b8282be6ba5 --- /dev/null +++ b/.storybook/custom-docs/blocks/Import.tsx @@ -0,0 +1,35 @@ +import {Source, useOf} from '@storybook/addon-docs/blocks'; + +interface DocsParameters { + importSubpath?: string; + exportName?: string; +} + +interface PreparedMeta { + parameters?: {docs?: DocsParameters}; +} + +export const Import = () => { + const {preparedMeta} = useOf('meta', ['meta']) as {preparedMeta: PreparedMeta}; + const exportName = preparedMeta.parameters?.docs?.exportName; + const importSubpath = preparedMeta.parameters?.docs?.importSubpath; + + if (!exportName || !importSubpath) { + return null; + } + + const builtPath = `import ${exportName} from '@jetbrains/ring-ui-built/${importSubpath}';`; + const sourcePath = `import ${exportName} from '@jetbrains/ring-ui/${importSubpath}';`; + + return ( + <> +

{'Import'}

+ +

{'Quick start (ready-to-use ES modules):'}

+ + +

{'Build from sources (webpack):'}

+ + + ); +}; diff --git a/.storybook/custom-docs/custom-docs.tsx b/.storybook/custom-docs/custom-docs.tsx new file mode 100644 index 00000000000..cb5d1636ee1 --- /dev/null +++ b/.storybook/custom-docs/custom-docs.tsx @@ -0,0 +1,12 @@ +import {Controls, Primary, Title} from '@storybook/addon-docs/blocks'; + +import {Import} from './blocks/Import'; + +export const CustomDocs = () => ( + <> + + <Primary /> + <Import /> + <Controls /> + </> +); diff --git a/.storybook/main.js b/.storybook/main.js index c7bad24947d..f9a00afb3ed 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,5 +1,5 @@ import {createRequire} from 'node:module'; -import {dirname, join} from 'node:path'; +import {dirname, join, resolve} from 'node:path'; const require = createRequire(import.meta.url); const path = require('path'); @@ -12,7 +12,8 @@ export default { stories: [ // Make welcome stories default '../src/welcome.stories.tsx', - '../src/**/*.stories.{js,ts,tsx}', + '../src/**/!(template)*.mdx', + '../src/**/!(template)*.stories.{js,ts,tsx}', ], features: { @@ -24,9 +25,17 @@ export default { webpackFinal(config) { ringConfig.componentsPath.push(__dirname, path.resolve(__dirname, '../src')); + const autoDocumentationRule = config.module.rules.find(rule => /react-docgen-loader\.js$/.test(rule.loader)); + const mdxRule = config.module.rules.find( + rule => + rule.test.toString().startsWith('/\\.mdx') && rule.use?.some(u => u.loader?.includes('@storybook/addon-docs')), + ); + config.module.rules = [ ...ringConfig.config.module.rules, config.module.rules.find(rule => /react-docgen-loader\.js$/.test(rule.loader)), + autoDocumentationRule, + mdxRule, { test: /\.md$/, loader: 'raw-loader', @@ -44,6 +53,14 @@ export default { {test: /\.m?js$/, resolve: {fullySpecified: false}}, ]; + config.resolve = { + ...config.resolve, + alias: { + ...config.resolve?.alias, + '@custom-docs': resolve(__dirname, './custom-docs'), + }, + }; + const serverUri = pkgConfig.hub; const clientId = pkgConfig.clientId; const hubConfig = JSON.stringify({serverUri, clientId}); diff --git a/.storybook/preview.ts b/.storybook/preview.tsx similarity index 96% rename from .storybook/preview.ts rename to .storybook/preview.tsx index 293e264e0f2..adfaca40d7b 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.tsx @@ -10,6 +10,7 @@ import strictModeDecorator from './strict-mode-decorator'; import stylesDecorator from './styles-decorator'; import themeDecorator from './theme-decorator'; import {darkMatcher, theme} from './theme'; +import {CustomDocs} from './custom-docs/custom-docs'; const updateTheme = () => applyTheme(darkMatcher.matches ? Theme.DARK : Theme.LIGHT, document.documentElement); updateTheme(); @@ -28,6 +29,7 @@ export const parameters = { notes ?? component?.__docgenInfo?.description, theme, codePanel: true, + page: CustomDocs, }, a11y: { test: 'error', diff --git a/.storybook/template/template.mdx b/.storybook/template/template.mdx new file mode 100644 index 00000000000..50ef69f4884 --- /dev/null +++ b/.storybook/template/template.mdx @@ -0,0 +1,23 @@ +{/* Base MDX template for component documentation */} + +import { Meta, Title, Primary, Controls } from '@storybook/addon-docs/blocks'; +import { Import } from '@custom-docs/blocks/Import'; + +import * as TemplateStories from './template.stories'; + +<Meta of={TemplateStories} /> + +{/* Component name */} +<Title /> + +{/* Component description (optional for now) */} +<p>Write your component description here.</p> + +{/* Main example */} +<Primary /> + +{/* Import section */} +<Import /> + +{/* Props table */} +<Controls /> diff --git a/.storybook/template/template.stories.tsx b/.storybook/template/template.stories.tsx new file mode 100644 index 00000000000..f2e6a92d5a2 --- /dev/null +++ b/.storybook/template/template.stories.tsx @@ -0,0 +1,46 @@ +// This is a template Storybook configuration. + +import {type Meta, type StoryObj} from '@storybook/react-webpack5'; + +// Replace with your component import +import Button, {type ButtonProps} from '../../src/button/button'; + +// Example Storybook metadata +const meta: Meta<typeof Button> = { + title: 'Entity/Template', // example: Components/Button + component: Button, + + parameters: { + // Docs parameters for the custom Import block + docs: { + importSubpath: 'components/template/template', // update to match your folder structure + exportName: 'Template', // name of the exported component + }, + }, + + // Prop controls and descriptions for the Docs panel + argTypes: { + disabled: { + control: 'boolean', + description: 'Disables the button when true', + }, + onClick: { + action: 'clicked', + description: 'Callback fired when the button is clicked', + }, + }, + + // Default args for the basic story + args: { + children: 'Click me', + disabled: false, + }, +}; + +export default meta; +type Story = StoryObj<typeof Button>; + +// Add your stories here +export const Basic: Story = { + render: (args: ButtonProps) => <Button {...args} />, +}; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc493bc0ee4..de6b1a5bdda 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -80,3 +80,15 @@ so every component could be inspected via running storybook (`npm start`) and th - Click "Generate" and then "Copy" - Paste the copied text into `/design-tokens.json` - Run `npm run update-styles` + +### Creating a new Story + +1. Copy the base story template: [template.stories.tsx](.storybook/template/template.stories.tsx) + +2. Rename it to your component (e.g. `button.stories.tsx`) and adjust imports and metadata. + +Storybook will automatically generate docs with an example, import section, and props table. +If you need a custom documentation layout (extra sections, guidelines, etc.), +use [template.mdx](.storybook/template/template.mdx) as a starting point. + + diff --git a/src/progress-bar/progress-bar.stories.tsx b/src/progress-bar/progress-bar.stories.tsx index 31612135860..041f7662e77 100644 --- a/src/progress-bar/progress-bar.stories.tsx +++ b/src/progress-bar/progress-bar.stories.tsx @@ -24,6 +24,12 @@ const meta: Meta<typeof ProgressBar> = { ); }, ], + parameters: { + docs: { + importSubpath: 'components/progress-bar/progress-bar', + exportName: 'ProgressBar', + }, + }, argTypes: { value: { control: {type: 'range', min: 0, max: 1, step: 0.1}, diff --git a/test-helpers/jest-globals.js b/test-helpers/jest-globals.js index c832fa2bb0e..13f66b85d50 100644 --- a/test-helpers/jest-globals.js +++ b/test-helpers/jest-globals.js @@ -47,7 +47,7 @@ Object.defineProperty(window, 'matchMedia', { }), }); -setProjectAnnotations(require('../.storybook/preview.ts')); +setProjectAnnotations(require('../.storybook/preview.tsx')); HTMLDialogElement.prototype.show = jest.fn(); HTMLDialogElement.prototype.showModal = jest.fn();