Skip to content

Commit

Permalink
Merge pull request #5018 from storybooks/ts-migration/addons
Browse files Browse the repository at this point in the history
Typescript-Migration: @storybook/addons
  • Loading branch information
ndelangen authored Dec 27, 2018
2 parents b359bb2 + a903991 commit 87c6d93
Show file tree
Hide file tree
Showing 23 changed files with 231 additions and 166 deletions.
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lib/cli/test
*.bundle.js
*.js.map
*.ts
*.tsx

!.remarkrc.js
!.babelrc.js
Expand Down
2 changes: 1 addition & 1 deletion addons/notes/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
"license": "MIT",
"main": "dist/public_api.js",
"types": "dist/public_api.d.ts",
"jsnext:main": "src/public_api.ts",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
"dependencies": {
"@emotion/styled": "^0.10.6",
"@storybook/addons": "4.2.0-alpha.7",
"@storybook/channels": "4.2.0-alpha.7",
"core-js": "^2.5.7",
"marked": "^0.5.2",
"prop-types": "^15.6.2"
Expand Down
58 changes: 35 additions & 23 deletions addons/notes/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,46 @@
import addons, { makeDecorator } from '@storybook/addons';
import { parse as renderMarkdown } from 'marked';
import {
addons,
makeDecorator,
StoryContext,
StoryGetter,
WrapperSettings,
} from '@storybook/addons';

function wrapper(
getStory: StoryGetter,
context: StoryContext,
{ options, parameters }: WrapperSettings
) {
const channel = addons.getChannel();

const storyOptions = parameters || options;

const { text, markdown, markdownOptions } =
typeof storyOptions === 'string'
? {
text: storyOptions,
markdown: undefined,
markdownOptions: undefined,
}
: storyOptions;

if (!text && !markdown) {
throw new Error('You must set of one of `text` or `markdown` on the `notes` parameter');
}

channel.emit('storybook/notes/add_notes', text || renderMarkdown(markdown, markdownOptions));

return getStory(context);
}

// todo resolve any after @storybook/addons and @storybook/channels are migrated to TypeScript
export const withNotes = makeDecorator({
name: 'withNotes',
parameterName: 'notes',
skipIfNoParametersOrOptions: true,
allowDeprecatedUsage: true,
wrapper: (getStory: (context: any) => any, context: any, { options, parameters }: any) => {
const channel = addons.getChannel();

const storyOptions = parameters || options;

const { text, markdown, markdownOptions } =
typeof storyOptions === 'string'
? {
text: storyOptions,
markdown: undefined,
markdownOptions: undefined,
}
: storyOptions;

if (!text && !markdown) {
throw new Error('You must set of one of `text` or `markdown` on the `notes` parameter');
}

channel.emit('storybook/notes/add_notes', text || renderMarkdown(markdown, markdownOptions));

return getStory(context);
},
wrapper,
});

export const withMarkdownNotes = (text: string, options: any) =>
Expand Down
24 changes: 10 additions & 14 deletions addons/notes/src/register.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import * as React from 'react';
import addons from '@storybook/addons';

import * as PropTypes from 'prop-types';

import styled from '@emotion/styled';

// todo this is going to be refactored after the migration of @storybook/channel to TypeScript
interface NotesChannel {
emit: any;
on(listener: string, callback: (text: string) => void): any;
removeListener(listener: string, callback: (text: string) => void): void;
}
import { Channel } from '@storybook/channels';
import { addons } from '@storybook/addons';

interface NotesApi {
setQueryParams: any; // todo check correct definition
Expand All @@ -20,7 +15,7 @@ interface NotesApi {

interface NotesProps {
active: boolean;
channel: NotesChannel;
channel: Channel;
api: NotesApi;
}

Expand All @@ -35,7 +30,6 @@ const Panel = styled.div({
});

export class Notes extends React.Component<NotesProps, NotesState> {

static propTypes = {
active: PropTypes.bool.isRequired,
channel: PropTypes.shape({
Expand Down Expand Up @@ -91,9 +85,9 @@ export class Notes extends React.Component<NotesProps, NotesState> {
const { text } = this.state;
const textAfterFormatted = text
? text
.trim()
.replace(/(<\S+.*>)\n/g, '$1')
.replace(/\n/g, '<br />')
.trim()
.replace(/(<\S+.*>)\n/g, '$1')
.replace(/\n/g, '<br />')
: '';

return active ? (
Expand All @@ -109,6 +103,8 @@ addons.register('storybook/notes', (api: any) => {
const channel = addons.getChannel();
addons.addPanel('storybook/notes/panel', {
title: 'Notes',
render: ({ active }: { active: boolean }) => <Notes channel={channel} api={api} active={active}/>,
render: ({ active }: { active: boolean }) => (
<Notes channel={channel} api={api} active={active} />
),
});
});
4 changes: 1 addition & 3 deletions addons/notes/src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// Fixes 'noImplicitAny' lint error because @storybook/addons isn't migrated to typescript yet
declare module '@storybook/addons';

// Only necessary for 0.x.x. Version 10.x.x has definition files included
declare module '@emotion/styled';
declare module 'global';
3 changes: 1 addition & 2 deletions addons/notes/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
"rootDir": "./src"
},
"include": [
"src/**/*.ts",
"src/**/*.tsx"
"src/**/*"
],
"exclude": [
"src/__tests__/**/*"
Expand Down
1 change: 0 additions & 1 deletion addons/storyshots/storyshots-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
"devDependencies": {
"@storybook/addon-actions": "4.2.0-alpha.7",
"@storybook/addon-links": "4.2.0-alpha.7",
"@storybook/addons": "4.2.0-alpha.1",
"@storybook/react": "4.2.0-alpha.7",
"enzyme-to-json": "^3.3.4",
"react": "^16.6.0"
Expand Down
7 changes: 5 additions & 2 deletions lib/addons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
"url": "https://github.com/storybooks/storybook.git"
},
"license": "MIT",
"main": "dist/index.js",
"jsnext:main": "src/index.js",
"main": "dist/public_api.js",
"types": "dist/public_api.d.ts",
"scripts": {
"prepare": "node ../../scripts/prepare.js"
},
Expand All @@ -25,6 +25,9 @@
"global": "^4.3.2",
"util-deprecate": "^1.0.2"
},
"devDependencies": {
"@types/util-deprecate": "^1.0.0"
},
"publishConfig": {
"access": "public"
}
Expand Down
88 changes: 0 additions & 88 deletions lib/addons/src/index.js

This file was deleted.

80 changes: 80 additions & 0 deletions lib/addons/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import global from 'global';
import { Channel } from '@storybook/channels';
import { ReactElement } from 'react';

export interface PanelOptions {
active: boolean;
}

export interface Panel {
title: string;

render(options: PanelOptions): ReactElement<any>;
}

export type Loader = (callback: (api: any) => void) => void;

interface LoaderKeyValue {
[key: string]: Loader;
}

interface PanelKeyValue {
[key: string]: Panel;
}

export class AddonStore {
private loaders: LoaderKeyValue = {};
private panels: PanelKeyValue = {};
private channel: Channel | undefined;

getChannel() {
// this.channel should get overwritten by setChannel. If it wasn't called (e.g. in non-browser environment), throw.
if (!this.channel) {
throw new Error(
'Accessing nonexistent addons channel, see https://storybook.js.org/basics/faq/#why-is-there-no-addons-channel'
);
}

return this.channel;
}

hasChannel() {
return !!this.channel;
}

setChannel(channel: Channel) {
this.channel = channel;
}

getPanels() {
return this.panels;
}

addPanel(name: string, panel: Panel) {
this.panels[name] = panel;
}

register(name: string, registerCallback: (api: any) => void) {
this.loaders[name] = registerCallback;
}

loadAddons(api: any) {
Object.values(this.loaders).forEach(value => value(api));
}
}

// Enforce addons store to be a singleton
const KEY = '__STORYBOOK_ADDONS';

function getAddonsStore() {
if (!global[KEY]) {
global[KEY] = new AddonStore();
}
return global[KEY];
}

// Exporting this twice in order to to be able to import it like { addons } instead of 'addons'
// prefer import { addons } from '@storybook/addons' over import addons from '@storybook/addons'
//
// See public_api.ts
export const addons = getAddonsStore();
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import deprecate from 'util-deprecate';
import { makeDecorator } from './make-decorator';
import { defaultDecorateStory } from '../../core/src/client/preview/client_api';
import { makeDecorator, StoryContext } from './make-decorator';

// Copy & paste from internal api: core/client/preview/client_api
export const defaultDecorateStory = (getStory: Function, decorators: Function[]) =>
decorators.reduce(
(decorated, decorator) => (context: StoryContext) =>
decorator(() => decorated(context), context),
getStory
);

jest.mock('util-deprecate');
let deprecatedFns = [];
deprecate.mockImplementation((fn, warning) => {
let deprecatedFns: any[] = [];
(deprecate as any).mockImplementation((fn: (...args: any) => any, warning: string) => {
const deprecatedFn = jest.fn(fn);
deprecatedFns.push({
deprecatedFn,
Expand Down
Loading

0 comments on commit 87c6d93

Please sign in to comment.