Skip to content

Commit

Permalink
Merge pull request #91 from lifeiscontent/feat/storybook-7-compat
Browse files Browse the repository at this point in the history
  • Loading branch information
lifeiscontent authored Mar 27, 2023
2 parents e233f98 + 8bf52c2 commit 6bb0673
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 44 deletions.
10 changes: 7 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export const ADDON_ID = 'addon-apolloClient';
export const PARAM_KEY = 'apolloClient';
export const NAME = 'ApolloClient';
export const ADDON_ID = 'apollo-client' as const;
export const PANEL_ID = `${ADDON_ID}/panel` as const;
export const PARAM_KEY = 'apolloClient' as const;
export const EVENTS = {
REQUEST: `${ADDON_ID}/REQUEST`,
RESULT: `${ADDON_ID}/RESULT`,
} as const;
16 changes: 8 additions & 8 deletions src/decorators.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC } from 'react';
import { PARAM_KEY, ADDON_ID } from './constants';
import { useGlobals, useEffect, useParameter } from '@storybook/addons';
import { EVENTS, PARAM_KEY } from './constants';
import { useParameter, useChannel, useEffect } from '@storybook/addons';
import { print } from 'graphql';
import type { Parameters } from './types';

Expand All @@ -9,14 +9,14 @@ export const WithApolloClient = (Story: FC<unknown>): JSX.Element => {
Partial<Parameters>
>(PARAM_KEY, {}) as Partial<Parameters>;
const { mocks = [] } = providerProps ?? {};
const [, setGlobals] = useGlobals();

const emit = useChannel({}, []);
useEffect(() => {
setGlobals({
[`${ADDON_ID}/queries`]: mocks.map((mock) => print(mock.request.query)),
emit(EVENTS.RESULT, {
activeIndex: mocks.length ? 0 : -1,
mocks,
queries: mocks.map((mock) => print(mock.request.query)),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [emit, mocks]);

if (!MockedProvider) {
console.warn(
Expand Down
45 changes: 25 additions & 20 deletions src/panel.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { Fragment, useState } from 'react';
import { useGlobals, useParameter } from '@storybook/api';
import React, { Fragment, useEffect, useState } from 'react';

import {
Form as _Form,
Placeholder as _Placeholder,
SyntaxHighlighter as _SyntaxHighlighter,
TabsState,
} from '@storybook/components';
import { PARAM_KEY, ADDON_ID } from './constants';
import { MockedResponse, Parameters } from './types';
import { MockedResponse } from './types';
import { OperationDefinitionNode } from 'graphql';
import { setStore, store, subscribeStore } from './store';

const Form: {
Field: React.FC<
Expand All @@ -21,7 +21,11 @@ const Form: {
const Placeholder: React.FC<
React.ComponentProps<typeof _Placeholder> & { children: React.ReactNode }
> = _Placeholder;
const SyntaxHighlighter: React.FC<React.ComponentProps<typeof _SyntaxHighlighter> & { children: React.ReactNode }> = _SyntaxHighlighter;
const SyntaxHighlighter: React.FC<
React.ComponentProps<typeof _SyntaxHighlighter> & {
children: React.ReactNode;
}
> = _SyntaxHighlighter;

const getOperationName = (mockedResponse: MockedResponse): string => {
if (mockedResponse.request.operationName) {
Expand All @@ -41,32 +45,33 @@ const getOperationName = (mockedResponse: MockedResponse): string => {
};

export const ApolloClientPanel: React.FC = () => {
const [globals] = useGlobals();

const queries = globals[`${ADDON_ID}/queries`] ?? [];

const { mocks = [] } = useParameter<Partial<Parameters>>(
PARAM_KEY,
{}
) as Partial<Parameters>;
const [activeMockIndex, setActiveMockIndex] = useState<number>(() =>
mocks.length ? 0 : -1
const [{ mocks, activeIndex, queries }, _setState] = useState(() => store);
useEffect(
() =>
subscribeStore((update) => {
_setState(update);
}),
[]
);

if (mocks.length === 0) {
return <Placeholder>No mocks for this story</Placeholder>;
}

const mockedResponse = mocks[activeMockIndex];
const query = queries[activeMockIndex];
const mockedResponse = mocks[activeIndex];
const query = queries[activeIndex];

return (
<Fragment key={activeMockIndex}>
<Fragment key={query}>
<Form.Field label="Mocks">
<Form.Select
value={activeMockIndex}
value={activeIndex}
disabled={activeIndex === -1}
onChange={(event) =>
setActiveMockIndex(Number(event.currentTarget.value))
setStore((prev) => ({
...prev,
activeIndex: Number(event.currentTarget.value),
}))
}
size="auto"
>
Expand Down
16 changes: 10 additions & 6 deletions src/register.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
import { addons, RenderOptions, types } from '@storybook/addons';
import { addons, types } from '@storybook/addons';
import { AddonPanel } from '@storybook/components';
import { ApolloClientPanel } from './panel';
import { ADDON_ID, PARAM_KEY } from './constants';
import { ADDON_ID, EVENTS, PANEL_ID, PARAM_KEY } from './constants';
import { getTitle } from './title';
import { setStore } from './store';

addons.register(ADDON_ID, (api) => {
addons.add(ADDON_ID, {
const channel = api.getChannel();
channel.addListener(EVENTS.RESULT, setStore);
addons.add(PANEL_ID, {
paramKey: PARAM_KEY,
render({ active = false, key }: RenderOptions) {
title: getTitle,
type: types.PANEL,
match: ({ viewMode }) => viewMode === 'story',
render({ active = false, key }) {
return (
<AddonPanel key={key} active={active}>
{!active || !api.getCurrentStoryData() ? null : <ApolloClientPanel />}
</AddonPanel>
);
},
title: getTitle,
type: types.PANEL,
});
});
33 changes: 33 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { MockedResponse } from './types';

export let store: {
queries: string[];
mocks: MockedResponse[];
activeIndex: number;
} = {
queries: [],
mocks: [],
activeIndex: -1,
};

type Listener = (updatedStore: typeof store) => void;

let subscribers: Listener[] = [];

export const setStore = (
nextStore: Partial<typeof store> | ((prevStore: typeof store) => typeof store)
) => {
store =
nextStore instanceof Function
? nextStore(store)
: { ...store, ...nextStore };
subscribers.forEach((x) => x(store));
};

export const subscribeStore = (subscriber: Listener) => {
subscribers.push(subscriber);
subscriber(store);
return () => {
subscribers = subscribers.filter((x) => x === subscriber);
};
};
8 changes: 4 additions & 4 deletions src/title.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { PARAM_KEY } from './constants';

export function getTitle(): string {
// eslint-disable-next-line react-hooks/rules-of-hooks
const params = useParameter<Parameters>(PARAM_KEY);
const { mocks = [] } = useParameter<Parameters>(PARAM_KEY, {
mocks: [],
});

return params?.mocks?.length
? `Apollo Client (${params.mocks.length})`
: 'Apollo Client (0)';
return mocks.length ? `Apollo Client (${mocks.length})` : 'Apollo Client (0)';
}
6 changes: 3 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { DocumentNode } from "graphql";
import { DocumentNode } from 'graphql';

export interface MockedProviderProps {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
mocks?: ReadonlyArray<MockedResponse>;
mocks?: MockedResponse[];
children?: React.ReactNode;
}

export type MockedProvider = React.FC<MockedProviderProps>;

export interface Parameters extends MockedProviderProps {
MockedProvider: MockedProvider;
MockedProvider?: MockedProvider;
}

export interface MockedResponse {
Expand Down

0 comments on commit 6bb0673

Please sign in to comment.