storybook-addon-grid
Version:
Column guides for your stories
239 lines (231 loc) • 6.92 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/components";
import { STORY_RENDERED } from "@storybook/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";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
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) => /* @__PURE__ */ jsx(Column, {
color
}, index)),
[columns, color]
);
let gridNodes = /* @__PURE__ */ jsx(Grid, {
gap,
gutter,
maxWidth,
columns,
children: columnDivs
});
return /* @__PURE__ */ jsxs(Fragment, {
children: [
/* @__PURE__ */ jsx(Global, {
styles: {
[`#root`]: {
position: "relative",
zIndex: 0
}
}
}),
/* @__PURE__ */ jsx(Wrapper, {
"data-addon-id": ADDON_ID,
"data-visible": visible,
children: gridNodes
})
]
});
}
// src/Grids.tsx
import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
function ManagerRenderedGrids() {
let { columns, gap, color, gutter, maxWidth, disable } = useParameter(PARAM_KEY, {});
let [state] = useAddonState(ADDON_ID);
return /* @__PURE__ */ jsx2(Grids, {
columns,
color,
gap,
visible: disable != null ? !disable : state?.visible ?? false,
gutter,
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(
/* @__PURE__ */ jsx2(CacheProvider, {
value: styleCache.get(head),
children: /* @__PURE__ */ jsx2(ManagerRenderedGrids, {})
}),
iframeDocument.body
);
}
);
// src/Tool.tsx
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
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 /* @__PURE__ */ jsxs3(React3.Fragment, {
children: [
/* @__PURE__ */ jsx3(IconButton, {
title: "Toggle Column Guides",
disabled,
active: isActive,
onClick: toggleGrid,
children: /* @__PURE__ */ jsxs3("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",
children: [
/* @__PURE__ */ jsx3("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"
}),
/* @__PURE__ */ jsx3("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"
})
]
})
}),
ready && !disabled ? /* @__PURE__ */ jsx3(ManagerRenderedGridsContainer, {}) : null
]
}, ADDON_ID);
}
// src/manager.tsx
import { jsx as jsx4 } from "react/jsx-runtime";
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 /* @__PURE__ */ jsx4(Tool, {});
}
});
});