storybook-addon-custom-event-broadcaster
Version:
storybook addon for broadcasting custom events
248 lines (234 loc) • 8.65 kB
JavaScript
import { addons, types, useParameter } from 'storybook/manager-api';
import React2, { memo, useState } from 'react';
import { Button, AddonPanel } from 'storybook/internal/components';
import { AddIcon } from '@storybook/icons';
import { styled } from 'storybook/theming';
// src/manager.tsx
// src/constants.ts
var ADDON_ID = "storybook/custom-event-broadcaster";
var PANEL_ID = `${ADDON_ID}/panel`;
var RowContainer = styled.div`
display: grid;
grid-template-columns: 2fr 3fr 2fr 100px;
gap: 12px;
padding: 16px;
background: ${(props) => props.theme.background.content};
border: 2px solid ${(props) => props.theme.color.border};
border-radius: 6px;
transition: all 0.2s;
&:hover {
border-color: ${(props) => props.theme.color.mediumdark};
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
`;
var StyledInput = styled.input`
padding: 10px 12px;
border: 2px solid ${(props) => props.theme.color.border};
border-radius: 4px;
background: ${(props) => props.theme.input.background};
color: ${(props) => props.theme.input.color};
font-size: 13px;
font-family: inherit;
transition: border-color 0.2s;
&:focus {
outline: none;
border-color: ${(props) => props.theme.color.secondary};
box-shadow: 0 0 0 1px ${(props) => props.theme.color.secondary};
}
&::placeholder {
color: ${(props) => props.theme.color.mediumdark};
}
`;
var StyledTextarea = styled.textarea`
padding: 10px 12px;
border: 2px solid ${(props) => props.theme.color.border};
border-radius: 4px;
background: ${(props) => props.theme.input.background};
color: ${(props) => props.theme.input.color};
font-size: 13px;
font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
resize: vertical;
min-height: 80px;
line-height: 1.6;
transition: border-color 0.2s;
&:focus {
outline: none;
border-color: ${(props) => props.theme.color.secondary};
box-shadow: 0 0 0 1px ${(props) => props.theme.color.secondary};
}
&::placeholder {
color: ${(props) => props.theme.color.mediumdark};
}
`;
var AddSelectorButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px 12px;
background: ${(props) => props.theme.background.content};
border: 2px dashed ${(props) => props.theme.color.mediumdark};
border-radius: 4px;
cursor: pointer;
color: ${(props) => props.theme.color.defaultText};
font-size: 12px;
width: 100%;
transition: all 0.2s;
font-weight: 500;
&:hover {
border-color: ${(props) => props.theme.color.secondary};
color: ${(props) => props.theme.color.secondary};
background: ${(props) => props.theme.background.hoverable};
}
svg {
width: 14px;
height: 14px;
}
`;
var SendButton = styled(Button)`
height: 40px;
padding: 0 20px;
font-size: 13px;
font-weight: 600;
white-space: nowrap;
`;
var SelectorContainer = styled.div`
position: relative;
`;
var fireEvent = (eventName, eventData, selector = "") => {
const data = eventData || null;
try {
const parsedData = data ? JSON.parse(data) : {};
const iframe = document.getElementById("storybook-preview-iframe");
const storyBookIframeDocument = iframe?.contentWindow?.document;
if (!storyBookIframeDocument) {
console.warn("Could not find storybook preview iframe");
return;
}
if (selector.length > 0) {
const element = storyBookIframeDocument.querySelector(selector);
if (element) {
element.dispatchEvent(new CustomEvent(eventName, { detail: parsedData }));
console.log(`\u2705 Event "${eventName}" fired on "${selector}"`, parsedData);
} else {
console.warn(`\u26A0\uFE0F Selector "${selector}" not found`);
}
} else {
storyBookIframeDocument.dispatchEvent(new CustomEvent(eventName, { detail: parsedData }));
console.log(`\u2705 Event "${eventName}" fired globally`, parsedData);
}
} catch (error) {
console.error("\u274C Failed to fire event:", error);
}
};
var CustomEventRow = memo(
({ selectorDefault = "", eventNameDefault = "", eventDataDefault = "{}" }) => {
const [displaySelector, setDisplaySelector] = useState(selectorDefault.length > 0);
const [selector, setSelector] = useState(selectorDefault);
const [eventName, setEventName] = useState(eventNameDefault);
const [eventData, setEventData] = useState(
typeof eventDataDefault === "string" ? eventDataDefault : JSON.stringify(eventDataDefault, null, 2)
);
return /* @__PURE__ */ React2.createElement(RowContainer, null, /* @__PURE__ */ React2.createElement(
StyledInput,
{
type: "text",
value: eventName,
onChange: (e) => setEventName(e.target.value),
placeholder: "event-name"
}
), /* @__PURE__ */ React2.createElement(
StyledTextarea,
{
value: eventData,
onChange: (e) => setEventData(e.target.value),
placeholder: '{"key": "value"}'
}
), /* @__PURE__ */ React2.createElement(SelectorContainer, null, !displaySelector && selector.length === 0 ? /* @__PURE__ */ React2.createElement(AddSelectorButton, { onClick: () => setDisplaySelector(true) }, /* @__PURE__ */ React2.createElement(AddIcon, null), "Add selector") : /* @__PURE__ */ React2.createElement(
StyledInput,
{
type: "text",
value: selector,
onChange: (e) => setSelector(e.target.value),
placeholder: "tp-button"
}
)), /* @__PURE__ */ React2.createElement(SendButton, { onClick: () => fireEvent(eventName, eventData, selector) }, "Send"));
}
);
CustomEventRow.displayName = "CustomEventRow";
var Container = styled.div`
padding: 20px;
background: ${(props) => props.theme.background.content};
`;
var Header = styled.div`
display: grid;
grid-template-columns: 2fr 3fr 2fr 100px;
gap: 12px;
padding: 12px 16px;
background: ${(props) => props.theme.color.darker};
border-radius: 4px;
margin-bottom: 16px;
font-weight: 600;
font-size: 13px;
color: ${(props) => props.theme.color.lightest};
border: 1px solid ${(props) => props.theme.color.border};
`;
var AddRowButton = styled.button`
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px;
margin-top: 16px;
background: ${(props) => props.theme.background.content};
border: 2px dashed ${(props) => props.theme.color.mediumdark};
border-radius: 4px;
cursor: pointer;
color: ${(props) => props.theme.color.defaultText};
font-size: 13px;
font-weight: 500;
transition: all 0.2s;
&:hover {
border-color: ${(props) => props.theme.color.secondary};
background: ${(props) => props.theme.background.hoverable};
color: ${(props) => props.theme.color.secondary};
}
svg {
width: 16px;
height: 16px;
}
`;
var EventsContainer = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
`;
var CustomEventPanel = () => {
const customEventPanelParameterData = useParameter("customEventPanel", []);
const [additionalRows, setAdditionalRows] = useState(0);
const addNewRow = () => {
setAdditionalRows((prev) => prev + 1);
};
return /* @__PURE__ */ React2.createElement(Container, null, /* @__PURE__ */ React2.createElement(Header, null, /* @__PURE__ */ React2.createElement("div", null, "Event Name"), /* @__PURE__ */ React2.createElement("div", null, "Data (JSON)"), /* @__PURE__ */ React2.createElement("div", null, "Selector (optional)"), /* @__PURE__ */ React2.createElement("div", null, "Action")), /* @__PURE__ */ React2.createElement(EventsContainer, null, customEventPanelParameterData.length > 0 ? customEventPanelParameterData.map((config, index) => /* @__PURE__ */ React2.createElement(
CustomEventRow,
{
key: `config-${index}`,
selectorDefault: config.selector,
eventNameDefault: config.eventName,
eventDataDefault: config.eventData
}
)) : /* @__PURE__ */ React2.createElement(CustomEventRow, null), [...Array(additionalRows)].map((_, index) => /* @__PURE__ */ React2.createElement(CustomEventRow, { key: `additional-${index}` }))), /* @__PURE__ */ React2.createElement(AddRowButton, { onClick: addNewRow }, /* @__PURE__ */ React2.createElement(AddIcon, null), "Add new event"));
};
// src/Panel.tsx
var Panel = (props) => {
return /* @__PURE__ */ React2.createElement(AddonPanel, { ...props }, /* @__PURE__ */ React2.createElement(CustomEventPanel, null));
};
// src/manager.tsx
addons.register(ADDON_ID, () => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: "Custom Events",
match: ({ viewMode }) => viewMode === "story",
render: Panel
});
});