storybookjs / addon-designs

A Storybook addon that embeds Figma, websites, or images in the addon panel.
https://storybookjs.github.io/addon-designs
MIT License
876 stars 71 forks source link

Storybook addon-docs and Figma images #179

Closed frassinier closed 1 year ago

frassinier commented 1 year ago

Is your feature request related to a problem? Please describe.

I want to add Figma images, just images, like any other images, into MDX files that are used for documentation purposes (using addon-docs). To do so, I would like to contribute a new docs block. But I need help, please!

The goal is to pass a custom JSX tag into my Storybook docs MDX file and see an image generated by Figma directly.

Describe the solution you'd like

image

Using figma-js.

import React from 'react';
import { FileImageResponse } from 'figma-js';

function getMetadata(url: string) {
    const parsedUrl = new URL(url);
    return {
        projectId: parsedUrl.pathname.split('/')[2],
        nodeId: parsedUrl.searchParams.get('node-id'),
    };
}

const FigmaImage = ({
    src,
    alt = '',
    ...rest
}: React.ImgHTMLAttributes<HTMLImageElement>) => {
    const figma = React.useContext(FigmaContext);

    const [fileImageResponse, setFileImageResponse] = React.useState<FileImageResponse>();

    React.useEffect(() => {
        if ('serviceWorker' in navigator) {
            window.addEventListener('load', () => {
                navigator.serviceWorker.register('/figma-image-sw.js').then(
                    registration => {
                        console.log('[FigmaImage] ServiceWorker registration successful with scope: ', registration.scope);
                    },
                    err => {
                        console.log('[FigmaImage] ServiceWorker registration failed: ', err);
                    },
                );
            });
        } else {
            console.log('[FigmaImage] Service workers are not supported.');
        }
    }, []);

    React.useEffect(() => {
        if (src) {
            const { projectId, nodeId } = getMetadata(src);
            figma
                .fileImages(projectId, {
                    ids: [nodeId || ''],
                })
                .then(({ data }) => setFileImageResponse(data))
                .catch(reason => {
                    console.error(
                        '[FigmaImage] Verify that you configured STORYBOOK_FIGMA_ACCESS_TOKEN correctly!',
                        reason,
                    );
                });
        }
    }, [src, figma]);

    if (!fileImageResponse) {
        return <span>Fetching Figma data...</span>;
    }

    return Object.values(fileImageResponse.images).map((image: string, index: number) => (
        <figure key={index}>
            <img
                src={image}
                alt={alt}
                {...rest}
            />
        </figure>
    ));
};

export { FigmaImage };

where FigmaContext.tsx is

import React from 'react';
import * as Figma from 'figma-js';

const token = process.env.STORYBOOK_FIGMA_ACCESS_TOKEN;

export default React.createContext({
    ...Figma.Client({
        personalAccessToken: token,
    }),
});

and the service worker, figma-image-sw.js :

const CACHE_NAME = 'cache-or-figma';

const FIGMA_BASEURL = 'https://api.figma.com';

self.addEventListener('fetch', event => {
    if (event.request.method === 'GET' && event.request.url.startsWith(FIGMA_BASEURL)) {
        console.info(`[${CACHE_NAME}] The service worker is serving the Figma assets`, event);

        event.respondWith(
            caches.open(CACHE_NAME).then(
                cache =>
                    console.debug(`[${CACHE_NAME}] Get Figma asset from cache`, event.request.url) ||
                    cache.match(event.request).then(match => match || fetch(event.request)),
            ),
        );

        event.waitUntil(
            caches.open(CACHE_NAME).then(cache =>
                fetch(event.request).then(response =>
                    cache.put(event.request, response.clone()).then(
                        () =>
                            console.debug(
                                `[${CACHE_NAME}] Async get Figma assets from network`,
                                event.request.url,
                            ) || response,
                    ),
                ),
            ),
        );
    }
});

Then I would like to add this piece of code to the README of the repo:

3. Add it to docs!

import { Meta } from '@storybook/addon-docs';
import FigmaImage from "storybook-addon-designs/blocks/FigmaImage";

<Meta title="Button" />

# Button

Buttons are interactive elements used by users as clickable actions.

<FigmaImage src="https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File?node-id=12345" />

Describe alternatives you've considered

Don't want to create a new addon for that.

Design types

pocka commented 1 year ago

type: "figspec" already does the job: it fetches design spec as images then renders them.

However considering your requirements, I believe creating a simple React component (FigmaImage in your code example) then importing it from docs page is the simplest and cleanest solution. You do not need an addon for using custom component inside docs pages.

https://storybook.js.org/docs/react/writing-docs/docs-page#with-a-custom-component