Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"mcpServers": {
"canvas-kit-storybook": {
"url": "http://localhost:9001/mcp"
}
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# dependencies
node_modules
.yarn/install-state.gz

# misc
.DS_Store
Expand Down
175 changes: 141 additions & 34 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,52 @@
import mdx from '@mdx-js/rollup';
import crypto from 'node:crypto';
import {existsSync, readFileSync} from 'node:fs';
import {dirname, resolve} from 'node:path';

import {StorybookConfig} from '@storybook/react-vite';
import remarkGfm from 'remark-gfm';
import ts from 'typescript';
import {mergeConfig} from 'vite';

// Drop the `/index.ts` if using the published package
import {styleTransformer} from '@workday/canvas-kit-styling-transform';
import {ExportedSymbol, Value} from '@workday/canvas-kit-docs/docgen/docTypes';
import {
createConfig,
styleTransformer,
vitePluginTypescriptWithTransformers,
} from '@workday/canvas-kit-styling-transform';
import {DocParser} from '@workday/canvas-kit-docs/docgen/docParser.ts';
import type {ExportedSymbol, Value} from '@workday/canvas-kit-docs/docgen/docTypes.ts';
import {componentParser} from '@workday/canvas-kit-docs/docgen/plugins/componentParser.ts';
import {enhancedComponentParser} from '@workday/canvas-kit-docs/docgen/plugins/enhancedComponentParser.ts';
import {modelParser} from '@workday/canvas-kit-docs/docgen/plugins/modelParser.ts';

import {version} from '../lerna.json' assert {type: 'json'};
import stylingConfig from '../styling.config';
import { vitePluginInlineSpecifications } from './vite-plugin-inline-specifications';
import { vitePluginRedirectMDXToGithub } from './vite-plugin-redirect-mdx-to-github';
import { vitePluginWholeSource } from './vite-plugin-whole-source';
import { vitePluginTypescriptWithTransformers } from '@workday/canvas-kit-styling-transform';
import { getDocParser } from '@workday/canvas-kit-docs/docgen/createDocProgram';
import pkg from '../lerna.json' with {type: 'json'};
import {vitePluginInlineSpecifications} from './vite-plugin-inline-specifications.ts';
import {vitePluginRedirectMDXToGithub} from './vite-plugin-redirect-mdx-to-github.ts';
import {vitePluginWholeSource} from './vite-plugin-whole-source.ts';

// const modulesPath = path.resolve(__dirname, '../modules');
const processDocs = process.env.SKIP_DOCGEN !== 'true';

const docsMap = new Map<string, ExportedSymbol<Value>[]>();

// Inline styling config to avoid importing handleFocusRing which pulls in
// @workday/canvas-kit-react/common (a directory subpath that Node ESM can't resolve).
// focusRing() still works at runtime via Emotion — it's just not statically compiled.
const stylingConfig = createConfig({
prefix: 'cnvs',
getPrefix(path) {
const match = path.match(/.+modules\/(preview|labs)-react\/([^/]+)\/.+/);
if (match) {
return `cnvs-${match[1]}`;
}
return 'cnvs';
},
seed: crypto.createHash('sha256').update(pkg.version).digest('hex').slice(0, 6),
fallbackFiles: [],
});

const config: StorybookConfig = {
framework: '@storybook/react-vite',
staticDirs: ['../public'],
stories: ['../modules/**/mdx/**/*.mdx', '../modules/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
{
name: '@storybook/addon-essentials',
options: {
actions: false, // Disabled because actions is SLOW
},
},
'@storybook/addon-storysource',
{
name: '@storybook/addon-docs',
options: {
Expand All @@ -43,9 +57,9 @@ const config: StorybookConfig = {
},
},
},
'@storybook/addon-mcp',
],
core: {
builder: '@storybook/builder-vite',
disableTelemetry: true,
},
docs: {
Expand All @@ -54,47 +68,57 @@ const config: StorybookConfig = {
},
typescript: {
check: false,
reactDocgen: false, // we'll handle this ourselves
reactDocgen: 'react-docgen-typescript',
},
viteFinal(config) {
return mergeConfig(
{
plugins: [
vitePluginInlineSpecifications(),
vitePluginRedirectMDXToGithub(),
{
enforce: 'pre',
...mdx({
include: '*.md',
providerImportSource: '@mdx-js/react',
remarkPlugins: [remarkGfm],
}),
},
vitePluginWholeSource(),
vitePluginTypescriptWithTransformers({
include: /modules\/.+\.tsx?/,
exclude: /examples|stories|spec|codemod|docs/,
transformers: [
processDocs
? program => {
const docParser = getDocParser(program);
const docParser = new DocParser(program, [
enhancedComponentParser,
componentParser,
modelParser,
] as any);
return _context => {
return node => {
if (ts.isSourceFile(node)) {
const fileName = node.fileName;
const symbols = docParser.getExportedSymbols(fileName);
docsMap.set(fileName, symbols);
}

return node;
};
};
}
: undefined,
program => styleTransformer(program, {...stylingConfig, extractCSS: false}),
program => {
const transform = styleTransformer(program, {
...stylingConfig,
extractCSS: false,
});
return context => {
const visit = transform(context);
return sourceFile => {
try {
return visit(sourceFile);
} catch {
return sourceFile;
}
};
};
},
],
postTransform(code, id) {
let newCode = code.replace('%VERSION%', version);
let newCode = code.replace('%VERSION%', pkg.version);
if (docsMap.get(id) && processDocs) {
return (
newCode +
Expand All @@ -116,4 +140,87 @@ if (window.__updateDocs) {
},
};

const EXAMPLE_EXTENSIONS = ['.tsx', '.ts', '.jsx', '.js'];

function resolveExampleFile(storyDir: string, importPath: string): string | null {
const base = resolve(storyDir, importPath);
for (const ext of EXAMPLE_EXTENSIONS) {
const fullPath = base + ext;
if (existsSync(fullPath)) return fullPath;
}
if (existsSync(base)) return base;
return null;
}

function enrichManifestSnippets(existing: Record<string, any>) {
const components = existing?.components?.components;
if (!components) return existing;

for (const component of Object.values(components) as any[]) {
if (!component.path || !component.stories?.length) continue;

const storyFilePath = resolve(process.cwd(), component.path);
const storyDir = dirname(storyFilePath);
let storySource: string;
try {
storySource = readFileSync(storyFilePath, 'utf-8');
} catch {
continue;
}

const importMap = new Map<string, string>();
const importRe = /import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/g;
let m;
while ((m = importRe.exec(storySource)) !== null) {
const importPath = m[2];
if (!importPath.includes('/examples/') && !importPath.startsWith('./examples/')) continue;
for (const spec of m[1].split(',')) {
const trimmed = spec.trim();
if (!trimmed) continue;
const parts = trimmed.split(/\s+as\s+/);
const localName = (parts[1] || parts[0]).trim();
const resolved = resolveExampleFile(storyDir, importPath);
if (resolved) importMap.set(localName, resolved);
}
}

if (importMap.size === 0) continue;

const renderMap = new Map<string, string>();
const renderRe = /export\s+(?:const|var|let)\s+(\w+)[^=]*=\s*\{[^}]*?render:\s*(\w+)/gs;
while ((m = renderRe.exec(storySource)) !== null) {
renderMap.set(m[1], m[2]);
}

for (const story of component.stories) {
let exportName: string | undefined;
const snippetMatch = story.snippet?.match(/^const\s+(\w+)\s*=/);
if (snippetMatch) {
exportName = snippetMatch[1];
} else {
exportName = story.name.replace(/\s+/g, '');
}

const renderFnName = renderMap.get(exportName);
if (!renderFnName) continue;

const examplePath = importMap.get(renderFnName);
if (!examplePath) continue;

try {
story.snippet = readFileSync(examplePath, 'utf-8').trim();
} catch {
// keep original
}
}
}

return existing;
}

// Storybook's importModule returns mod.default, so named exports from main.ts
// are not visible to the preset system. Attach the hook directly on the config
// object so it survives as part of the preset's "rest" properties.
(config as any).experimental_manifests = enrichManifestSnippets;

export default config;
2 changes: 1 addition & 1 deletion .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import {addons} from '@storybook/manager-api';
import {addons} from 'storybook/manager-api';
import canvasTheme from './theme';
import {Label} from './Label';

Expand Down
3 changes: 1 addition & 2 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {DocsContainer, DocsPage} from '@storybook/addon-docs';
import {DocsContainer} from '@storybook/addon-docs/blocks';

import {defaultCanvasTheme} from '@workday/canvas-kit-react/common';
import '@workday/canvas-tokens-web/css/base/_variables.css';
Expand Down Expand Up @@ -69,7 +69,6 @@ export const parameters = {
},
docs: {
container: DocsContainer,
page: DocsPage,
theme,
},
chromatic: {
Expand Down
2 changes: 1 addition & 1 deletion .storybook/theme.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {create} from '@storybook/theming';
import {create} from 'storybook/theming';
import {version} from '../lerna.json';
import {system} from '@workday/canvas-tokens-web';

Expand Down
2 changes: 1 addition & 1 deletion .storybook/vite-plugin-inline-specifications.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type PluginOption} from 'vite';

import {parseSpecFile} from '@workday/canvas-kit-docs/utils/parseSpecFile';
import {parseSpecFile} from '@workday/canvas-kit-docs/utils/parseSpecFile.ts';

/**
* Inline specification metadata into MDX files
Expand Down
4 changes: 2 additions & 2 deletions .storybook/vite-plugin-redirect-mdx-to-github.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import path from 'node:path';
import {type PluginOption} from 'vite';

import routes from './routes';
import routes from './routes.js';

const basePath = path.resolve(__dirname, '../');
const basePath = process.cwd();

/**
* Inlines specification metadata into MDX files
Expand Down
2 changes: 1 addition & 1 deletion .storybook/vite-plugin-whole-source.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {type PluginOption} from 'vite';

import extractExports from '@workday/canvas-kit-docs/webpack/extract-exports';
import extractExports from '@workday/canvas-kit-docs/webpack/extract-exports.js';

/**
* Inline specification metadata into MDX files
Expand Down
20 changes: 20 additions & 0 deletions .yarn/patches/@emotion-cache-npm-11.7.1-82b45442ee.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/package.json b/package.json
index 437f0bf23db1dbfb007a327777b966264e51e66c..ea5954c773d536919b10a5af1f605bf7697ea6dc 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,15 @@
"description": "emotion's cache",
"main": "dist/emotion-cache.cjs.js",
"module": "dist/emotion-cache.esm.js",
+ "exports": {
+ ".": {
+ "types": "./types/index.d.ts",
+ "import": "./dist/emotion-cache.esm.js",
+ "require": "./dist/emotion-cache.cjs.js",
+ "default": "./dist/emotion-cache.cjs.js"
+ },
+ "./package.json": "./package.json"
+ },
"browser": {
"./dist/emotion-cache.cjs.js": "./dist/emotion-cache.browser.cjs.js",
"./dist/emotion-cache.esm.js": "./dist/emotion-cache.browser.esm.js"
26 changes: 26 additions & 0 deletions .yarn/patches/@emotion-css-npm-11.7.1-25ff8755a7.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
diff --git a/package.json b/package.json
index 45969ee73338ec79e4bffb1952edaabd0cf5060b..f607bfe5f63032c2eb87ba1130eae7cd8173bd97 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,21 @@
"main": "dist/emotion-css.cjs.js",
"module": "dist/emotion-css.esm.js",
"types": "types/index.d.ts",
+ "exports": {
+ ".": {
+ "types": "./types/index.d.ts",
+ "import": "./dist/emotion-css.esm.js",
+ "require": "./dist/emotion-css.cjs.js",
+ "default": "./dist/emotion-css.cjs.js"
+ },
+ "./create-instance": {
+ "types": "./types/create-instance.d.ts",
+ "import": "./create-instance/dist/emotion-css-create-instance.esm.js",
+ "require": "./create-instance/dist/emotion-css-create-instance.cjs.js",
+ "default": "./create-instance/dist/emotion-css-create-instance.cjs.js"
+ },
+ "./package.json": "./package.json"
+ },
"files": [
"src",
"dist",
20 changes: 20 additions & 0 deletions .yarn/patches/@emotion-memoize-npm-0.7.5-e5e7e9eeca.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
diff --git a/package.json b/package.json
index 3feaa423f3da3b4ae55179aeda2b1f16588d396f..12f4cbe94a32228391c94cef2c3906961b5499ce 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,15 @@
"description": "emotion's memoize utility",
"main": "dist/emotion-memoize.cjs.js",
"module": "dist/emotion-memoize.esm.js",
+ "exports": {
+ ".": {
+ "types": "./types/index.d.ts",
+ "import": "./dist/emotion-memoize.esm.js",
+ "require": "./dist/emotion-memoize.cjs.js",
+ "default": "./dist/emotion-memoize.cjs.js"
+ },
+ "./package.json": "./package.json"
+ },
"types": "types/index.d.ts",
"license": "MIT",
"repository": "https://github.com/emotion-js/emotion/tree/master/packages/memoize",
Loading
Loading