Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-docs/Angular: Inline rendering support with angular-elements #13525

Merged
merged 5 commits into from
Jan 28, 2021
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
4 changes: 2 additions & 2 deletions addons/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ Storybook Docs supports all view layers that Storybook supports except for React
| Source | + | + | + | + | + | + | + | + | + | + | + |
| Notes / Info | + | + | + | + | + | + | + | + | + | + | + |
| Props table | + | + | + | + | + | | | | | | |
| Props controls | + | + | | | | | | | | | |
| Props controls | + | + | + | | | | | | | | |
| Description | + | + | + | + | + | | | | | | |
| Inline stories | + | + | | | + | + | | | | | |
| Inline stories | + | + | + | | + | + | | | | | |

**Note:** `#` = WIP support

Expand Down
24 changes: 24 additions & 0 deletions addons/docs/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,30 @@ And for `MDX` you can modify it as an attribute on the `Story` element:
<Story name='basic' height='400px'>{...}</Story>
```

## Inline Stories

Storybook Docs renders all Angular stories inside IFrames by default. But it is possible to use an inline rendering:

To get this, you'll first need to install Angular elements:

```sh
yarn add -D @angular/elements @webcomponents/custom-elements
```

Then update `.storybook/preview.js`:

```js
import { addParameters } from '@storybook/angular';
import { prepareForInline } from '@storybook/addon-docs/angular/inline';

addParameters({
docs: {
inlineStories: true,
prepareForInline,
},
});
```

## More resources

Want to learn more? Here are some more articles on Storybook Docs:
Expand Down
1 change: 1 addition & 0 deletions addons/docs/angular/inline.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../dist/esm/frameworks/angular/prepareForInline');
5 changes: 5 additions & 0 deletions addons/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"@babel/core": "^7.12.10",
"@emotion/core": "^10.1.1",
"@emotion/styled": "^10.0.27",
"@storybook/angular": "6.2.0-alpha.18",
"@storybook/react": "6.2.0-alpha.18",
"@storybook/vue": "6.2.0-alpha.18",
"@storybook/web-components": "6.2.0-alpha.18",
Expand Down Expand Up @@ -123,6 +124,7 @@
},
"peerDependencies": {
"@babel/core": "^7.11.5",
"@storybook/angular": "6.2.0-alpha.18",
"@storybook/vue": "6.2.0-alpha.18",
"babel-loader": "^8.0.0",
"react": "^16.8.0 || ^17.0.0",
Expand All @@ -132,6 +134,9 @@
"webpack": ">=4"
},
"peerDependenciesMeta": {
"@storybook/angular": {
"optional": true
},
"@storybook/vue": {
"optional": true
},
Expand Down
47 changes: 47 additions & 0 deletions addons/docs/src/frameworks/angular/prepareForInline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import { IStory, StoryContext } from '@storybook/angular';
import { ElementRendererService } from '@storybook/angular/element-renderer';
import { StoryFn } from '@storybook/addons';

const customElementsVersions: Record<string, number> = {};

/**
* Uses angular element to generate on-the-fly web components and reference it with `customElements`
* then it is added into react
*/
export const prepareForInline = (storyFn: StoryFn<IStory>, { id, parameters }: StoryContext) => {
// Upgrade story version in order that the next defined component has a unique key
customElementsVersions[id] =
customElementsVersions[id] !== undefined ? customElementsVersions[id] + 1 : 0;

const customElementsName = `${id}_${customElementsVersions[id]}`;

const Story = class Story extends React.Component {
wrapperRef: React.RefObject<unknown>;

elementName: string;

constructor(props: any) {
super(props);
this.wrapperRef = React.createRef();
}

async componentDidMount() {
// eslint-disable-next-line no-undef
customElements.define(
customElementsName,
await new ElementRendererService().renderAngularElement({
storyFnAngular: storyFn(),
parameters,
})
);
}

render() {
return React.createElement(customElementsName, {
ref: this.wrapperRef,
});
}
};
return React.createElement(Story);
};
1 change: 1 addition & 0 deletions app/angular/element-renderer.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/ts3.9/element-renderer.d';
1 change: 1 addition & 0 deletions app/angular/element-renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/ts3.9/element-renderer');
10 changes: 10 additions & 0 deletions app/angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@
"@angular/compiler": "^11.1.0",
"@angular/compiler-cli": "^11.1.0",
"@angular/core": "^11.1.0",
"@angular/elements": "^11.1.0",
"@angular/forms": "^11.1.0",
"@angular/platform-browser": "^11.1.0",
"@angular/platform-browser-dynamic": "^11.1.0",
"@nrwl/workspace": "^11.1.5",
"@types/autoprefixer": "^9.7.2",
"@types/jest": "^25.2.3",
"@webcomponents/custom-elements": "^1.4.3",
"jest": "^26.6.3",
"jest-preset-angular": "^8.3.2",
"ts-jest": "^26.4.4"
Expand All @@ -88,18 +90,26 @@
"@angular/compiler": ">=6.0.0",
"@angular/compiler-cli": ">=6.0.0",
"@angular/core": ">=6.0.0",
"@angular/elements": ">=6.0.0",
"@angular/forms": ">=6.0.0",
"@angular/platform-browser": ">=6.0.0",
"@angular/platform-browser-dynamic": ">=6.0.0",
"@babel/core": "*",
"@nrwl/workspace": ">=11.1.0",
"@webcomponents/custom-elements": ">=1.4.3",
"rxjs": "^6.0.0",
"typescript": "^3.4.0 || >=4.0.0",
"zone.js": "^0.8.29 || ^0.9.0 || ^0.10.0 || ^0.11.0"
},
"peerDependenciesMeta": {
"@angular/elements": {
"optional": true
},
"@nrwl/workspace": {
"optional": true
},
"@webcomponents/custom-elements": {
"optional": true
}
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Should be added first :
// Custom Elements polyfill. Required for browsers that do not natively support Custom Elements.
import '@webcomponents/custom-elements';
// Custom Elements ES5 shim. Required when using ES5 bundles on browsers that natively support
// Custom Elements (either because the browser does not support ES2015 modules or because the app
// is explicitly configured to generate ES5 only bundles).
import '@webcomponents/custom-elements/src/native-shim';

import { Injector, NgModule, Type } from '@angular/core';
import { createCustomElement, NgElementConstructor } from '@angular/elements';

import { BehaviorSubject } from 'rxjs';
import { ICollection, StoryFnAngularReturnType } from '../types';
import { Parameters } from '../types-6-0';
import { getStorybookModuleMetadata } from './StorybookModule';
import { RendererService } from './RendererService';

/**
* Bootstrap angular application to generate a web component with angular element
*/
export class ElementRendererService {
private platform = RendererService.getInstance().platform;

/**
* Returns a custom element generated by Angular elements
*/
public async renderAngularElement({
storyFnAngular,
parameters,
}: {
storyFnAngular: StoryFnAngularReturnType;
parameters: Parameters;
}): Promise<CustomElementConstructor> {
const ngModule = getStorybookModuleMetadata(
{ storyFnAngular, parameters },
new BehaviorSubject<ICollection>(storyFnAngular.props)
);

return this.platform
.bootstrapModule(createElementsModule(ngModule))
.then((m) => m.instance.ngEl);
}
}

const createElementsModule = (ngModule: NgModule): Type<{ ngEl: CustomElementConstructor }> => {
@NgModule({ ...ngModule })
class ElementsModule {
public ngEl: NgElementConstructor<unknown>;

constructor(private injector: Injector) {
this.ngEl = createCustomElement(ngModule.bootstrap[0] as Type<unknown>, {
injector: this.injector,
});
}

ngDoBootstrap() {}
}
return ElementsModule;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class RendererService {
return RendererService.instance;
}

private platform: PlatformRef;
public platform: PlatformRef;

private staticRoot = document.getElementById('root');

Expand All @@ -43,6 +43,7 @@ export class RendererService {
}
// platform should be set after enableProdMode()
this.platform = platformBrowserDynamic();
this.initAngularBootstrapElement();
}

/**
Expand Down Expand Up @@ -83,7 +84,7 @@ export class RendererService {
);
}

initAngularBootstrapElement() {
private initAngularBootstrapElement() {
// Adds DOM element that angular will use as bootstrap component
const storybookWrapperElement = document.createElement(
RendererService.SELECTOR_STORYBOOK_WRAPPER
Expand Down
5 changes: 4 additions & 1 deletion app/angular/src/client/preview/types-6-0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import {
BaseMeta,
BaseStory,
Parameters as DefaultParameters,
StoryContext as DefaultStoryContext,
} from '@storybook/addons';
import { StoryFnAngularReturnType } from './types';

export { Args, ArgTypes, StoryContext } from '@storybook/addons';
export { Args, ArgTypes } from '@storybook/addons';

type AngularComponent = any;
type AngularReturnType = StoryFnAngularReturnType;
Expand All @@ -33,3 +34,5 @@ export type Parameters = DefaultParameters & {
angularLegacyRendering?: boolean;
component: unknown;
};

export type StoryContext = DefaultStoryContext & { parameters: Parameters };
1 change: 1 addition & 0 deletions app/angular/src/element-renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ElementRendererService } from './client/preview/angular-beta/ElementRendererService';
1 change: 1 addition & 0 deletions examples/angular-cli/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ module.exports = {
'@storybook/addon-jest',
'@storybook/addon-backgrounds',
'@storybook/addon-a11y',
'@storybook/addon-toolbars',
],
};
22 changes: 19 additions & 3 deletions examples/angular-cli/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { addParameters, addDecorator } from '@storybook/angular';
import { addParameters } from '@storybook/angular';
import { setCompodocJson } from '@storybook/addon-docs/angular';
import { prepareForInline } from '@storybook/addon-docs/angular/inline';
import addCssWarning from '../src/cssWarning';

// @ts-ignore
Expand All @@ -18,7 +19,22 @@ addCssWarning();

addParameters({
docs: {
// inlineStories: true,
iframeHeight: '60px',
inlineStories: true,
prepareForInline,
},
});

export const globalTypes = {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'light',
toolbar: {
icon: 'paintbrush',
items: [
{ value: 'light', title: 'Light theme' },
{ value: 'dark', title: 'Dark theme' },
],
},
},
};
2 changes: 2 additions & 0 deletions examples/angular-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@angular-devkit/core": "^11.1.0",
"@angular/cli": "^11.1.0",
"@angular/compiler-cli": "^11.1.0",
"@angular/elements": "^11.1.0",
"@compodoc/compodoc": "^1.1.11",
"@storybook/addon-a11y": "6.2.0-alpha.18",
"@storybook/addon-actions": "6.2.0-alpha.18",
Expand All @@ -54,6 +55,7 @@
"@types/jest": "^25.2.3",
"@types/node": "^14.14.20",
"@types/webpack-env": "^1.16.0",
"@webcomponents/custom-elements": "^1.4.3",
"babel-plugin-require-context-hook": "^1.0.0",
"global": "^4.4.0",
"jasmine-core": "~3.6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
exports[`Storyshots Addon/Links button with link to another story 1`] = `
<storybook-wrapper>
<storybook-button-component
_nghost-a-c8=""
_nghost-a-c6=""
ng-reflect-text="Go to Welcome Story"
>
<button
_ngcontent-a-c8=""
_ngcontent-a-c6=""
>
Go to Welcome Story
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,37 @@
exports[`Storyshots Core/Parameters passed to story 1`] = `
<storybook-wrapper>
<storybook-button-component
_nghost-a-c9=""
_nghost-a-c11=""
ng-reflect-text="Parameters are {
\\"docs\\": {
"
>
<button
_ngcontent-a-c9=""
_ngcontent-a-c11=""
>
Parameters are {
"docs": {
"iframeHeight": "60px"
"inlineStories": true
},
"globalTypes": {
"theme": {
"name": "Theme",
"description": "Global theme for components",
"defaultValue": "light",
"toolbar": {
"icon": "paintbrush",
"items": [
{
"value": "light",
"title": "Light theme"
},
{
"value": "dark",
"title": "Dark theme"
}
]
}
}
},
"globalParameter": "globalParameter",
"framework": "angular",
Expand Down
Loading