From 6dc2d5cfe1774fa8e57fff91ee305c53d58112f6 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Wed, 20 May 2026 16:05:34 -0400 Subject: [PATCH 1/2] feat(storybook): add global GitHub source link to every story Adds a withGitHubSource decorator in preview.tsx that appends a small 'View source on GitHub' link below every story. The link is derived automatically from context.parameters.fileName (the absolute path to the stories file), strips everything before /src/, and replaces .stories.tsx with .tsx so it points to the component source file rather than the stories file itself. No per-story configuration needed -- works on every canvas and docs page. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .storybook/preview.tsx | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index cb1e316f..b7540d20 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -146,6 +146,40 @@ function applyBrandStyles(brand: BrandConfig, isDark: boolean) { document.head.appendChild(styleTag); } +// Appends a "View source on GitHub" link below each story, derived from the +// story file's absolute path on disk (context.parameters.fileName). +const withGitHubSource: Decorator = (Story, context) => { + const fileName = context.parameters?.fileName as string | undefined; + const srcIndex = fileName ? fileName.indexOf('/src/') : -1; + + const githubUrl = + srcIndex >= 0 + ? `https://github.com/mieweb/ui/blob/main/${fileName! + .slice(srcIndex + 1) + .replace(/\.stories(\.[^.]+)$/, '$1')}` + : null; + + return ( + <> + + {githubUrl && ( +
+ + View source on GitHub ↗ + +
+ )} + + ); +}; + // Brand switcher decorator const withBrand: Decorator = (Story, context) => { const brandName = context.globals.brand || 'bluehive'; @@ -300,7 +334,7 @@ const preview: Preview = { }, }, }, - decorators: [withBrand], + decorators: [withGitHubSource, withBrand], }; export default preview; From 3c782d5dda8bae88f6d50e613e9a063bb4c35ad0 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Wed, 20 May 2026 16:11:28 -0400 Subject: [PATCH 2/2] fix(storybook): harden GitHub source link path handling - Normalize Windows backslashes to forward slashes before any path operations so the link renders correctly on Windows dev machines. - Only strip '.stories' from the basename when it strictly matches 'Name.stories.(ts|tsx|js|jsx)' (no extra dot-segments). Non-standard names like AGGrid.enhanced.stories.tsx now safely link to the stories file itself rather than a non-existent component file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .storybook/preview.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index b7540d20..5e9c34c4 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -149,15 +149,22 @@ function applyBrandStyles(brand: BrandConfig, isDark: boolean) { // Appends a "View source on GitHub" link below each story, derived from the // story file's absolute path on disk (context.parameters.fileName). const withGitHubSource: Decorator = (Story, context) => { - const fileName = context.parameters?.fileName as string | undefined; + const rawFileName = context.parameters?.fileName as string | undefined; + // Normalize Windows backslashes to forward slashes before any path operations + const fileName = rawFileName ? rawFileName.replace(/\\/g, '/') : undefined; const srcIndex = fileName ? fileName.indexOf('/src/') : -1; - const githubUrl = - srcIndex >= 0 - ? `https://github.com/mieweb/ui/blob/main/${fileName! - .slice(srcIndex + 1) - .replace(/\.stories(\.[^.]+)$/, '$1')}` - : null; + const githubUrl = (() => { + if (srcIndex < 0 || !fileName) return null; + const relPath = fileName.slice(srcIndex + 1); + const basename = relPath.split('/').pop() ?? ''; + // Only strip `.stories` when the basename is strictly `Name.stories.(ts|tsx|js|jsx)` + // (no extra dot-segments before `.stories`). Otherwise link to the stories file itself. + const stripped = /^[^.]+\.stories\.(tsx?|jsx?)$/.test(basename) + ? relPath.replace(/\.stories(\.[^.]+)$/, '$1') + : relPath; + return `https://github.com/mieweb/ui/blob/main/${stripped}`; + })(); return ( <>