import { registerTool, Email, Row, Column, renderToHtml, renderToJson } from "@unlayer/react-elements"; // custom tools — the SAME config shape as the Builder's unlayer.registerTool const PlayerHero = registerTool({ name: "player_hero", renderer: { exporters: { web: (v) => heroMarkup(v), // your (values) => html functions email: (v) => heroEmailMarkup(v), // email-safe variant }}, }); // …Masthead, FormStats, SeasonNotes, ProgrammeFooter registered the same way // torvalds — GOLD · 79 OVR · computed from public activity const card = ( <Email backgroundColor="#f2ecdd" contentWidth="720px"> <Row><Column> <Masthead /> <PlayerHero playerName="Linus Torvalds" ovr={79} position="CM" tier="GOLD" /> <FormStats stats={{"BUILD":99,"REVIEW":48,"SHIP":50,"VOLUME":99,"IMPACT":99}} /> <SeasonNotes /> <ProgrammeFooter handle="torvalds" /> </Column></Row> </Email> ); const html = renderToHtml(card, { title: "torvalds · GitHub FC", fonts }); const design = renderToJson(card); // → stored as { type: "custom", slug, values } // opens in the Builder as REAL tools // exporters are plain functions — the pitch computes your heat blobs // from activity zones (reviews / commits / shipping) and draws real SVG: function pitchSvg({ blobs }) { const ellipses = blobs.map(([x, y, w]) => `<ellipse cx="${x * 640}" cy="${y * 300}" rx="${60 + w * 150}" ry="${44 + w * 96}" fill="url(#jet)"/>`); return `<svg viewBox="0 0 640 300">…turf, markings, ${ellipses.join("")}…</svg>`; } // register the same definitions in the editor embed (customJS) and a // remixer gets property panels — change the tier, colors, coordinates // with widgets, and each tool re-renders itself from its values.
$ npm install @unlayer/react-elements · docs · design.json · open it in the editor →