Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dabc124
feat: enhance Select component with additional stories and features
Aunshon Apr 2, 2026
377d091
feat: update ComboboxDropdownMenuDemo to improve dropdown menu struct…
Aunshon Apr 2, 2026
44af396
feat: enhance Combobox component with dropdown menu and dialog for la…
Aunshon Apr 2, 2026
2245416
feat: enhance ComboboxInput component with aria-invalid styles and tr…
Aunshon Apr 2, 2026
f2bc6b1
feat: enhance AsyncCombobox component with improved search functional…
Aunshon Apr 2, 2026
29dc014
feat: add AsyncMultiCombobox component with various demo stories
Aunshon Apr 2, 2026
c793a5a
feat: update AsyncMultiCombobox badge styles for improved appearance
Aunshon Apr 2, 2026
0c8f593
feat: improve AsyncMultiCombobox accessibility and interaction for di…
Aunshon Apr 2, 2026
725f193
feat: add static options demos for AsyncCombobox and AsyncMultiCombob…
Aunshon Apr 2, 2026
c171100
feat: add SmartMultiSelect and SmartSelect components with comprehens…
Aunshon Apr 2, 2026
cd7e13f
feat: enhance SmartSelect and SmartMultiSelect components with create…
Aunshon Apr 2, 2026
d426bb0
feat: optimize option handling in SmartSelect create functionality
Aunshon Apr 2, 2026
0bf9d64
feat: refactor addOption function to update filteredOptions in a sing…
Aunshon Apr 2, 2026
2a83360
feat: add create functionality and custom forms to SmartMultiSelect c…
Aunshon Apr 2, 2026
a76b96f
feat: replace ChevronsUpDownIcon with ChevronDownIcon in SmartMultiSe…
Aunshon Apr 2, 2026
6399e0a
feat: add Babel plugin to inject source code of wrapper functions int…
Aunshon Apr 2, 2026
e43b9af
feat: add Claude theme and dark theme to theme options
Aunshon Apr 2, 2026
7ca9cf5
Refactor code structure for improved readability and maintainability
Aunshon Apr 2, 2026
362de4c
Implement feature X to enhance user experience and optimize performance
Aunshon Apr 3, 2026
4560196
Implement feature X to enhance user experience and fix bug Y in module Z
Aunshon Apr 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .storybook/babel-plugin-story-source.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* Babel plugin that extracts wrapper/demo function source code and injects it
* as `parameters.docs.source.code` into Storybook story exports.
*
* Problem: Stories that use `render: () => <DemoWrapper />` cause Storybook's
* "Show code" panel to display `<DemoWrapper />` instead of the actual
* component usage inside the wrapper.
*
* Solution: At compile time, this plugin:
* 1. Collects all top-level non-exported function declarations (the wrappers).
* 2. For each story export whose render body is `<WrapperName />`, it copies
* the wrapper function's original source into
* `parameters.docs.source.code` so Storybook displays it verbatim.
*/
module.exports = function storySourcePlugin({ types: t }) {
return {
name: 'story-source-injector',
visitor: {
Program: {
enter(programPath, state) {
const fileSource = state.file.code;
if (!fileSource) return;

// ── Step 1: collect wrapper functions ────────────────────────
const wrapperFunctions = new Map();

for (const stmt of programPath.get('body')) {
// Skip any exported declaration
if (
stmt.isExportNamedDeclaration() ||
stmt.isExportDefaultDeclaration()
) {
continue;
}

if (stmt.isFunctionDeclaration()) {
const name = stmt.node.id?.name;
if (!name) continue;

const { start, end } = stmt.node;
if (start == null || end == null) continue;

wrapperFunctions.set(name, fileSource.slice(start, end));
}
}

if (wrapperFunctions.size === 0) return;

// ── Step 2: inject source into matching story exports ────────
for (const stmt of programPath.get('body')) {
if (!stmt.isExportNamedDeclaration()) continue;

const decl = stmt.get('declaration');
if (!decl.isVariableDeclaration()) continue;

for (const declarator of decl.get('declarations')) {
let init = declarator.get('init');

// Unwrap `{ ... } satisfies Type` or `{ ... } as Type`
if (
init.isTSSatisfiesExpression?.() ||
init.isTSAsExpression?.()
) {
init = init.get('expression');
}

if (!init.isObjectExpression()) continue;

const properties = init.get('properties');

// Find `render` property
const renderProp = properties.find(
(p) =>
p.isObjectProperty() &&
p.get('key').isIdentifier({ name: 'render' })
);
if (!renderProp) continue;

const renderVal = renderProp.get('value');
if (!renderVal.isArrowFunctionExpression()) continue;

const renderBody = renderVal.get('body');
if (!renderBody.isJSXElement()) continue;

// Must be a self-closing element: <WrapperName />
const opening = renderBody.get('openingElement');
const nameNode = opening.get('name');
if (!nameNode.isJSXIdentifier()) continue;

const wrapperName = nameNode.node.name;
if (!wrapperFunctions.has(wrapperName)) continue;

// Skip stories that already define their own `parameters`
const hasParams = properties.some(
(p) =>
p.isObjectProperty() &&
p.get('key').isIdentifier({ name: 'parameters' })
);
if (hasParams) continue;

// Build: parameters: { docs: { source: { code, language } } }
const sourceCode = wrapperFunctions.get(wrapperName);

const sourceObj = t.objectExpression([
t.objectProperty(
t.identifier('code'),
t.stringLiteral(sourceCode)
),
t.objectProperty(
t.identifier('language'),
t.stringLiteral('tsx')
),
]);

const docsObj = t.objectExpression([
t.objectProperty(t.identifier('source'), sourceObj),
]);

const paramsObj = t.objectExpression([
t.objectProperty(t.identifier('docs'), docsObj),
]);

init.node.properties.push(
t.objectProperty(t.identifier('parameters'), paramsObj)
);
Comment on lines +93 to +125
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t bail out when parameters already exists; merge docs.source instead.

At Line 99, the plugin exits if parameters exists, so many valid stories won’t get injected source at all. This undermines the plugin’s core behavior.

Suggested diff
-              const hasParams = properties.some(
-                (p) =>
-                  p.isObjectProperty() &&
-                  p.get('key').isIdentifier({ name: 'parameters' })
-              );
-              if (hasParams) continue;
+              const paramsProp = properties.find(
+                (p) =>
+                  p.isObjectProperty() &&
+                  p.get('key').isIdentifier({ name: 'parameters' })
+              );
...
-              const paramsObj = t.objectExpression([
-                t.objectProperty(t.identifier('docs'), docsObj),
-              ]);
-
-              init.node.properties.push(
-                t.objectProperty(t.identifier('parameters'), paramsObj)
-              );
+              if (!paramsProp) {
+                const paramsObj = t.objectExpression([
+                  t.objectProperty(t.identifier('docs'), docsObj),
+                ]);
+                init.node.properties.push(
+                  t.objectProperty(t.identifier('parameters'), paramsObj)
+                );
+                continue;
+              }
+
+              const paramsVal = paramsProp.get('value');
+              if (!paramsVal.isObjectExpression()) continue;
+
+              const hasDocs = paramsVal.get('properties').some(
+                (p) =>
+                  p.isObjectProperty() &&
+                  p.get('key').isIdentifier({ name: 'docs' })
+              );
+              if (!hasDocs) {
+                paramsVal.node.properties.push(
+                  t.objectProperty(t.identifier('docs'), docsObj)
+                );
+              }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.storybook/babel-plugin-story-source.js around lines 93 - 125, The code
currently bails out when a story already has a parameters property (hasParams) —
instead, locate the existing parameters object in init.node.properties (the
objectProperty with key 'parameters'), and merge/augment it: ensure it has a
docs objectProperty and within that a source objectProperty, then set/replace
the source's code and language using the source string from
wrapperFunctions.get(wrapperName). Build the sourceObj (code + language) and
docsObj as needed but when parameters exists, update its properties rather than
returning early so you only add/overwrite parameters.docs.source while
preserving any other existing parameter keys.

}
}
},
},
},
};
};
3 changes: 3 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ const config: StorybookConfig = {
const babelLoaderForStories = {
loader: require.resolve("babel-loader"),
options: {
plugins: [
require.resolve("./babel-plugin-story-source"),
],
presets: [
[require.resolve("@babel/preset-react"), { runtime: "automatic" }],
[require.resolve("@babel/preset-typescript"), { allowDeclareFields: true }],
Expand Down
22 changes: 22 additions & 0 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@ export const globalTypes = {
{ value: 'cyberpunk', title: 'Cyberpunk' },
{ value: 'twitter', title: 'Twitter' },
{ value: 'slate', title: 'Slate' },
{ value: 'claude', title: 'Claude' },
{ value: 'claymorphism', title: 'Claymorphism' },
{ value: 'clean-slate', title: 'Clean Slate' },
{ value: 'modern-minimal', title: 'Modern Minimal' },
{ value: 'nature', title: 'Nature' },
{ value: 'neo-brutalism', title: 'Neo Brutalism' },
{ value: 'notebook', title: 'Notebook' },
{ value: 'ocean-breeze', title: 'Ocean Breeze' },
{ value: 'supabase', title: 'Supabase' },
{ value: 'terminal', title: 'Terminal' },
{ value: 'whatsapp', title: 'WhatsApp' },
],
showName: true,
},
Expand Down Expand Up @@ -91,6 +102,17 @@ export const decorators = [
'cyberpunk': { tokens: Themes.cyberpunkTheme, darkTokens: Themes.cyberpunkDarkTheme },
'twitter': { tokens: Themes.twitterTheme, darkTokens: Themes.twitterDarkTheme },
slate: { tokens: Themes.slateTheme, darkTokens: Themes.slateDarkTheme },
claude: { tokens: Themes.claudeTheme, darkTokens: Themes.claudeDarkTheme },
claymorphism: { tokens: Themes.claymorphismTheme, darkTokens: Themes.claymorphismDarkTheme },
'clean-slate': { tokens: Themes.cleanSlateTheme, darkTokens: Themes.cleanSlateDarkTheme },
'modern-minimal': { tokens: Themes.modernMinimalTheme, darkTokens: Themes.modernMinimalDarkTheme },
nature: { tokens: Themes.natureTheme, darkTokens: Themes.natureDarkTheme },
'neo-brutalism': { tokens: Themes.neoBrutalismTheme, darkTokens: Themes.neoBrutalismDarkTheme },
notebook: { tokens: Themes.notebookTheme, darkTokens: Themes.notebookDarkTheme },
'ocean-breeze': { tokens: Themes.oceanBreezeTheme, darkTokens: Themes.oceanBreezeDarkTheme },
supabase: { tokens: Themes.supabaseTheme, darkTokens: Themes.supabaseDarkTheme },
terminal: { tokens: Themes.terminalTheme, darkTokens: Themes.terminalDarkTheme },
whatsapp: { tokens: Themes.whatsappTheme, darkTokens: Themes.whatsappDarkTheme },
};

const activeBrand = themeMap[brand] || themeMap.default;
Expand Down
Loading
Loading