Skip to content

Commit eb8269c

Browse files
authored
IBX-10161: Improve twig preview UX (#6)
1 parent 723ad59 commit eb8269c

File tree

4 files changed

+89
-20
lines changed

4 files changed

+89
-20
lines changed

.storybook/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const config = {
6363
},
6464
env: (envConfig) => ({
6565
...envConfig,
66-
TWIG_COMPONENTS_URL: 'http://localhost:8000/storybook/preview',
66+
TWIG_COMPONENTS_BASE_URL: 'http://localhost:8000',
6767
}),
6868
};
6969
export default config;

src/storybook/addons/framework-selector/FrameworkSelectorDecorator.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { renderToString } from 'react-dom/server';
44
import type { Renderer, StoryContext, PartialStoryFn as StoryFunction } from 'storybook/internal/types';
55
import { useArgs, useGlobals } from 'storybook/internal/preview-api';
66

7-
import { FRAMEWORK } from './constants';
7+
import { FRAMEWORK, ROUTES } from './constants';
88

99
type argsType = Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
1010
interface IframeResize {
@@ -23,6 +23,12 @@ type IframeMessageData = IframeResize | IframeMethods;
2323

2424
const getStoryId = (kind: string) => kind.replace('components/src/', '').toLowerCase();
2525
const getIframeSrc = (id: string, args: argsType) => {
26+
const baseUrl = process.env.TWIG_COMPONENTS_BASE_URL;
27+
28+
if (baseUrl === undefined || baseUrl === '') {
29+
throw new Error('TWIG_COMPONENTS_BASE_URL environment variable is not set.');
30+
}
31+
2632
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2733
const storyProperties = Object.entries(args).reduce((accumulator: argsType, [propertyName, propertyValue]: [string, any]) => {
2834
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
@@ -41,10 +47,11 @@ const getIframeSrc = (id: string, args: argsType) => {
4147
/* eslint-enable @typescript-eslint/no-unsafe-assignment */
4248
}, {});
4349
const storyPropertiesStringified = JSON.stringify(storyProperties);
44-
const urlParams = new URLSearchParams({ properties: storyPropertiesStringified });
45-
const twigUrl = `${process.env.TWIG_COMPONENTS_URL ?? ''}/${id}?${urlParams}`;
50+
const previewUrl = new URL(`${ROUTES.PREVIEW}/${id}`, baseUrl);
51+
52+
previewUrl.searchParams.set('properties', storyPropertiesStringified);
4653

47-
return twigUrl;
54+
return previewUrl.toString();
4855
};
4956

5057
// eslint-disable-next-line ibexa/max-lines-per-function-jsx
@@ -55,14 +62,15 @@ const FrameworkSelectorDecorator = (
5562
const [globals] = useGlobals();
5663
const [args] = useArgs();
5764
const iframeRef = useRef<HTMLIFrameElement>(null);
58-
const { title }: { title: string } = context;
65+
const { id, title }: { id: string; title: string } = context;
5966
const renderTwigSelector = () => {
6067
const storyId = getStoryId(title);
6168
const twigUrl = getIframeSrc(storyId, args);
6269

6370
return (
6471
<iframe
65-
id={storyId}
72+
className="twig-preview"
73+
id={`twig-preview-${id}`}
6674
ref={iframeRef}
6775
src={twigUrl}
6876
style={{

src/storybook/addons/framework-selector/components/Tool.tsx

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,71 @@
1-
import React, { memo, useCallback } from 'react';
1+
import React, { memo, useCallback, useEffect, useState } from 'react';
22

33
import { IconButton } from 'storybook/internal/components';
44
import { useGlobals } from 'storybook/internal/manager-api';
55

6-
import { FRAMEWORK, KEY } from '../constants';
6+
import { FRAMEWORK, KEY, ROUTES } from '../constants';
77

88
export const Tool = memo(() => {
99
const [globals, updateGlobals, storyGlobals] = useGlobals();
10+
const [twigEnabled, setTwigEnabled] = useState(false);
1011
const isLocked = KEY in storyGlobals;
12+
const isTwigActive = globals[KEY] === FRAMEWORK.TWIG;
1113
const toggle = useCallback((frameworkId: string) => {
1214
updateGlobals({
1315
[KEY]: frameworkId,
1416
});
1517
}, []);
18+
const openTwigPreview = useCallback(() => {
19+
const storybookIframe = window.document.querySelector<HTMLIFrameElement>('#storybook-preview-iframe');
20+
21+
if (!storybookIframe?.contentWindow?.document) {
22+
return;
23+
}
24+
25+
const twigIframe = storybookIframe.contentWindow.document.querySelector<HTMLIFrameElement>('.twig-preview');
26+
27+
if (!twigIframe) {
28+
return;
29+
}
30+
31+
window.open(twigIframe.src, '_blank');
32+
}, []);
33+
const renderTwigLink = useCallback(() => {
34+
if (!isTwigActive) {
35+
return null;
36+
}
37+
38+
return (
39+
<IconButton
40+
disabled={isLocked}
41+
key="twig-link"
42+
onClick={() => {
43+
openTwigPreview();
44+
}}
45+
title="Open Twig preview"
46+
>
47+
Open Twig preview
48+
</IconButton>
49+
);
50+
}, [isTwigActive, isLocked]);
51+
52+
useEffect(() => {
53+
const baseUrl = process.env.TWIG_COMPONENTS_BASE_URL;
54+
55+
if (baseUrl === undefined || baseUrl === '') {
56+
return;
57+
}
58+
59+
const statusUrl = new URL(ROUTES.STATUS, baseUrl);
60+
61+
fetch(statusUrl)
62+
.then(() => {
63+
setTwigEnabled(true);
64+
})
65+
.catch(() => {
66+
setTwigEnabled(false);
67+
});
68+
}, []);
1669

1770
return (
1871
<>
@@ -27,17 +80,20 @@ export const Tool = memo(() => {
2780
>
2881
React
2982
</IconButton>
30-
<IconButton
31-
active={globals[KEY] === FRAMEWORK.TWIG}
32-
disabled={isLocked}
33-
key={FRAMEWORK.TWIG}
34-
onClick={() => {
35-
toggle(FRAMEWORK.TWIG);
36-
}}
37-
title="Twig"
38-
>
39-
Twig
40-
</IconButton>
83+
{twigEnabled && (
84+
<IconButton
85+
active={isTwigActive}
86+
disabled={isLocked}
87+
key={FRAMEWORK.TWIG}
88+
onClick={() => {
89+
toggle(FRAMEWORK.TWIG);
90+
}}
91+
title="Twig"
92+
>
93+
Twig
94+
</IconButton>
95+
)}
96+
{renderTwigLink()}
4197
</>
4298
);
4399
});

src/storybook/addons/framework-selector/constants.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ export const EVENTS = {
1313
REQUEST: `${ADDON_ID}/request`,
1414
RESULT: `${ADDON_ID}/result`,
1515
};
16+
17+
export const ROUTES = {
18+
PREVIEW: 'storybook/preview',
19+
STATUS: 'storybook/status',
20+
};

0 commit comments

Comments
 (0)