@storybook/addon-designs
Version:
Storybook addon for embedding your design preview in addon panel
74 lines (69 loc) • 11.2 kB
JavaScript
import {lazy,Component,Fragment,useState,useEffect,Suspense,useMemo,useCallback}from'react';import {css,jsx}from'storybook/theming';import {Placeholder,Link,FlexBar,Separator,Tabs,IconButton}from'storybook/internal/components';import {FigspecFileViewer,FigspecFrameViewer}from'@figspec/react';import {addons,types,useStorybookState,useParameter}from'storybook/manager-api';import {ZoomIcon,ZoomOutIcon,ZoomResetIcon}from'@storybook/icons';var Ce=Object.defineProperty;var D=(e,o)=>()=>(e&&(o=e(e=0)),o);var ke=(e,o)=>{for(var r in o)Ce(e,r,{get:o[r],enumerable:true});};var w,Re,Le,Ae,A=D(()=>{w=({config:e,defer:o=false})=>{let[r,t]=useState(o?void 0:e.url),[n,a]=useState(false);return useEffect(()=>{if(!o)return;let i=requestAnimationFrame(()=>{t(e.url);});return ()=>cancelAnimationFrame(i)},[o,e.url]),useEffect(()=>{a(false);},[r]),jsx("div",{css:Re},!n&&jsx(Placeholder,{css:Le},"Loading..."),jsx("iframe",{css:Ae,src:r,allowFullScreen:e.allowFullscreen,onLoad:()=>a(true)}))},Re=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
`,Le=css`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`,Ae=css`
position: relative;
width: 100%;
height: 100%;
border: none;
z-index: 1;
`;});var $,De,q,Z=D(()=>{A();$=/https:\/\/[\w.-]+\.?figma.com\/([\w-]+)\/([0-9a-zA-Z]{22,128})(?:\/.*)?$/,De=e=>$.test(e),q=({config:e})=>{let o=useMemo(()=>De(e.url)?{url:`https://www.figma.com/embed?embed_host=${e.embedHost||location.hostname}&url=${e.url}`,allowFullscreen:e.allowFullscreen}:(console.warn(`[storybook-addon-designs] The URL you specified is not valid Figma URL.
The addon fallbacks to normal iframe mode.For more detail, please check <https://www.figma.com/developers/embed>.`),e),[e.url,e.allowFullscreen,e.embedHost]);return jsx(w,{defer:true,config:o})};});var fe={};ke(fe,{Figspec:()=>pe,default:()=>go});function M(e){return e.status!==200?Promise.reject(e.statusText):e.json()}function uo(e){if(e.accessToken)return e.accessToken;try{return process.env.STORYBOOK_FIGMA_ACCESS_TOKEN??null}catch{return null}}function de(e){return "absoluteBoundingBox"in e?[e]:!e.children||e.children.length===0?[]:e.children.map(de).reduce((o,r)=>o.concat(r),[])}var le,pe,go,ue=D(()=>{Z();le=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
`;pe=({config:e})=>{let[o,r]=useState({state:"loading"}),t=async n=>{r({state:"loading"});try{let a=e.url.match($);if(!a)throw new Error(e.url+" is not a valid Figma URL.");let[,,i]=a,g=new URL(e.url).searchParams.get("node-id"),v=uo(e);if(!v)throw new Error("Personal Access Token is required.");let m={"X-FIGMA-TOKEN":v},l=new URL(`https://api.figma.com/v1/files/${i}`),s=new URL(`https://api.figma.com/v1/images/${i}`);if(s.searchParams.set("format","svg"),!g){let b=await fetch(l.href,{headers:m,signal:n}).then(T=>M(T)),ye=de(b.document);s.searchParams.set("ids",ye.map(T=>T.id).join(","));let we=await fetch(s.href,{headers:m,signal:n}).then(T=>M(T));r({state:"fetched",value:{type:"file",props:{documentNode:b,renderedImages:we.images,link:e.url}}});return}l.pathname+="/nodes",l.searchParams.set("ids",g),s.searchParams.set("ids",g);let[f,S]=await Promise.all([fetch(l.href,{headers:m,signal:n}).then(b=>M(b)),fetch(s.href,{headers:m,signal:n}).then(b=>M(b))]);r({state:"fetched",value:{type:"frame",props:{nodes:f,renderedImage:Object.values(S.images)[0],link:e.url}}});}catch(a){if(a instanceof DOMException&&a.code===DOMException.ABORT_ERR)return;console.error(a),r({state:"failed",error:a instanceof Error?a.message:String(a)});}};switch(useEffect(()=>{let n=false,a=()=>{n=true;},i=new AbortController;return t(i.signal).then(a,a),()=>{n||i.abort();}},[e.url]),o.state){case "loading":return jsx(Placeholder,null,jsx(Fragment,null,"Loading Figma file..."));case "failed":return jsx(Placeholder,null,jsx(Fragment,null,"Failed to load Figma file"),jsx(Fragment,null,o.error));case "fetched":return o.value.type==="file"?jsx(FigspecFileViewer,{css:le,...o.value.props}):jsx(FigspecFrameViewer,{css:le,...o.value.props})}},go=pe;});var L="STORYBOOK_ADDON_DESIGNS",U=L+"/panel",y="design";var E=class extends Component{state={hasError:false};static getDerivedStateFromError(o){return {hasError:true,error:o}}componentDidCatch(o,r){console.group("An error occurred during rendering Addon panel of storybook-addon-designs"),console.log("--- Error ---"),console.error(o),console.log("--- React Component Stack ---"),console.error(r.componentStack),console.groupEnd();}render(){return this.state.hasError?jsx(Placeholder,null,jsx(Fragment,null,"Failed to render addon UI"),jsx(Fragment,null,jsx("p",null,"Sorry, this addon has crashed due to the below error has thrown during rendering the addon UI."),jsx("pre",null,String(this.state.error)),jsx("p",null,"See console log for more details. To clear the error state, please reload the page."," ",jsx(Link,{href:"https://github.com/storybookjs/addon-designs/issues/new?assignees=&labels=category%3A+bug&template=bug_report.yml",target:"_blank",rel:"noopener",withArrow:true,cancel:false},"Bug report")))):this.props.children}};Z();A();var Q=(e,o)=>{let[r,t]=useState([0,0]),[n,a]=useState(false),i=useCallback(s=>{s.button===0&&(t([s.screenX,s.screenY]),a(true));},[a,t]),d=useCallback(s=>{let f=s.touches[0];t([f.screenX,f.screenY]),a(true);},[a,t]),g=useCallback(s=>{n&&t(f=>(e([s[0]-f[0],s[1]-f[1]]),s));},[t,n,...o]),v=useCallback(s=>{let{screenX:f,screenY:S}=s;g([f,S]);},[g]),m=useCallback(s=>{let{screenX:f,screenY:S}=s.touches[0];g([f,S]);},[t,n,...o]),l=useCallback(()=>{t([0,0]),a(false);},[a,t]);return {onMouseDown:i,onMouseMove:v,onMouseUp:l,onMouseLeave:l,onTouchStart:d,onTouchMove:m,onTouchCancel:l,onTouchEnd:l}};var ee=({children:e,className:o,style:r,defaultValue:t,value:n,onChange:a})=>{let[i,d]=useState([0,0]);useEffect(()=>{d(t||n||[0,0]);},[t]);let g=Q(m=>{a&&a(m),d(l=>[l[0]+m[0],l[1]+m[1]]);},[d,a]),v=useMemo(()=>{let m=n||i;return {transform:`translate(${m[0]}px, ${m[1]}px)`}},[n,i]);return jsx("div",{css:$e,className:o,style:r,...g},jsx("div",{css:Ze,style:v},e))};var $e=css`
position: relative;
overflow: hidden;
&:active {
cursor: move;
}
`,Ze=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
`;var oe=({onZoomIn:e,onZoomOut:o,onReset:r})=>jsx(Fragment,null,jsx(IconButton,{onClick:e},jsx(ZoomIcon,null)),jsx(IconButton,{onClick:o},jsx(ZoomOutIcon,null)),jsx(IconButton,{onClick:r},jsx(ZoomResetIcon,null)));var re=(e,o)=>{let[r,t]=useState(1);useEffect(()=>{t(e);},o);let n=useCallback(()=>{t(d=>d+.1);},[t]),a=useCallback(()=>{t(d=>Math.max(d-.1,.1));},[t]),i=useCallback(()=>{t(1);},[t]);return {scale:r,zoomIn:n,zoomOut:a,resetZoom:i}};var te=({config:e})=>{let o=re(e.scale||1,[e.scale]),r=useMemo(()=>({transform:`scale(${o.scale})`}),[o.scale]);return jsx("div",{css:Je},jsx(FlexBar,{border:true},jsx("div",{style:{display:"grid",gridAutoFlow:"column",gap:"4px",alignItems:"center"}},jsx(Fragment,{key:"left"},jsx("p",null,jsx("b",null,"Image")),jsx(Separator,null),jsx(oe,{onReset:o.resetZoom,onZoomIn:o.zoomIn,onZoomOut:o.zoomOut})))),jsx(ee,{css:Qe,defaultValue:e.offset},jsx("img",{css:xe,src:e.url,style:r})))};var Je=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
flex-direction: column;
align-items: stretch;
`,Qe=css`
flex-grow: 1;
`,xe=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
pointer-events: none;
border-radius: 1px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.15);
`;var ae=({config:e})=>jsx("div",{css:oo},jsx(Link,{cancel:false,href:e.url,target:e.target??"_blank",rel:e.rel??"noopener",withArrow:e.showArrow??true},e.label||e.url));var oo=css`
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
`;A();var no=e=>{if(e.protocol!=="https:")return {valid:false,error:jsx(Fragment,null,"Expected HTTPS link, received ",jsx("code",null,e.protocol),".")};if(e.hostname!=="www.sketch.com")return {valid:false,error:jsx(Fragment,null,"Expected a hostname ",jsx("code",null,"www.sketch.com"),", received"," ",jsx("code",null,e.hostname))};let o=jsx(Fragment,null,"Expected pathname ",jsx("code",null,"/s/<string>/a/<string>"),", received"," ",jsx("code",null,e.pathname),"."),r=e.pathname.split("/").slice(1);if(r.length<4)return {valid:false,error:o};if(r[0]==="embed")return {valid:true,data:{url:e.href,offscreen:false}};let[t,n,a,i]=r;return t!=="s"||!n||a!=="a"||!i?{valid:false,error:o}:{valid:true,data:{url:`https://www.sketch.com/embed/s/${n}/a/${i}`,offscreen:false}}},se=({config:e})=>{let o=useMemo(()=>{let r=no(new URL(e.url));return r.valid?{...r,data:{...e,...r.data}}:r},[e]);return o.valid?jsx(w,{defer:true,config:o.data}):jsx(Placeholder,null,jsx(Fragment,null,"Invalid Sketch URL"),jsx(Fragment,null,o.error))};var ce=({tabs:e,deps:o=[]})=>{let[r,t]=useState(e[0].id);return useEffect(()=>{t(e[0].id);},o),jsx(Tabs,{absolute:true,selected:r,actions:{onSelect:t}},e.map(n=>jsx("div",{key:n.id,id:n.id,title:n.name},n.offscreen||r===n.id?n.content:null)))};var Fo=lazy(()=>Promise.resolve().then(()=>(ue(),fe))),Pe=({config:e})=>{if(!e||"length"in e&&e.length===0)return jsx(Placeholder,null,jsx(Fragment,null,"No designs found"),jsx(Fragment,null,"Learn how to"," ",jsx(Link,{href:"https://github.com/storybookjs/addon-designs#3-add-it-to-story",target:"_blank",rel:"noopener",withArrow:true,cancel:false},"display design preview for the story")));let o=[...e instanceof Array?e:[e]].map(r=>{let t={id:JSON.stringify(r),name:r.name||r.type?.toUpperCase()||"ERROR",offscreen:r.offscreen??true};switch(r.type){case "iframe":return {...t,content:jsx(w,{config:r})};case "figma":return {...t,content:jsx(q,{config:r}),offscreen:false};case "sketch":return {...t,content:jsx(se,{config:r})};case "figspec":case "experimental-figspec":return r.type==="experimental-figspec"&&console.warn("[storybook-addon-designs] `experimental-figspec` is deprecated. We will remove it in v7.0. Please replace it to `figspec` type."),{...t,content:jsx(Suspense,{fallback:"Preparing Figspec viewer..."},jsx(Fo,{config:r})),offscreen:false};case "image":return {...t,content:jsx(te,{config:r})};case "link":return {...t,content:jsx(ae,{config:r})}}return {...t,content:jsx(Placeholder,null,jsx(Fragment,null,"Invalid config type"),jsx(Fragment,null,"Config type you set is not supported. Please choose one from"," ",jsx(Link,{href:"https://github.com/storybookjs/addon-designs/blob/master/packages/storybook-addon-designs/src/config.ts",target:"_blank",rel:"noopener",withArrow:true,cancel:false},"available config types")))}});return o.length===1?jsx("div",null,o[0].content):jsx(ce,{tabs:o,deps:[e]})};var K=({active:e})=>{let o=useStorybookState(),r=useParameter(y),[t,n]=useState(e);return useEffect(()=>{n(e);},[r]),useEffect(()=>{e&&n(true);},[e]),t?jsx(Pe,{key:o.storyId,config:r}):null};var R="Design";function be(e){addons.register(L,o=>{addons.add(U,{title:R,render({active:t}){return t?jsx(E,null,jsx(K,{active:true})):jsx("noscript",null)},type:types.TAB,paramKey:y});});}be();//# sourceMappingURL=register-tab.mjs.map
//# sourceMappingURL=register-tab.mjs.map