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 = () => (
+ <>
+
+
+
+
+ >
+);
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';
+
+
+
+{/* Component name */}
+
+
+{/* Component description (optional for now) */}
+Write your component description here.
+
+{/* Main example */}
+
+
+{/* Import section */}
+
+
+{/* Props table */}
+
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 = {
+ 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;
+
+// Add your stories here
+export const Basic: Story = {
+ render: (args: ButtonProps) => ,
+};
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 = {
);
},
],
+ 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();