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

UI: refactor Canvas component so we can improve types for PREVIEW addons and TAB addons #23311

Merged
merged 21 commits into from
Jul 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7f518f2
add more jsdoc
ndelangen Jul 4, 2023
a175244
types refactor & cleanup
ndelangen Jul 4, 2023
a51e2ef
fixes
ndelangen Jul 4, 2023
82f3a92
cleaning up types, make a special case for PREVIEW types addons, beca…
ndelangen Jul 5, 2023
ce322de
miniscule improvements in jsdoc/types
ndelangen Jul 5, 2023
fe2ac2f
overhaul addon types a bit more, make type for preview wrapper correc…
ndelangen Jul 5, 2023
fac7c0c
cleanup
ndelangen Jul 5, 2023
d39674f
fixes
ndelangen Jul 5, 2023
343f7ad
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 5, 2023
cc39db8
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 6, 2023
ae3a05b
Merge branch 'norbert/addon-types-follow-up' of github.com:storybookj…
ndelangen Jul 6, 2023
3b6b8aa
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 6, 2023
cef21fc
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 6, 2023
c863eb7
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 6, 2023
4b705f6
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 7, 2023
0e572a2
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 10, 2023
7953660
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 11, 2023
5acde6d
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 11, 2023
8573eb1
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 12, 2023
6382df6
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 12, 2023
a80b1ff
Merge branch 'norbert/page-addons-refactor' into norbert/addon-types-…
ndelangen Jul 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 7 additions & 6 deletions code/addons/a11y/src/manager.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as api from '@storybook/manager-api';
import type { Addon_BaseType } from '@storybook/types';
import { PANEL_ID } from './constants';
import './manager';

Expand All @@ -8,6 +9,8 @@ mockedApi.useAddonState = jest.fn();
const mockedAddons = api.addons as jest.Mocked<typeof api.addons>;
const registrationImpl = mockedAddons.register.mock.calls[0][1];

const isPanel = (input: Parameters<typeof mockedAddons.add>[1]): input is Addon_BaseType =>
input.type === api.types.PANEL;
describe('A11yManager', () => {
it('should register the panels', () => {
// when
Expand All @@ -31,9 +34,8 @@ describe('A11yManager', () => {
// given
mockedApi.useAddonState.mockImplementation(() => [undefined]);
registrationImpl(api as unknown as api.API);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === api.types.PANEL)?.title as Function;
const title = mockedAddons.add.mock.calls.map(([_, def]) => def).find(isPanel)
?.title as Function;

// when / then
expect(title()).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -66,9 +68,8 @@ describe('A11yManager', () => {
},
]);
registrationImpl(mockedApi);
const title = mockedAddons.add.mock.calls
.map(([_, def]) => def)
.find(({ type }) => type === api.types.PANEL)?.title as Function;
const title = mockedAddons.add.mock.calls.map(([_, def]) => def).find(isPanel)
?.title as Function;

// when / then
expect(title()).toMatchInlineSnapshot(`
Expand Down
11 changes: 7 additions & 4 deletions code/lib/manager-api/src/lib/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import type {
Addon_BaseType,
Addon_PageType,
Addon_Types,
Addon_TypesMapping,
Addon_WrapperType,
} from '@storybook/types';
import { Addon_TypesEnum } from '@storybook/types';
import { logger } from '@storybook/client-logger';
Expand Down Expand Up @@ -97,9 +99,7 @@ export class AddonStore {

getElements<T extends Addon_Types | Addon_TypesEnum.experimental_PAGE>(
type: T
): T extends Addon_TypesEnum.experimental_PAGE
? Addon_Collection<Addon_PageType>
: Addon_Collection<Addon_BaseType> {
): Addon_Collection<Addon_TypesMapping[T]> {
if (!this.elements[type]) {
this.elements[type] = {};
}
Expand Down Expand Up @@ -139,7 +139,10 @@ export class AddonStore {
*/
add(
id: string,
addon: Addon_BaseType | (Omit<Addon_PageType, 'id'> & DeprecatedAddonWithId)
addon:
| Addon_BaseType
| (Omit<Addon_PageType, 'id'> & DeprecatedAddonWithId)
| (Omit<Addon_WrapperType, 'id'> & DeprecatedAddonWithId)
): void {
const { type } = addon;
const collection = this.getElements(type);
Expand Down
8 changes: 3 additions & 5 deletions code/lib/manager-api/src/modules/addons.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {
Addon_BaseType,
Addon_Collection,
Addon_PageType,
Addon_Types,
Addon_TypesMapping,
API_Panels,
API_StateMerger,
} from '@storybook/types';
Expand All @@ -25,9 +25,7 @@ export interface SubAPI {
*/
getElements: <T extends Addon_Types | Addon_TypesEnum.experimental_PAGE = Addon_Types>(
type: T
) => T extends Addon_TypesEnum.experimental_PAGE
? Addon_Collection<Addon_PageType>
: Addon_Collection<Addon_BaseType>;
) => Addon_Collection<Addon_TypesMapping[T]>;
/**
* Returns a collection of all panels.
* This is the same as calling getElements('panel')
Expand Down Expand Up @@ -58,7 +56,7 @@ export interface SubAPI {
* Sets the state of an addon with the given ID.
* @template S - The type of the addon state.
* @param {string} addonId - The ID of the addon to set the state for.
* @param {S | API_StateMerger<S>} newStateOrMerger - The new state to set, or a function that merges the current state with the new state.
* @param {S | API_StateMerger<S>} newStateOrMerger - The new state to set, or a function which receives the current state and returns the new state.
* @param {Options} [options] - Optional options for the state update.
* @deprecated This API might get dropped, if you are using this, please file an issue.
* @returns {Promise<S>} - A promise that resolves with the new state after it has been set.
Expand Down
12 changes: 6 additions & 6 deletions code/lib/manager-api/src/modules/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
StoryPreparedPayload,
DocsPreparedPayload,
API_DocsEntry,
API_ViewMode,
API_StatusState,
API_StatusUpdate,
} from '@storybook/types';
Expand Down Expand Up @@ -60,7 +61,6 @@ const STORY_INDEX_PATH = './index.json';
type Direction = -1 | 1;
type ParameterName = string;

type ViewMode = 'story' | 'info' | 'settings' | string | undefined;
type StoryUpdate = Partial<
Pick<API_StoryEntry, 'prepared' | 'parameters' | 'initialArgs' | 'argTypes' | 'args'>
>;
Expand All @@ -69,7 +69,7 @@ type DocsUpdate = Partial<Pick<API_DocsEntry, 'prepared' | 'parameters'>>;

export interface SubState extends API_LoadedRefData {
storyId: StoryId;
viewMode: ViewMode;
viewMode: API_ViewMode;
status: API_StatusState;
}

Expand Down Expand Up @@ -102,13 +102,13 @@ export interface SubAPI {
* @param {StoryId} [story] - The ID of the story to select.
* @param {Object} [obj] - An optional object containing additional options.
* @param {string} [obj.ref] - The ref ID of the story to select.
* @param {ViewMode} [obj.viewMode] - The view mode to display the story in.
* @param {API_ViewMode} [obj.viewMode] - The view mode to display the story in.
* @returns {void}
*/
selectStory: (
kindOrId?: string,
story?: StoryId,
obj?: { ref?: string; viewMode?: ViewMode }
obj?: { ref?: string; viewMode?: API_ViewMode }
) => void;
/**
* Returns the current story's data, including its ID, kind, name, and parameters.
Expand Down Expand Up @@ -588,7 +588,7 @@ export const init: ModuleFn<SubAPI, SubState, true> = ({
viewMode,
}: {
storyId: string;
viewMode: ViewMode;
viewMode: API_ViewMode;
[k: string]: any;
}) {
const { sourceType } = getEventMetadata(this, fullAPI);
Expand Down Expand Up @@ -714,7 +714,7 @@ export const init: ModuleFn<SubAPI, SubState, true> = ({
story?: StoryName;
name?: StoryName;
storyId: string;
viewMode: ViewMode;
viewMode: API_ViewMode;
}) {
const { ref } = getEventMetadata(this, fullAPI);

Expand Down
98 changes: 90 additions & 8 deletions code/lib/types/src/modules/addons.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/* eslint-disable @typescript-eslint/naming-convention */

import type { ReactElement, ReactNode, ValidationMap, WeakValidationMap } from 'react';
import type {
FC,
PropsWithChildren,
ReactElement,
ReactNode,
ValidationMap,
WeakValidationMap,
} from 'react';
import type { RenderData as RouterData } from '../../../router/src/types';
import type { ThemeVars } from '../../../theming/src/types';
import type {
Expand Down Expand Up @@ -299,8 +306,8 @@ export type BaseStory<TArgs, StoryFnReturnType> =
| Addon_BaseStoryObject<TArgs, StoryFnReturnType>;

export interface Addon_RenderOptions {
active?: boolean;
key?: string;
active: boolean;
key: string;
}

/**
Expand All @@ -312,16 +319,59 @@ export type ReactJSXElement = {
key: any;
};

export type Addon_Type = Addon_BaseType | Addon_PageType;
export type Addon_Type = Addon_BaseType | Addon_PageType | Addon_WrapperType;
export interface Addon_BaseType {
/**
* The title of the addon.
* This can be a simple string, but it can also be a React.FunctionComponent or a React.ReactElement.
*/
title: FCWithoutChildren | ReactNode;
type: Addon_Types;
/**
* The type of the addon.
* @example Addon_TypesEnum.PANEL
*/
type: Exclude<Addon_Types, Addon_TypesEnum.PREVIEW>;
/**
* The unique id of the addon.
* @warn This will become non-optional in 8.0
*
* This needs to be globally unique, so we recommend prefixing it with your org name or npm package name.
*
* Do not prefix with `storybook`, this is reserved for core storybook feature and core addons.
*
* @example 'my-org-name/my-addon-name'
*/
id?: string;
/**
* This component will wrap your `render` function.
*
* With it you can determine if you want your addon to be rendered or not.
*
* This is to facilitate addons keeping state, and keep listening for events even when they are not currently on screen/rendered.
*/
route?: (routeOptions: RouterData) => string;
/**
* This will determine the value of `active` prop of your render function.
*/
match?: (matchOptions: RouterData) => boolean;
render: (renderOptions: Addon_RenderOptions) => any | null;
/**
* The actual contents of your addon.
*
* This is called as a function, so if you want to use hooks,
* your function needs to return a JSX.Element within which components are rendered
*/
render: (renderOptions: Partial<Addon_RenderOptions>) => ReactElement<any, any> | null;
/**
* @unstable
*/
paramKey?: string;
/**
* @unstable
*/
disabled?: boolean;
/**
* @unstable
*/
hidden?: boolean;
}

Expand Down Expand Up @@ -372,6 +422,38 @@ export interface Addon_PageType {
render: FCWithoutChildren;
}

export interface Addon_WrapperType {
type: Addon_TypesEnum.PREVIEW;
/**
* The unique id of the page.
*/
id: string;
/**
* A React.FunctionComponent that wraps the story.
*
* This component must accept a children prop, and render it.
*/
render: FC<
PropsWithChildren<{
index: number;
children: ReactNode;
id: string;
storyId: StoryId;
active: boolean;
}>
>;
}

type Addon_TypeBaseNames = Exclude<
Addon_TypesEnum,
Addon_TypesEnum.PREVIEW | Addon_TypesEnum.experimental_PAGE
>;

export interface Addon_TypesMapping extends Record<Addon_TypeBaseNames, Addon_BaseType> {
[Addon_TypesEnum.PREVIEW]: Addon_WrapperType;
[Addon_TypesEnum.experimental_PAGE]: Addon_PageType;
}

export type Addon_Loader<API> = (api: API) => void;

export interface Addon_Loaders<API> {
Expand Down Expand Up @@ -405,7 +487,7 @@ export enum Addon_TypesEnum {
*/
PANEL = 'panel',
/**
* This adds items in the toolbar above the canvas - on the right side.
* This adds items in the toolbar above the canvas - on the left side.
*/
TOOL = 'tool',
/**
Expand All @@ -414,7 +496,7 @@ export enum Addon_TypesEnum {
TOOLEXTRA = 'toolextra',
/**
* This adds wrapper components around the canvas/iframe component storybook renders.
* @unstable
* @unstable this API is not stable yet, and is likely to change in 8.0.
*/
PREVIEW = 'preview',
/**
Expand Down