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

Web-components: Add typescript types and CLI template #12395

Merged
merged 11 commits into from
Dec 20, 2020
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 2 additions & 0 deletions app/web-components/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export {
isValidMetaData,
} from './customElements';

export * from './preview/types-6-0';

// TODO: disable HMR and do full page loads because of customElements.define
if (module && module.hot && module.hot.decline) {
module.hot.decline();
Expand Down
19 changes: 19 additions & 0 deletions app/web-components/src/client/preview/types-6-0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Args as DefaultArgs, Annotations, BaseMeta, BaseStory } from '@storybook/addons';
import { StoryFnHtmlReturnType } from './types';

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

/**
* Metadata to configure the stories for a component.
*
* @see [Default export](https://storybook.js.org/docs/formats/component-story-format/#default-export)
*/
export type Meta<Args = DefaultArgs> = BaseMeta<string> & Annotations<Args, StoryFnHtmlReturnType>;

/**
* Story function that represents a component example.
*
* @see [Named Story exports](https://storybook.js.org/docs/formats/component-story-format/#named-story-exports)
*/
export type Story<Args = DefaultArgs> = BaseStory<Args, StoryFnHtmlReturnType> &
Annotations<Args, StoryFnHtmlReturnType>;
1 change: 1 addition & 0 deletions app/web-components/src/client/preview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { TemplateResult, SVGTemplateResult } from 'lit-element';

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

export type StoryFnHtmlReturnType = string | Node | TemplateResult | SVGTemplateResult;

Expand Down
1 change: 1 addition & 0 deletions app/web-components/types-6-0.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './dist/client/preview/types-6-0.d';
35 changes: 35 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Button.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Story, Meta } from '@storybook/web-components';
import { Button, ButtonProps } from './Button';

export default {
title: 'Example/Button',
argTypes: {
backgroundColor: { control: 'color' },
onClick: { action: 'onClick' },
},
} as Meta;

const Template: Story<Partial<ButtonProps>> = (args) => Button(args);

export const Primary = Template.bind({});
Primary.args = {
primary: true,
label: 'Button',
};

export const Secondary = Template.bind({});
Secondary.args = {
label: 'Button',
};

export const Large = Template.bind({});
Large.args = {
size: 'large',
label: 'Button',
};

export const Small = Template.bind({});
Small.args = {
size: 'small',
label: 'Button',
};
42 changes: 42 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Button.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { html } from 'lit-html';
import './button.css';

export interface ButtonProps {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if we should make all properties here optional or ButtonProps should be exported as a partial type (type ButtonProps = Partial<{...}>). To simplify all the references to just ButtonProps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Partial means you can skip mandatory properties. Which is not what we want

Copy link
Contributor

Choose a reason for hiding this comment

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

It does but my point is everywhere we reference this, we use Partial anyway. So we should probably solve the problem at the interface here rather than every time it gets used (which could mean using partial here or just making the properties optional).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have the same interface for all frameworks. Do you have an example of where Partial would bring value ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not arguing FOR partial im telling you its used already in this PR:

const Template: Story<Partial<ButtonProps>> = (args) => Button(args);

in the stories.

which in every other framework's stories would instead be:

const Template: Story<ButtonProps> = (args) => Button(args);

so i was just trying to point out that maybe instead of using partial, we should make the optional properties in ButtonProps optional. If that's all of them, fine, or consider using partial when defining the type rather than when using it.

/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: 'small' | 'medium' | 'large';
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
}
/**
* Primary UI component for user interaction
*/
export const Button = ({ primary, backgroundColor, size, label, onClick }: ButtonProps) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';

return html`
<button
type="button"
class=${['storybook-button', `storybook-button--${size || 'medium'}`, mode].join(' ')}
style=${backgroundColor && { backgroundColor }}
Copy link
Contributor

Choose a reason for hiding this comment

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

To be aligned with lit best practice we should use ifDefined(backgroundColor) here. It can be imported from lit-html/directives/if-defined

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not a web component expert but if I'm right, there's other options than lit-html, we tried to limit this dependency in the code we provide.
@shilman WDYT ?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I get that. I wasn't sure either, we want it to be as generic as possible so it isn't tied into one library.

I've since realised too I wasn't quite right as in lit we wouldn't even set this property. We could just leave it as an example if you want to keep it generic though

@click=${onClick}
>
${label}
</button>
`;
};
16 changes: 16 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Header.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Story, Meta } from '@storybook/web-components';
import { Header, HeaderProps } from './Header';

export default {
title: 'Example/Header',
} as Meta;

const Template: Story<Partial<HeaderProps>> = (args) => Header(args);

export const LoggedIn = Template.bind({});
LoggedIn.args = {
user: {},
};

export const LoggedOut = Template.bind({});
LoggedOut.args = {};
52 changes: 52 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { html } from 'lit-html';

import { Button } from './Button';
import './header.css';

export interface HeaderProps {
user?: {};
onLogin: () => void;
onLogout: () => void;
onCreateAccount: () => void;
}

export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => html`
<header>
<div class="wrapper">
<div>
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
fill="#FFF"
/>
<path
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
fill="#555AB9"
/>
<path
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
fill="#91BAF8"
/>
</g>
</svg>
<h1>Acme</h1>
</div>
<div>
${user
? Button({ size: 'small', onClick: onLogout, label: 'Log out' })
: html`${Button({
size: 'small',
onClick: onLogin,
label: 'Log in',
})}
${Button({
primary: true,
size: 'small',
onClick: onCreateAccount,
label: 'Sign up',
})}`}
</div>
</div>
</header>
`;
19 changes: 19 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Page.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Story, Meta } from '@storybook/web-components';
import { Page, PageProps } from './Page';
import * as HeaderStories from './Header.stories';

export default {
title: 'Example/Page',
} as Meta;

const Template: Story<Partial<PageProps>> = (args) => Page(args);

export const LoggedIn = Template.bind({});
LoggedIn.args = {
...HeaderStories.LoggedIn.args,
};

export const LoggedOut = Template.bind({});
LoggedOut.args = {
...HeaderStories.LoggedOut.args,
};
70 changes: 70 additions & 0 deletions lib/cli/src/frameworks/web-components/ts/Page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { html } from 'lit-html';
import { Header } from './Header';
import './page.css';

export interface PageProps {
user?: {};
onLogin: () => void;
onLogout: () => void;
onCreateAccount: () => void;
}

export const Page = ({ user, onLogin, onLogout, onCreateAccount }: PageProps) => html`
<article>
${Header({
user,
onLogin,
onLogout,
onCreateAccount,
})}

<section>
<h2>Pages in Storybook</h2>
<p>
We recommend building UIs with a
<a href="https://componentdriven.org" target="_blank" rel="noopener noreferrer">
<strong>component-driven</strong> </a
>process starting with atomic components and ending with pages.
</p>
<p>
Render pages with mock data. This makes it easy to build and review page states without
needing to navigate to them in your app. Here are some handy patterns for managing page data
in Storybook:
</p>
<ul>
<li>
Use a higher-level connected component. Storybook helps you compose such data from the
"args" of child component stories
</li>
<li>
Assemble data in the page component from your services. You can mock these services out
using Storybook.
</li>
</ul>
<p>
Get a guided tutorial on component-driven development at
<a href="https://www.learnstorybook.com" target="_blank" rel="noopener noreferrer">
Learn Storybook
</a>
. Read more in the
<a href="https://storybook.js.org/docs" target="_blank" rel="noopener noreferrer">
docs
</a>
.
</p>
<div className="tip-wrapper">
<span className="tip">Tip</span> Adjust the width of the canvas with the
<svg width="10" height="10" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fillRule="evenodd">
<path
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
id="a"
fill="#999"
/>
</g>
</svg>
Viewports addon in the toolbar
</div>
</section>
</article>
`;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,4 @@
]
]
}
}
}
6 changes: 6 additions & 0 deletions scripts/run-e2e-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ export const web_components: Parameters = {
additionalDeps: ['react', 'react-dom'],
};

export const web_components_typescript: Parameters = {
...web_components,
name: 'web_components_typescript',
typescript: true,
};

export const webpack_react: Parameters = {
name: 'webpack_react',
version: 'latest',
Expand Down