Skip to content

Commit

Permalink
docs: adding react snippets in Storybook (#2934)
Browse files Browse the repository at this point in the history
* Adding agenda + login React stories
* Agenda + Login with React stories
* Adding stackblitz editing + get and files
* Adding people + people picker
* Adding person
* Added person-card + picker
* Adding planner
* Updated stories to use useCallback
* Last wave of react components
* fixed regex and formatting for mgt-samples
* Update .storybook/main.js
* Updated based on the PR review
* Updating the base path to account for PR storybooks
* Ignore the assets folder to update package versions
* Adding comments to add context to this weird condition
* Update packages
* Support for next preview versions
* Adding a person w/ person-card React story
* Missing ViewType
* Missing mgt-login from embed overview story
* Using lowerCase PR
* Use the app.js to register the provider
* Changing Storybook auth to avoid unpkg
* Ensuring the right version (next or the actual version)
* Updating to leverage existing versionInfo
* Updated with new string unions
* Removing stale spfx files

fix: ensure loginInitiated is fired every time
  • Loading branch information
sebastienlevert authored Jan 24, 2024
1 parent 3da9560 commit 2fb1064
Show file tree
Hide file tree
Showing 103 changed files with 2,172 additions and 443 deletions.
110 changes: 66 additions & 44 deletions .storybook/addons/codeEditorAddon/codeAddon.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { addons, makeDecorator } from '@storybook/preview-api';
import { ProviderState } from '../../../packages/mgt-element/dist/es6/providers/IProvider';
import { EditorElement } from './editor';
import { CLIENTID, SETPROVIDER_EVENT, AUTH_PAGE } from '../../env';
import { beautifyContent } from '../../utils/beautifyContent';

const mgtScriptName = './mgt.storybook.js';

Expand Down Expand Up @@ -61,6 +62,7 @@ const setupEditorResize = (first, separator, last, dragComplete) => {
};
};

let reactRegex = /<react\b[^>]*>([\s\S]*?)<\/react>/gm;
let scriptRegex = /<script\b[^>]*>([\s\S]*?)<\/script>/gm;
let styleRegex = /<style\b[^>]*>([\s\S]*?)<\/style>/gm;

Expand Down Expand Up @@ -89,20 +91,25 @@ export const withCodeEditor = makeDecorator({
let scriptMatches = scriptRegex.exec(storyHtml);
let scriptCode = scriptMatches && scriptMatches.length > 1 ? scriptMatches[1].trim() : '';

let reactMatches = reactRegex.exec(storyHtml);
let reactCode = reactMatches && reactMatches.length > 1 ? reactMatches[1].trim() : '';

let styleMatches = styleRegex.exec(storyHtml);
let styleCode = styleMatches && styleMatches.length > 1 ? styleMatches[1].trim() : '';

storyHtml = storyHtml
.replace(styleRegex, '')
.replace(scriptRegex, '')
.replace(/\n?<!---->\n?/g, '')
.trim();

let editor = new EditorElement();
editor.files = {
html: storyHtml,
js: scriptCode,
css: styleCode
?.replace(styleRegex, '')
?.replace(reactRegex, '')
?.replace(scriptRegex, '')
?.replace(/\n?<!---->\n?/g, '')
?.trim();

const fileTypes = reactCode ? ['react', 'css'] : ['html', 'js', 'css'];

let editor = new EditorElement(fileTypes);

const isEditorEnabled = () => {
return !context.parameters.docs?.editor?.hidden;
};

const getContent = async (url, json) => {
Expand Down Expand Up @@ -152,9 +159,9 @@ export const withCodeEditor = makeDecorator({
]).then(values => {
//editor.autoFormat = false;
editor.files = {
html: values[0],
js: values[1],
css: values[2]
html: beautifyContent('html', values[0]),
js: beautifyContent('js', values[1]),
css: beautifyContent('css', values[2])
};
});
});
Expand Down Expand Up @@ -207,12 +214,12 @@ export const withCodeEditor = makeDecorator({
redirectUri: "${window.location.origin}/${AUTH_PAGE}"
});`;
}

loadEditorContent();
});

const componentRegistration = `
`;
const getStoryTitle = context => {
const storyTitle = `${context?.title} - ${context?.story}`;
return storyTitle;
};

const loadEditorContent = () => {
const storyElement = document.createElement('iframe');
Expand All @@ -224,35 +231,35 @@ export const withCodeEditor = makeDecorator({

let { html, css, js } = editor.files;
js = js.replace(
/import \{([^\}]+)\}\s+from\s+['"]@microsoft\/mgt\x2d([^\}]+)['"];/gm,
/import \{([^\}]+)\}\s+from\s+['"]@microsoft\/mgt\x2d.*['"];/gm,
`import {$1} from '${mgtScriptName}';`
);

const docContent = `
<html>
<head>
<script type="module" src="${mgtScriptName}"></script>
<script type="module">
import { registerMgtComponents } from "${mgtScriptName}";
registerMgtComponents();
</script>
<script type="module">
${providerInitCode}
</script>
<style>
${themeToggleCss}
${css}
</style>
</head>
<body>
${themeToggle}
${html}
<script type="module">
${js}
</script>
</body>
</html>
`;
<html>
<head>
<script type="module" src="${mgtScriptName}"></script>
<script type="module">
import { registerMgtComponents } from "${mgtScriptName}";
registerMgtComponents();
</script>
<script type="module">
${providerInitCode}
</script>
<style>
${themeToggleCss}
${css}
</style>
</head>
<body>
${themeToggle}
${html}
<script type="module">
${js}
</script>
</body>
</html>
`;

doc.open();
doc.write(docContent);
Expand All @@ -274,13 +281,17 @@ export const withCodeEditor = makeDecorator({
setupEditorResize(storyElementWrapper, separator, editor, () => editor.layout());

root.className = 'story-mgt-root';
storyElementWrapper.className = 'story-mgt-preview-wrapper';

storyElementWrapper.className = isEditorEnabled() ? 'story-mgt-preview-wrapper' : 'story-mgt-preview-wrapper-full';
separator.className = 'story-mgt-separator';
editor.className = 'story-mgt-editor';

root.appendChild(storyElementWrapper);
root.appendChild(separator);
root.appendChild(editor);

if (isEditorEnabled()) {
root.appendChild(editor);
}

window.addEventListener('resize', () => {
storyElementWrapper.style.height = null;
Expand All @@ -289,6 +300,17 @@ export const withCodeEditor = makeDecorator({
editor.style.width = null;
});

if (isEditorEnabled()) {
editor.files = {
html: beautifyContent('html', storyHtml),
react: beautifyContent('js', reactCode),
js: beautifyContent('js', scriptCode),
css: beautifyContent('css', styleCode)
};

editor.title = getStoryTitle(context);
}

return root;
}
});
35 changes: 25 additions & 10 deletions .storybook/addons/codeEditorAddon/editor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LitElement, css, html } from 'lit';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
import { generateProject } from './projectBuilder';

let debounce = (func, wait, immediate) => {
var timeout;
Expand Down Expand Up @@ -50,6 +51,10 @@ export class EditorElement extends LitElement {
border: 1px solid transparent;
}
.tab-right {
float: right;
}
.tab[aria-selected='true'] {
background-color: white;
color: rgb(51, 51, 51);
Expand Down Expand Up @@ -83,11 +88,10 @@ export class EditorElement extends LitElement {
return this.internalFiles;
}

constructor() {
constructor(fileTypes) {
super();
this.internalFiles = [];
this.fileTypes = ['html', 'js', 'css'];
this.autoFormat = true;
this.fileTypes = fileTypes ?? ['html', 'js', 'css'];

this.editorRoot = document.createElement('div');
this.editorRoot.setAttribute('slot', 'editor');
Expand Down Expand Up @@ -120,10 +124,14 @@ export class EditorElement extends LitElement {
html: {
model: monaco.editor.createModel('', 'html'),
state: null
},
react: {
model: monaco.editor.createModel('', 'typescript'),
state: null
}
};

this.currentEditorState = this.editorState.html;
this.currentEditorState = this.editorState[this.fileTypes[0]];

this.editor = monaco.editor.create(htmlElement, {
model: this.currentEditorState.model,
Expand Down Expand Up @@ -185,7 +193,7 @@ export class EditorElement extends LitElement {
}

showTab(type) {
this.editor.updateOptions({ readOnly: false });
this.editor.updateOptions({ readOnly: type === 'react' });

this.currentType = type;
if (this.files && typeof this.files[type] !== 'undefined') {
Expand All @@ -195,10 +203,6 @@ export class EditorElement extends LitElement {
this.editor.setModel(this.currentEditorState.model);
this.editor.restoreViewState(this.currentEditorState.state);
}

if (this.autoFormat) {
this.editor.getAction('editor.action.formatDocument').run();
}
}

tabKeyDown = e => {
Expand Down Expand Up @@ -245,6 +249,17 @@ export class EditorElement extends LitElement {
</button>
`
)}
<button
@click="${_ => generateProject(this.title, this.files)}"
id="project"
role="tab"
class="tab tab-right"
title="Edit in StackBlitz"
>
<svg viewBox="0 0 14 14" width="14px" height="14px" class="css-149xqrd"><path d="M2 1a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.5a.5.5 0 0 0-1 0V12H2V2h4.5a.5.5 0 0 0 0-1H2Z"></path><path d="M7.35 7.36 12 2.7v1.8a.5.5 0 0 0 1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 1 0 0 1h1.8L6.64 6.64a.5.5 0 1 0 .7.7Z"></path></svg>
</button>
</div>
<div
class="editor-root"
Expand All @@ -262,7 +277,7 @@ export class EditorElement extends LitElement {
role="tabpanel"
id="${`tab-${type}`}"
aria-labelledby="${type}"
tabindex=0
tabindex=0
hidden
></div>
`
Expand Down
111 changes: 111 additions & 0 deletions .storybook/addons/codeEditorAddon/projectBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import sdk from '@stackblitz/sdk';
import { beautifyContent } from '../../utils/beautifyContent';
import { getCleanVersionInfo } from '../../versionInfo';

const TEMPLATE_PATH = [window.location.protocol, '//', window.location.host, window.location.pathname]
.join('')
.replace('iframe.html', '');
const REACT_TEMPLATE_PATH = TEMPLATE_PATH + 'stackblitz/react/';
const REACT_TEMPLATE_FILES = [
{ name: 'package.json', type: 'json' },
{ name: 'index.html', type: 'html' },
{ name: 'tsconfig.json', type: 'json' },
{ name: 'tsconfig.node.json', type: 'json' },
{ name: 'vite.config.ts', type: 'js' },
{ name: 'src/vite-env.d.ts', type: 'js' },
{ name: 'src/main.css', type: 'css' },
{ name: 'src/main.tsx', type: 'js' }
];
const HTML_TEMPLATE_PATH = TEMPLATE_PATH + 'stackblitz/html/';
const HTML_TEMPLATE_FILES = [
{ name: 'index.html', type: 'html' },
{ name: 'main.css', type: 'css' },
{ name: 'app.js', type: 'js' },
{ name: 'main.js', type: 'js' },
{ name: 'package.json', type: 'json' },
{ name: 'style.css', type: 'css' }
];

export const generateProject = async (title, files) => {
files.react && files.react !== '\n' ? await openReactProject(title, files) : await openHtmlProject(title, files);
};

let openReactProject = async (title, files) => {
const snippets = [
{ name: 'src/App.tsx', type: 'js', content: files.react },
{ name: 'src/App.css', type: 'css', content: files.css }
];
const stackblitzFiles = await buildFiles(REACT_TEMPLATE_PATH, REACT_TEMPLATE_FILES, snippets);
sdk.openProject(
{
files: stackblitzFiles,
template: 'node',
title: title
},
{
openFile: 'src/App.tsx',
newWindow: true
}
);
};

let openHtmlProject = async (title, files) => {
const snippets = [
{ name: 'index.html', type: 'html', content: files.html },
{ name: 'style.css', type: 'css', content: files.css },
{ name: 'main.js', type: 'js', content: files.js }
];
const stackblitzFiles = await buildFiles(HTML_TEMPLATE_PATH, HTML_TEMPLATE_FILES, snippets);
sdk.openProject(
{
files: stackblitzFiles,
template: 'node',
title: title
},
{
openFile: 'index.html',
newWindow: true
}
);
};

let buildFiles = async (templatePath, files, snippets) => {
const stackblitzFiles = {};
await Promise.all(
files.map(async file => {
let fileContent = await loadFile(templatePath + file.name);
fileContent = fileContent.replace(/<mgt-version><\/mgt-version>/g, getCleanVersionInfo());
stackblitzFiles[file.name] = beautifyContent(file.type, fileContent);
})
);

snippets.map(file => {
let fileContent = '';
if (file.content) {
if (stackblitzFiles[file.name] && stackblitzFiles[file.name] !== '\n') {
fileContent = stackblitzFiles[file.name].replace(/<mgt-component><\/mgt-component>/g, file.content);
} else {
fileContent = file.content;
}
}

fileContent = beautifyContent(file.type, fileContent);
stackblitzFiles[file.name] = fileContent;
});

return stackblitzFiles;
};

let loadFile = async fileUrl => {
let content = '';
if (fileUrl) {
let response = await fetch(fileUrl);

if (response.ok) {
content = await response.text();
} else {
console.warn(`🦒: Can't get snippet from '${fileUrl}'`);
}
}
return content;
};
Loading

0 comments on commit 2fb1064

Please sign in to comment.