UNPKG

storybook-addon-custom-event-broadcaster

Version:
248 lines (234 loc) 8.65 kB
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 }); });