Skip to content
Merged
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
5 changes: 5 additions & 0 deletions apps/public-docsite-v9-headless/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { polyfillBodyAndObserve } from '@microsoft/focusgroup-polyfill/shadowless';

import * as rootPreview from '../../../.storybook/preview';
import { tailwindSandboxTemplate } from './tailwind-sandbox-template';

polyfillBodyAndObserve();

Expand All @@ -19,6 +20,10 @@ export const parameters = {
order: ['Introduction', 'Headless Components'],
},
},
exportToSandbox: {
...rootPreview.parameters.exportToSandbox,
...tailwindSandboxTemplate,
},
};

export const tags = ['autodocs'];
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const sandboxApp = `import { Provider } from '@fluentui/react-headless-components-preview';
import { Example } from './example';

const App = () => (
<Provider>
<Example />
</Provider>
);

export default App;
`;

const tailwindSandboxTemplate = {
devDependencies: {
tailwindcss: '^4.0.0',
'@tailwindcss/vite': '^4.0.0',
},
transformFiles: (/** @type {Record<string, string>} */ files) => ({
...files,
'src/index.css': '@import "tailwindcss";\n',
'src/App.tsx': sandboxApp,
'src/index.tsx': `import './index.css';\n${files['src/index.tsx']}`,
'vite.config.ts': files['vite.config.ts']
.replace(
"import react from '@vitejs/plugin-react'",
"import react from '@vitejs/plugin-react'\nimport tailwindcss from '@tailwindcss/vite'",
)
.replace('plugins: [react()]', 'plugins: [react(), tailwindcss()]'),
}),
};

module.exports = { tailwindSandboxTemplate };
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add template extension params",
"packageName": "@fluentui/react-storybook-addon-export-to-sandbox",
"email": "vgenaev@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,25 @@
*
* only pure API definitions of addon are allowed to live here, that are used both internal and for external storybook `Parameter` type extensions
*/

export interface SandboxContext {
provider: 'codesandbox-cloud' | 'codesandbox-browser' | 'stackblitz-cloud';
bundler: 'vite' | 'cra';
storyExportToken: string;
storyFile: string;
dependencies: Record<string, string>;
requiredDependencies: Record<string, string>;
optionalDependencies: Record<string, string>;
devDependencies: Record<string, string>;
}

interface ParametersConfig {
optionalDependencies?: Record<string, string>;
requiredDependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
provider: 'codesandbox-cloud' | 'codesandbox-browser' | 'stackblitz-cloud';
bundler: 'vite' | 'cra';
transformFiles?: (files: Record<string, string>, ctx: SandboxContext) => Record<string, string>;
}

export interface ParametersExtension {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ describe(`sabdbox-scaffold`, () => {
`,
description: 'react sandbox demo',
title: 'react sandbox',
requiredDependencies: {},
optionalDependencies: {},
devDependencies: {},
};
describe(`cra`, () => {
it(`should generate scaffold for codesandbox-browser`, () => {
Expand Down Expand Up @@ -48,7 +51,7 @@ describe(`sabdbox-scaffold`, () => {
}",
"public/index.html": "<div id=\\"root\\"></div>",
"src/App.tsx": "import { FluentProvider, webLightTheme } from '@fluentui/react-components';
import { DefaultTitle as Example } from './example';
import { Example } from './example';

const App = () => {
return (
Expand All @@ -64,7 +67,9 @@ describe(`sabdbox-scaffold`, () => {
import { Text } from '@proj/react-components';

export const Default = () => <Text>This is an example of the Text component's usage.</Text>;
",

export { DefaultTitle as Example };
",
"src/index.tsx": "import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
Expand Down Expand Up @@ -163,7 +168,7 @@ describe(`sabdbox-scaffold`, () => {
}",
"public/index.html": "<div id=\\"root\\"></div>",
"src/App.tsx": "import { FluentProvider, webLightTheme } from '@fluentui/react-components';
import { DefaultTitle as Example } from './example';
import { Example } from './example';

const App = () => {
return (
Expand All @@ -179,7 +184,9 @@ describe(`sabdbox-scaffold`, () => {
import { Text } from '@proj/react-components';

export const Default = () => <Text>This is an example of the Text component's usage.</Text>;
",

export { DefaultTitle as Example };
",
"src/index.tsx": "import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
Expand Down Expand Up @@ -244,7 +251,7 @@ describe(`sabdbox-scaffold`, () => {
}",
"public/index.html": "<div id=\\"root\\"></div>",
"src/App.tsx": "import { FluentProvider, webLightTheme } from '@fluentui/react-components';
import { DefaultTitle as Example } from './example';
import { Example } from './example';

const App = () => {
return (
Expand All @@ -260,7 +267,9 @@ describe(`sabdbox-scaffold`, () => {
import { Text } from '@proj/react-components';

export const Default = () => <Text>This is an example of the Text component's usage.</Text>;
",

export { DefaultTitle as Example };
",
"src/index.tsx": "import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
Expand Down Expand Up @@ -380,7 +389,7 @@ describe(`sabdbox-scaffold`, () => {
}
}",
"src/App.tsx": "import { FluentProvider, webLightTheme } from '@fluentui/react-components';
import { DefaultTitle as Example } from './example';
import { Example } from './example';

const App = () => {
return (
Expand All @@ -396,7 +405,9 @@ describe(`sabdbox-scaffold`, () => {
import { Text } from '@proj/react-components';

export const Default = () => <Text>This is an example of the Text component's usage.</Text>;
",

export { DefaultTitle as Example };
",
"src/index.tsx": "import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
Expand Down Expand Up @@ -505,7 +516,7 @@ describe(`sabdbox-scaffold`, () => {
}
}",
"src/App.tsx": "import { FluentProvider, webLightTheme } from '@fluentui/react-components';
import { DefaultTitle as Example } from './example';
import { Example } from './example';

const App = () => {
return (
Expand All @@ -521,7 +532,9 @@ describe(`sabdbox-scaffold`, () => {
import { Text } from '@proj/react-components';

export const Default = () => <Text>This is an example of the Text component's usage.</Text>;
",

export { DefaultTitle as Example };
",
"src/index.tsx": "import * as React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
Expand Down Expand Up @@ -587,4 +600,143 @@ describe(`sabdbox-scaffold`, () => {
`);
});
});

describe(`extension params`, () => {
it(`should re-export the story as Example from generated example.tsx`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
});

expect(actual['src/example.tsx']).toContain(`export { DefaultTitle as Example };`);
});

it(`should keep FluentProvider in App.tsx by default`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
});

expect(actual['src/App.tsx']).toContain(
"import { FluentProvider, webLightTheme } from '@fluentui/react-components';",
);
expect(actual['src/App.tsx']).toContain("import { Example } from './example';");
});

it(`should merge devDependencies into Vite package.json`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
devDependencies: { tailwindcss: '^4.0.0', '@tailwindcss/vite': '^4.0.0' },
});

const pkg = JSON.parse(actual['package.json']);
expect(pkg.devDependencies.tailwindcss).toBe('^4.0.0');
expect(pkg.devDependencies['@tailwindcss/vite']).toBe('^4.0.0');
expect(pkg.devDependencies.vite).toBe('^5.0.0');
expect(pkg.dependencies.tailwindcss).toBeUndefined();
});

it(`should merge devDependencies into CRA package.json`, () => {
const actual = scaffold.cra({
provider: 'stackblitz-cloud',
bundler: 'cra',
...config,
devDependencies: { tailwindcss: '^4.0.0' },
});

const pkg = JSON.parse(actual['package.json']);
expect(pkg.devDependencies.tailwindcss).toBe('^4.0.0');
expect(pkg.devDependencies['react-scripts']).toBe('^5.0.0');
});
});

describe(`transformFiles`, () => {
it(`should patch an existing generated file`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
transformFiles: files => ({
...files,
'vite.config.ts': files['vite.config.ts'].replace('react()', 'react(), tailwindcss()'),
}),
});

expect(actual['vite.config.ts']).toContain('plugins: [react(), tailwindcss()]');
});

it(`should add a new file`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
transformFiles: files => ({ ...files, 'extra.txt': 'hello' }),
});

expect(actual['extra.txt']).toBe('hello');
expect(actual['vite.config.ts']).toBeDefined();
});

it(`should drop a file when the returned map omits its key`, () => {
const actual = scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
transformFiles: files => {
const { 'index.html': _dropped, ...rest } = files;
return rest;
},
});

expect(actual['index.html']).toBeUndefined();
expect(actual['src/App.tsx']).toBeDefined();
});

it(`should expose context to the callback`, () => {
let captured: unknown;
scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
requiredDependencies: { 'some-pkg': '^1.0.0' },
optionalDependencies: { react: '^18' },
devDependencies: { tailwindcss: '^4.0.0' },
transformFiles: (files, ctx) => {
captured = ctx;
return files;
},
});

expect(captured).toEqual({
provider: 'stackblitz-cloud',
bundler: 'vite',
storyExportToken: 'DefaultTitle',
storyFile: config.storyFile,
dependencies: {},
requiredDependencies: { 'some-pkg': '^1.0.0' },
optionalDependencies: { react: '^18' },
devDependencies: { tailwindcss: '^4.0.0' },
});
});

it(`should see provider-specific extras in the input map (.stackblitzrc)`, () => {
let capturedKeys: string[] = [];
scaffold.vite({
provider: 'stackblitz-cloud',
bundler: 'vite',
...config,
transformFiles: files => {
capturedKeys = Object.keys(files);
return files;
},
});

expect(capturedKeys).toContain('.stackblitzrc');
expect(capturedKeys).toContain('vite.config.ts');
});
});
});
Loading
Loading