storybook-addon-grid
Version:
Column guides for your stories
186 lines (178 loc) • 5.98 kB
JavaScript
// src/manager.tsx
import { addons, types } from "storybook/manager-api";
// src/constants.ts
var ADDON_ID = "storybook-addon-grid";
var PARAM_KEY = "grid";
// src/Tool.tsx
import React3 from "react";
import {
useAddonState as useAddonState2,
useParameter as useParameter2,
useStorybookApi
} from "storybook/manager-api";
import { IconButton } from "storybook/internal/components";
import { STORY_RENDERED } from "storybook/internal/core-events";
// src/Grids.tsx
import React2 from "react";
import { createPortal } from "react-dom";
import { useAddonState, useParameter } from "storybook/manager-api";
import { CacheProvider, createCache } from "storybook/theming";
// src/ui.tsx
import React from "react";
import { Global, styled } from "storybook/theming";
var MAX_COLUMNS = 24;
var Wrapper = styled.div({
position: "relative",
zIndex: 1,
opacity: 0,
pointerEvents: "none",
'&[data-visible="true"]': {
opacity: 1
}
});
var Grid = styled.div(
({ gap, gutter, maxWidth, columns }) => {
let gutterRight = "0", gutterLeft = "0";
if (Array.isArray(gutter)) {
[gutterLeft, gutterRight] = gutter;
} else if (gutter != null) {
gutterLeft = gutterRight = gutter;
}
return {
position: "fixed",
inset: "0",
display: "grid",
gridTemplateColumns: `repeat(min(${columns}, ${MAX_COLUMNS}), 1fr)`,
gridTemplateRows: "100%",
gridColumnGap: gap,
width: "100%",
height: "100%",
margin: "0 auto",
maxWidth,
padding: `0 ${gutterRight} 0 ${gutterLeft}`,
boxSizing: "border-box"
};
}
);
var Column = styled.div(({ color }) => ({
width: "100%",
height: "100%",
backgroundColor: color
}));
function Grids({
visible,
columns = 12,
gap = "20px",
color = "rgba(255, 0, 0, 0.1)",
gutter = "50px",
maxWidth = "1024px"
}) {
let columnDivs = React.useMemo(
() => Array.from({
length: typeof columns === "number" ? columns : MAX_COLUMNS
}).map((_, index) => <Column key={index} color={color} />),
[columns, color]
);
let gridNodes = <Grid gap={gap} gutter={gutter} maxWidth={maxWidth} columns={columns}>{columnDivs}</Grid>;
return <>
<Global styles={{
[`#root`]: {
position: "relative",
zIndex: 0
}
}} />
<Wrapper data-addon-id={ADDON_ID} data-visible={visible}>{gridNodes}</Wrapper>
</>;
}
// src/Grids.tsx
function ManagerRenderedGrids() {
let { columns, gap, color, gutter, maxWidth, disable } = useParameter(PARAM_KEY, {});
let [state] = useAddonState(ADDON_ID);
return <Grids columns={columns} color={color} gap={gap} visible={disable != null ? !disable : state?.visible ?? false} gutter={gutter} maxWidth={maxWidth} />;
}
var styleCache = /* @__PURE__ */ new WeakMap();
var ManagerRenderedGridsContainer = React2.memo(
function ManagerRenderedGridsContainer2() {
let previewIframe = document.querySelector(
"#storybook-preview-iframe"
);
if (!previewIframe)
return null;
let iframeDocument = previewIframe.contentWindow?.document;
if (!iframeDocument)
return null;
let head = iframeDocument.head;
if (!head || !iframeDocument.body)
return null;
if (!styleCache.has(head))
styleCache.set(
head,
createCache({
key: ADDON_ID,
container: head
})
);
return createPortal(
<CacheProvider value={styleCache.get(head)}><ManagerRenderedGrids /></CacheProvider>,
iframeDocument.body
);
}
);
// src/Tool.tsx
var shortcut = ["control", "G"];
var Tool = React3.memo(ToolComponent);
function ToolComponent() {
let parameters = useParameter2(PARAM_KEY, {});
let [state, setState] = useAddonState2(ADDON_ID, {
visible: false
});
let [ready, setReady] = React3.useState(false);
let api = useStorybookApi();
let toggleGrid = React3.useCallback(() => {
setState(
(prev) => ({
visible: !prev?.visible || false
})
);
}, []);
React3.useEffect(() => {
api.setAddonShortcut(ADDON_ID, {
label: "Toggle Column Guides",
action: toggleGrid,
actionName: "toggle-column-grid",
defaultShortcut: shortcut,
showInMenu: true
});
}, [api, toggleGrid]);
React3.useEffect(() => {
let mounted = true;
function handler() {
mounted && setReady(true);
}
api.once(STORY_RENDERED, handler);
return () => {
mounted = false;
};
}, [api]);
let disabled = typeof parameters.disable === "boolean" ? parameters.disable : false;
let isActive = disabled ? !disabled : state.visible;
return <React3.Fragment key={ADDON_ID}>
<IconButton title="Toggle Column Guides" disabled={disabled} active={isActive} onClick={toggleGrid}><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
<path d="M3 6.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C4.52 3 5.08 3 6.2 3h.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C10 4.52 10 5.08 10 6.2v11.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C8.48 21 7.92 21 6.8 21h-.6c-1.12 0-1.68 0-2.108-.218a2 2 0 0 1-.874-.874C3 19.48 3 18.92 3 17.8V6.2Z" />
<path d="M14 6.2c0-1.12 0-1.68.218-2.108a2 2 0 0 1 .874-.874C15.52 3 16.08 3 17.2 3h.6c1.12 0 1.68 0 2.108.218a2 2 0 0 1 .874.874C21 4.52 21 5.08 21 6.2v11.6c0 1.12 0 1.68-.218 2.108a2 2 0 0 1-.874.874C19.48 21 18.92 21 17.8 21h-.6c-1.12 0-1.68 0-2.108-.218a2 2 0 0 1-.874-.874C14 19.48 14 18.92 14 17.8V6.2Z" />
</svg></IconButton>
{ready && !disabled ? <ManagerRenderedGridsContainer /> : null}
</React3.Fragment>;
}
// src/manager.tsx
addons.register(ADDON_ID, () => {
addons.add(ADDON_ID, {
type: types.TOOL,
title: "Column Grid",
paramKey: PARAM_KEY,
match: ({ viewMode, tabId }) => !!(viewMode && viewMode.match(/^(story|docs)$/)) && !tabId,
render() {
return <Tool />;
}
});
});