@dextinity/mail-react 2.0.0-canary-20260729062014

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +112 -0
  3. package/lib/blocks/factories/BlocksBlock.d.ts +14 -0
  4. package/lib/blocks/factories/BlocksBlock.js +15 -0
  5. package/lib/blocks/factories/ListBlock.d.ts +13 -0
  6. package/lib/blocks/factories/ListBlock.js +5 -0
  7. package/lib/blocks/factories/OneOfBlock.d.ts +14 -0
  8. package/lib/blocks/factories/OneOfBlock.js +15 -0
  9. package/lib/blocks/factories/OptionalBlock.d.ts +10 -0
  10. package/lib/blocks/factories/OptionalBlock.js +7 -0
  11. package/lib/blocks/factories/types.d.ts +4 -0
  12. package/lib/blocks/factories/types.js +1 -0
  13. package/lib/blocks/helpers/PropsWithData.d.ts +3 -0
  14. package/lib/blocks/helpers/PropsWithData.js +1 -0
  15. package/lib/blocks/pixelImage/HtmlPixelImageBlock.d.ts +11 -0
  16. package/lib/blocks/pixelImage/HtmlPixelImageBlock.js +18 -0
  17. package/lib/blocks/pixelImage/MjmlPixelImageBlock.d.ts +9 -0
  18. package/lib/blocks/pixelImage/MjmlPixelImageBlock.js +15 -0
  19. package/lib/blocks/pixelImage/common.d.ts +18 -0
  20. package/lib/blocks/pixelImage/common.js +1 -0
  21. package/lib/blocks/pixelImage/usePixelImageBlockConfig.d.ts +5 -0
  22. package/lib/blocks/pixelImage/usePixelImageBlockConfig.js +11 -0
  23. package/lib/blocks/pixelImage/usePixelImageBlockData.d.ts +16 -0
  24. package/lib/blocks/pixelImage/usePixelImageBlockData.js +80 -0
  25. package/lib/blocks/richText/common.d.ts +66 -0
  26. package/lib/blocks/richText/common.js +1 -0
  27. package/lib/blocks/richText/createRichTextBlock.d.ts +30 -0
  28. package/lib/blocks/richText/createRichTextBlock.js +81 -0
  29. package/lib/blocks/richText/createRichTextRenderers.d.ts +17 -0
  30. package/lib/blocks/richText/createRichTextRenderers.js +83 -0
  31. package/lib/blocks.generated.d.ts +140 -0
  32. package/lib/blocks.generated.js +1 -0
  33. package/lib/client/index.d.ts +1 -0
  34. package/lib/client/index.js +1 -0
  35. package/lib/client/renderMailHtml.d.ts +9 -0
  36. package/lib/client/renderMailHtml.js +7 -0
  37. package/lib/components/button/HtmlButton.d.ts +9 -0
  38. package/lib/components/button/HtmlButton.js +58 -0
  39. package/lib/components/button/MjmlButton.d.ts +10 -0
  40. package/lib/components/button/MjmlButton.js +91 -0
  41. package/lib/components/button/buttonProps.d.ts +35 -0
  42. package/lib/components/button/buttonProps.js +1 -0
  43. package/lib/components/button/defaultButtonStyles.d.ts +2 -0
  44. package/lib/components/button/defaultButtonStyles.js +9 -0
  45. package/lib/components/button/generateResponsiveButtonCss.d.ts +8 -0
  46. package/lib/components/button/generateResponsiveButtonCss.js +21 -0
  47. package/lib/components/divider/HtmlDivider.d.ts +9 -0
  48. package/lib/components/divider/HtmlDivider.js +43 -0
  49. package/lib/components/divider/MjmlDivider.d.ts +7 -0
  50. package/lib/components/divider/MjmlDivider.js +13 -0
  51. package/lib/components/divider/defaultDividerStyles.d.ts +2 -0
  52. package/lib/components/divider/defaultDividerStyles.js +4 -0
  53. package/lib/components/divider/dividerProps.d.ts +51 -0
  54. package/lib/components/divider/dividerProps.js +1 -0
  55. package/lib/components/divider/generateResponsiveDividerCss.d.ts +8 -0
  56. package/lib/components/divider/generateResponsiveDividerCss.js +15 -0
  57. package/lib/components/image/HtmlImage.d.ts +11 -0
  58. package/lib/components/image/HtmlImage.js +23 -0
  59. package/lib/components/image/MjmlImage.d.ts +10 -0
  60. package/lib/components/image/MjmlImage.js +22 -0
  61. package/lib/components/inlineLink/HtmlInlineLink.d.ts +17 -0
  62. package/lib/components/inlineLink/HtmlInlineLink.js +35 -0
  63. package/lib/components/mailRoot/MjmlMailRoot.d.ts +32 -0
  64. package/lib/components/mailRoot/MjmlMailRoot.js +25 -0
  65. package/lib/components/section/MjmlSection.d.ts +21 -0
  66. package/lib/components/section/MjmlSection.js +48 -0
  67. package/lib/components/text/HtmlText.d.ts +51 -0
  68. package/lib/components/text/HtmlText.js +47 -0
  69. package/lib/components/text/MjmlText.d.ts +40 -0
  70. package/lib/components/text/MjmlText.js +65 -0
  71. package/lib/components/text/OutlookTextStyleContext.d.ts +9 -0
  72. package/lib/components/text/OutlookTextStyleContext.js +10 -0
  73. package/lib/components/text/textStyles.d.ts +10 -0
  74. package/lib/components/text/textStyles.js +24 -0
  75. package/lib/components/wrapper/InsideMjmlWrapperContext.d.ts +3 -0
  76. package/lib/components/wrapper/InsideMjmlWrapperContext.js +6 -0
  77. package/lib/components/wrapper/MjmlWrapper.d.ts +14 -0
  78. package/lib/components/wrapper/MjmlWrapper.js +12 -0
  79. package/lib/config/ConfigProvider.d.ts +43 -0
  80. package/lib/config/ConfigProvider.js +16 -0
  81. package/lib/index.d.ts +47 -0
  82. package/lib/index.js +29 -0
  83. package/lib/server/index.d.ts +1 -0
  84. package/lib/server/index.js +1 -0
  85. package/lib/server/renderMailHtml.d.ts +9 -0
  86. package/lib/server/renderMailHtml.js +7 -0
  87. package/lib/storybook/CopyMailHtmlButton.d.ts +4 -0
  88. package/lib/storybook/CopyMailHtmlButton.js +33 -0
  89. package/lib/storybook/MailRendererDecorator.d.ts +7 -0
  90. package/lib/storybook/MailRendererDecorator.js +17 -0
  91. package/lib/storybook/MjmlWarningsPanel.d.ts +7 -0
  92. package/lib/storybook/MjmlWarningsPanel.js +33 -0
  93. package/lib/storybook/UsePublicImageUrlsToggle.d.ts +4 -0
  94. package/lib/storybook/UsePublicImageUrlsToggle.js +14 -0
  95. package/lib/storybook/index.d.ts +2 -0
  96. package/lib/storybook/index.js +9 -0
  97. package/lib/storybook/manager.d.ts +1 -0
  98. package/lib/storybook/manager.js +25 -0
  99. package/lib/storybook/preview.d.ts +39 -0
  100. package/lib/storybook/preview.js +27 -0
  101. package/lib/storybook/replaceImagesWithPublicUrl.d.ts +1 -0
  102. package/lib/storybook/replaceImagesWithPublicUrl.js +15 -0
  103. package/lib/styles/Styles.d.ts +6 -0
  104. package/lib/styles/Styles.js +16 -0
  105. package/lib/styles/generateResponsiveVariantCss.d.ts +26 -0
  106. package/lib/styles/generateResponsiveVariantCss.js +66 -0
  107. package/lib/styles/registerStyles.d.ts +19 -0
  108. package/lib/styles/registerStyles.js +14 -0
  109. package/lib/theme/ThemeProvider.d.ts +8 -0
  110. package/lib/theme/ThemeProvider.js +17 -0
  111. package/lib/theme/createBreakpoint.d.ts +7 -0
  112. package/lib/theme/createBreakpoint.js +11 -0
  113. package/lib/theme/createTheme.d.ts +24 -0
  114. package/lib/theme/createTheme.js +32 -0
  115. package/lib/theme/defaultTheme.d.ts +2 -0
  116. package/lib/theme/defaultTheme.js +27 -0
  117. package/lib/theme/responsiveValue.d.ts +31 -0
  118. package/lib/theme/responsiveValue.js +34 -0
  119. package/lib/theme/themeTypes.d.ts +204 -0
  120. package/lib/theme/themeTypes.js +1 -0
  121. package/lib/utils/css.d.ts +4 -0
  122. package/lib/utils/css.js +4 -0
  123. package/package.json +91 -0
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import redraftImport from "redraft";
4
+ import { HtmlText } from "../../components/text/HtmlText.js";
5
+ import { MjmlText } from "../../components/text/MjmlText.js";
6
+ import { builtInLinkTypes, createRichTextRenderers } from "./createRichTextRenderers.js";
7
+ // redraft is CommonJS-only: under native ESM the default import is the whole
8
+ // `module.exports` object, under bundlers it is the render function itself.
9
+ const redraft = typeof redraftImport === "function" ? redraftImport : redraftImport.default;
10
+ function MjmlBlockText({ bottomSpacing, variant, className, fontWeight, children, ...styleProps }) {
11
+ return (_jsx(MjmlText, { variant: variant, bottomSpacing: bottomSpacing, className: clsx("richTextBlock__text", className), ...(fontWeight !== undefined && { fontWeight: String(fontWeight) }), ...styleProps, children: children }));
12
+ }
13
+ function HtmlBlockText({ bottomSpacing, variant, className, children, ...styleProps }) {
14
+ return (_jsx(HtmlText, { element: "div", variant: variant, bottomSpacing: bottomSpacing, className: clsx("richTextBlock__text", className), style: styleProps, children: children }));
15
+ }
16
+ function isDraftBlock(block) {
17
+ if (typeof block !== "object" || block === null || !("key" in block) || !("text" in block)) {
18
+ return false;
19
+ }
20
+ const { key, text } = block;
21
+ return typeof key === "string" && typeof text === "string";
22
+ }
23
+ function isDraftContent(draftContent) {
24
+ if (typeof draftContent !== "object" || draftContent === null || !("blocks" in draftContent)) {
25
+ return false;
26
+ }
27
+ const { blocks } = draftContent;
28
+ return Array.isArray(blocks) && blocks.every(isDraftBlock);
29
+ }
30
+ function renderRichTextContent({ draftContent, blockTypes, linkTypes, inline, blockTextComponent }) {
31
+ if (!isDraftContent(draftContent)) {
32
+ return null;
33
+ }
34
+ const blocksWithText = draftContent.blocks.filter((block) => block.text !== "");
35
+ const lastBlockKey = blocksWithText[blocksWithText.length - 1]?.key;
36
+ if (lastBlockKey === undefined) {
37
+ return null;
38
+ }
39
+ const renderers = createRichTextRenderers({ blockTypes, linkTypes, inline, blockTextComponent, lastBlockKey });
40
+ return redraft({ ...draftContent, blocks: blocksWithText }, renderers);
41
+ }
42
+ /**
43
+ * Creates a pair of rich-text block components that render CMS RichText block
44
+ * data (draft-js raw content) as themed text.
45
+ *
46
+ * Call the factory once per configuration — at the top level of a file, not
47
+ * inside a component — and reuse the returned components. Call it again for
48
+ * differently-configured rich-text blocks (e.g. a generic and a headline-only
49
+ * one).
50
+ *
51
+ * `MjmlRichTextBlock` renders each draft block as `MjmlText` and must be
52
+ * placed within an `MjmlColumn`. `HtmlRichTextBlock` renders each draft block
53
+ * as `HtmlText` for raw-HTML contexts (e.g. inside `MjmlRaw`).
54
+ *
55
+ * ```ts
56
+ * export const { MjmlRichTextBlock, HtmlRichTextBlock } = createRichTextBlock({
57
+ * blockTypes: {
58
+ * "header-one": { variant: "heading1" },
59
+ * "paragraph-standard": { variant: "body" },
60
+ * },
61
+ * });
62
+ * ```
63
+ */
64
+ export function createRichTextBlock(options = {}) {
65
+ const blockTypes = options.blockTypes ?? {};
66
+ const linkTypes = {
67
+ ...builtInLinkTypes,
68
+ // Consumers declare each resolver's props via the typed map, but at
69
+ // runtime they are unknown draft-js data. This one contained cast lets
70
+ // consumers work typed without casting in their own resolvers.
71
+ ...options.linkTypes,
72
+ };
73
+ const inline = options.inline ?? {};
74
+ function MjmlRichTextBlock({ data }) {
75
+ return renderRichTextContent({ draftContent: data.draftContent, blockTypes, linkTypes, inline, blockTextComponent: MjmlBlockText });
76
+ }
77
+ function HtmlRichTextBlock({ data }) {
78
+ return renderRichTextContent({ draftContent: data.draftContent, blockTypes, linkTypes, inline, blockTextComponent: HtmlBlockText });
79
+ }
80
+ return { MjmlRichTextBlock, HtmlRichTextBlock };
81
+ }
@@ -0,0 +1,17 @@
1
+ import type { ComponentType, PropsWithChildren } from "react";
2
+ import type { Renderers } from "redraft";
3
+ import type { RichTextBlockTypeProps, RichTextInlineRenderer, RichTextLinkHrefResolver } from "./common.js";
4
+ export type BlockTextProps = PropsWithChildren<RichTextBlockTypeProps & {
5
+ /** Whether the theme's spacing below the text applies — set for every draft block except the last. */
6
+ bottomSpacing: boolean;
7
+ }>;
8
+ export declare const builtInLinkTypes: Record<string, RichTextLinkHrefResolver>;
9
+ interface CreateRichTextRenderersOptions {
10
+ blockTypes: Record<string, RichTextBlockTypeProps>;
11
+ linkTypes: Record<string, RichTextLinkHrefResolver>;
12
+ inline: Record<string, RichTextInlineRenderer>;
13
+ blockTextComponent: ComponentType<BlockTextProps>;
14
+ lastBlockKey: string;
15
+ }
16
+ export declare function createRichTextRenderers({ blockTypes, linkTypes, inline, blockTextComponent, lastBlockKey, }: CreateRichTextRenderersOptions): Renderers;
17
+ export {};
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { HtmlInlineLink } from "../../components/inlineLink/HtmlInlineLink.js";
3
+ const inlineStyleRenderers = {
4
+ // The explicit styles back up the semantic tags in rendering engines that don't apply their default styling.
5
+ BOLD: (children, { key }) => (_jsx("strong", { style: { fontWeight: "bold" }, children: children }, key)),
6
+ ITALIC: (children, { key }) => (_jsx("em", { style: { fontStyle: "italic" }, children: children }, key)),
7
+ SUB: (children, { key }) => _jsx("sub", { children: children }, key),
8
+ SUP: (children, { key }) => _jsx("sup", { children: children }, key),
9
+ STRIKETHROUGH: (children, { key }) => _jsx("s", { children: children }, key),
10
+ };
11
+ function resolveExternalLinkHref(props) {
12
+ if (typeof props !== "object" || props === null || !("targetUrl" in props)) {
13
+ return undefined;
14
+ }
15
+ const { targetUrl } = props;
16
+ return typeof targetUrl === "string" ? targetUrl : undefined;
17
+ }
18
+ export const builtInLinkTypes = {
19
+ external: resolveExternalLinkHref,
20
+ };
21
+ function getLinkBlock(entityData) {
22
+ if (typeof entityData !== "object" || entityData === null || !("block" in entityData)) {
23
+ return undefined;
24
+ }
25
+ const { block } = entityData;
26
+ if (typeof block !== "object" || block === null || !("type" in block) || !("props" in block)) {
27
+ return undefined;
28
+ }
29
+ const { type, props } = block;
30
+ return typeof type === "string" ? { type, props } : undefined;
31
+ }
32
+ function renderWithLineBreaks(node) {
33
+ if (typeof node === "string" && node.includes("\n")) {
34
+ const lines = node.split("\n");
35
+ return lines.flatMap((line, index) => (index === 0 ? [line] : [_jsx("br", {}, index), line]));
36
+ }
37
+ if (Array.isArray(node)) {
38
+ return node.map((child) => renderWithLineBreaks(child));
39
+ }
40
+ return node;
41
+ }
42
+ function createTextBlockRenderFn({ blockTextComponent: BlockText, blockTypeProps, lastBlockKey }) {
43
+ return (children, { keys }) => children.map((child, index) => (_jsx(BlockText, { bottomSpacing: keys[index] !== lastBlockKey, ...blockTypeProps, children: renderWithLineBreaks(child) }, keys[index])));
44
+ }
45
+ function createListBlockRenderFn({ listElement: ListElement, blockTextComponent: BlockText, blockTypeProps, lastBlockKey, }) {
46
+ return (children, { keys }) => (_jsx(BlockText, { bottomSpacing: !keys.includes(lastBlockKey), ...blockTypeProps, children: _jsx(ListElement, { className: "richTextBlock__list", style: { marginTop: 0, marginBottom: 0 }, children: children.map((child, index) => (_jsx("li", { className: "richTextBlock__listItem", children: renderWithLineBreaks(child) }, keys[index]))) }) }, keys.join("-")));
47
+ }
48
+ const listBlockTypes = ["unordered-list-item", "ordered-list-item"];
49
+ export function createRichTextRenderers({ blockTypes, linkTypes, inline, blockTextComponent, lastBlockKey, }) {
50
+ const blocks = {};
51
+ // "unstyled" is redraft's blockFallback: registering it makes every block type the caller did not configure render with base theme styles.
52
+ for (const blockType of new Set(["unstyled", ...Object.keys(blockTypes)])) {
53
+ if (listBlockTypes.includes(blockType)) {
54
+ continue;
55
+ }
56
+ blocks[blockType] = createTextBlockRenderFn({ blockTextComponent, blockTypeProps: blockTypes[blockType] ?? {}, lastBlockKey });
57
+ }
58
+ for (const listBlockType of listBlockTypes) {
59
+ blocks[listBlockType] = createListBlockRenderFn({
60
+ listElement: listBlockType === "unordered-list-item" ? "ul" : "ol",
61
+ blockTextComponent,
62
+ blockTypeProps: blockTypes[listBlockType] ?? {},
63
+ lastBlockKey,
64
+ });
65
+ }
66
+ return {
67
+ inline: { ...inlineStyleRenderers, ...inline },
68
+ blocks,
69
+ entities: {
70
+ LINK: (children, data, { key }) => {
71
+ const linkBlock = getLinkBlock(data);
72
+ if (linkBlock === undefined) {
73
+ return children;
74
+ }
75
+ const href = linkTypes[linkBlock.type]?.(linkBlock.props);
76
+ if (href === undefined) {
77
+ return children;
78
+ }
79
+ return (_jsx(HtmlInlineLink, { className: "richTextBlock__link", href: href, children: children }, key));
80
+ },
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,140 @@
1
+ export interface AnchorBlockData {
2
+ name?: string;
3
+ }
4
+ export interface DamFileDownloadLinkBlockData {
5
+ file?: {
6
+ id: string;
7
+ name: string;
8
+ fileUrl: string;
9
+ size: number;
10
+ mimetype: string;
11
+ scope?: unknown;
12
+ altText?: string;
13
+ title?: string;
14
+ };
15
+ openFileType: "NewTab" | "Download";
16
+ }
17
+ export interface DamImageBlockData {
18
+ attachedBlocks: Array<{
19
+ type: string;
20
+ props: PixelImageBlockData | SvgImageBlockData;
21
+ }>;
22
+ activeType?: string;
23
+ block?: {
24
+ type: string;
25
+ props: PixelImageBlockData | SvgImageBlockData;
26
+ };
27
+ }
28
+ export interface DamVideoBlockData {
29
+ autoplay?: boolean;
30
+ showControls?: boolean;
31
+ loop?: boolean;
32
+ previewImage: PixelImageBlockData;
33
+ damFile?: {
34
+ id: string;
35
+ name: string;
36
+ size: number;
37
+ mimetype: string;
38
+ contentHash: string;
39
+ title?: string;
40
+ altText?: string;
41
+ archived: boolean;
42
+ scope?: unknown;
43
+ fileUrl: string;
44
+ captions?: Array<{
45
+ id: string;
46
+ language: string;
47
+ fileUrl: string;
48
+ }>;
49
+ };
50
+ }
51
+ export interface EmailLinkBlockData {
52
+ email?: string;
53
+ }
54
+ export interface ExternalLinkBlockData {
55
+ targetUrl?: string;
56
+ openInNewWindow: boolean;
57
+ noFollow: boolean;
58
+ }
59
+ export interface InternalLinkBlockData {
60
+ targetPageAnchor?: string;
61
+ targetPage?: {
62
+ id: string;
63
+ name: string;
64
+ path: string;
65
+ scope?: unknown;
66
+ documentType: string;
67
+ };
68
+ }
69
+ export interface NewsletterImageBlockData {
70
+ image: PixelImageBlockData;
71
+ }
72
+ export interface PhoneLinkBlockData {
73
+ phone?: string;
74
+ }
75
+ export interface PixelImageBlockData {
76
+ damFile?: {
77
+ id: string;
78
+ name: string;
79
+ size: number;
80
+ mimetype: string;
81
+ contentHash: string;
82
+ title?: string;
83
+ altText?: string;
84
+ archived: boolean;
85
+ scope?: unknown;
86
+ image?: {
87
+ width: number;
88
+ height: number;
89
+ cropArea: {
90
+ focalPoint: "SMART" | "CENTER" | "NORTHWEST" | "NORTHEAST" | "SOUTHWEST" | "SOUTHEAST";
91
+ width?: number;
92
+ height?: number;
93
+ x?: number;
94
+ y?: number;
95
+ };
96
+ dominantColor?: string;
97
+ };
98
+ fileUrl: string;
99
+ };
100
+ cropArea?: {
101
+ focalPoint: "SMART" | "CENTER" | "NORTHWEST" | "NORTHEAST" | "SOUTHWEST" | "SOUTHEAST";
102
+ width?: number;
103
+ height?: number;
104
+ x?: number;
105
+ y?: number;
106
+ };
107
+ urlTemplate: string;
108
+ }
109
+ export interface SpaceBlockData {
110
+ height: number;
111
+ }
112
+ export interface SvgImageBlockData {
113
+ damFile?: {
114
+ id: string;
115
+ name: string;
116
+ size: number;
117
+ mimetype: string;
118
+ contentHash: string;
119
+ title?: string;
120
+ altText?: string;
121
+ archived: boolean;
122
+ scope?: unknown;
123
+ fileUrl: string;
124
+ };
125
+ }
126
+ export interface VimeoVideoBlockData {
127
+ autoplay?: boolean;
128
+ showControls?: boolean;
129
+ loop?: boolean;
130
+ previewImage: PixelImageBlockData;
131
+ vimeoIdentifier?: string;
132
+ }
133
+ export interface YouTubeVideoBlockData {
134
+ autoplay?: boolean;
135
+ showControls?: boolean;
136
+ loop?: boolean;
137
+ previewImage: PixelImageBlockData;
138
+ youtubeIdentifier?: string;
139
+ }
140
+ export type AllBlockNames = "Anchor" | "DamFileDownloadLink" | "DamImage" | "DamVideo" | "EmailLink" | "ExternalLink" | "InternalLink" | "NewsletterImage" | "PhoneLink" | "PixelImage" | "Space" | "SvgImage" | "VimeoVideo" | "YouTubeVideo";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export { renderMailHtml } from "./renderMailHtml.js";
@@ -0,0 +1 @@
1
+ export { renderMailHtml } from "./renderMailHtml.js";
@@ -0,0 +1,9 @@
1
+ import mjml2html from "mjml-browser";
2
+ import type { ReactElement } from "react";
3
+ type MjmlOptions = Parameters<typeof mjml2html>[1];
4
+ type MjmlWarning = ReturnType<typeof mjml2html>["errors"][number];
5
+ export declare function renderMailHtml(element: ReactElement, options?: MjmlOptions): {
6
+ html: string;
7
+ mjmlWarnings: MjmlWarning[];
8
+ };
9
+ export {};
@@ -0,0 +1,7 @@
1
+ import { renderToMjml } from "@faire/mjml-react/utils/renderToMjml.js";
2
+ import mjml2html from "mjml-browser";
3
+ export function renderMailHtml(element, options) {
4
+ const mjmlString = renderToMjml(element);
5
+ const { html, errors: mjmlWarnings } = mjml2html(mjmlString, { validationLevel: "soft", ...options });
6
+ return { html, mjmlWarnings };
7
+ }
@@ -0,0 +1,9 @@
1
+ import type { AnchorHTMLAttributes, ReactNode } from "react";
2
+ import type { Theme } from "../../theme/themeTypes.js";
3
+ import type { ButtonProps } from "./buttonProps.js";
4
+ export type HtmlButtonProps = ButtonProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof ButtonProps>;
5
+ /**
6
+ * Themed button for use inside MJML ending tags or outside of the MJML context.
7
+ */
8
+ export declare function HtmlButton({ variant: variantProp, fullWidth, target, href, className, style, children, ...restProps }: HtmlButtonProps): ReactNode;
9
+ export declare function generateHtmlButtonStyles(theme: Theme): string;
@@ -0,0 +1,58 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { registerStyles } from "../../styles/registerStyles.js";
4
+ import { defaultTheme } from "../../theme/defaultTheme.js";
5
+ import { getDefaultOrUndefined } from "../../theme/responsiveValue.js";
6
+ import { useOptionalTheme } from "../../theme/ThemeProvider.js";
7
+ import { defaultButtonStyles } from "./defaultButtonStyles.js";
8
+ import { generateResponsiveButtonCss } from "./generateResponsiveButtonCss.js";
9
+ /**
10
+ * Themed button for use inside MJML ending tags or outside of the MJML context.
11
+ */
12
+ export function HtmlButton({ variant: variantProp, fullWidth, target = "_blank", href = "#", className, style, children, ...restProps }) {
13
+ const theme = useOptionalTheme();
14
+ if (theme === null && variantProp !== undefined) {
15
+ throw new Error("The `variant` prop requires being wrapped in a ThemeProvider or MjmlMailRoot.");
16
+ }
17
+ const themeButton = theme?.button ?? defaultButtonStyles;
18
+ const textStyles = theme?.text ?? defaultTheme.text;
19
+ const { defaultVariant, variants, ...baseStyles } = themeButton;
20
+ const activeVariant = variantProp ?? defaultVariant;
21
+ const variantStyles = activeVariant ? variants?.[activeVariant] : undefined;
22
+ const mergedStyles = { ...baseStyles, ...variantStyles };
23
+ const backgroundColor = getDefaultOrUndefined(mergedStyles.backgroundColor);
24
+ const borderRadius = getDefaultOrUndefined(mergedStyles.borderRadius);
25
+ const innerPadding = getDefaultOrUndefined(mergedStyles.padding);
26
+ const cellStyle = {
27
+ border: getDefaultOrUndefined(mergedStyles.border),
28
+ borderRadius,
29
+ backgroundColor,
30
+ msoPaddingAlt: innerPadding,
31
+ };
32
+ const anchorStyle = {
33
+ display: fullWidth ? "block" : "inline-block",
34
+ boxSizing: fullWidth ? "border-box" : undefined,
35
+ width: fullWidth ? "100%" : undefined,
36
+ backgroundColor,
37
+ backgroundImage: getDefaultOrUndefined(mergedStyles.backgroundImage),
38
+ color: getDefaultOrUndefined(mergedStyles.color),
39
+ fontFamily: getDefaultOrUndefined(mergedStyles.fontFamily) ?? textStyles.fontFamily,
40
+ fontSize: getDefaultOrUndefined(mergedStyles.fontSize) ?? textStyles.fontSize,
41
+ fontWeight: getDefaultOrUndefined(mergedStyles.fontWeight) ?? textStyles.fontWeight,
42
+ lineHeight: getDefaultOrUndefined(mergedStyles.lineHeight) ?? textStyles.lineHeight,
43
+ margin: 0,
44
+ textAlign: "center",
45
+ textDecoration: "none",
46
+ padding: innerPadding,
47
+ borderRadius,
48
+ msoPaddingAlt: "0px",
49
+ ...style,
50
+ };
51
+ return (_jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: fullWidth ? "100%" : undefined, className: clsx("htmlButton", activeVariant && `htmlButton--${activeVariant}`, fullWidth && "htmlButton--fullWidth", className), style: { borderCollapse: "separate", lineHeight: "100%", msoLineHeightRule: "exactly" }, children: _jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { align: "center", valign: "middle", bgcolor: backgroundColor, style: cellStyle, children: _jsx("a", { ...restProps, href: href, target: target, style: anchorStyle, children: children }) }) }) }) }));
52
+ }
53
+ export function generateHtmlButtonStyles(theme) {
54
+ return generateResponsiveButtonCss(theme, {
55
+ styleSelector: (variantName) => `.htmlButton--${variantName} a`,
56
+ });
57
+ }
58
+ registerStyles(generateHtmlButtonStyles);
@@ -0,0 +1,10 @@
1
+ import { type IMjmlButtonProps } from "@faire/mjml-react";
2
+ import type { ReactNode } from "react";
3
+ import type { Theme } from "../../theme/themeTypes.js";
4
+ import type { ButtonProps } from "./buttonProps.js";
5
+ export type MjmlButtonProps = Omit<IMjmlButtonProps, "href" | "target"> & ButtonProps;
6
+ /**
7
+ * Themed button for use inside an `MjmlColumn`.
8
+ */
9
+ export declare function MjmlButton({ variant: variantProp, fullWidth, width, href, target, className, children, ...restProps }: MjmlButtonProps): ReactNode;
10
+ export declare function generateMjmlButtonStyles(theme: Theme): string;
@@ -0,0 +1,91 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MjmlButton as BaseMjmlButton } from "@faire/mjml-react";
3
+ import clsx from "clsx";
4
+ import { registerStyles } from "../../styles/registerStyles.js";
5
+ import { getDefaultOrUndefined } from "../../theme/responsiveValue.js";
6
+ import { useOptionalTheme } from "../../theme/ThemeProvider.js";
7
+ import { css } from "../../utils/css.js";
8
+ import { defaultButtonStyles } from "./defaultButtonStyles.js";
9
+ import { generateResponsiveButtonCss } from "./generateResponsiveButtonCss.js";
10
+ /**
11
+ * Themed button for use inside an `MjmlColumn`.
12
+ */
13
+ export function MjmlButton({ variant: variantProp, fullWidth, width, href = "#", target = "_blank", className, children, ...restProps }) {
14
+ const theme = useOptionalTheme();
15
+ const themedProps = getThemedProps(theme, variantProp);
16
+ return (_jsx(BaseMjmlButton, { ...themedProps.baseProps, ...restProps, href: href, target: target, width: fullWidth ? "100%" : width, className: clsx("mjmlButton", themedProps.activeVariant && `mjmlButton--${themedProps.activeVariant}`, fullWidth && "mjmlButton--fullWidth", className), children: children }));
17
+ }
18
+ function getThemedProps(theme, variantProp) {
19
+ if (theme === null && variantProp !== undefined) {
20
+ throw new Error("The `variant` prop requires being wrapped in a ThemeProvider or MjmlMailRoot.");
21
+ }
22
+ const themeButton = theme?.button ?? defaultButtonStyles;
23
+ const { defaultVariant, variants, ...baseStyles } = themeButton;
24
+ const activeVariant = variantProp ?? defaultVariant;
25
+ const variantStyles = activeVariant ? variants?.[activeVariant] : undefined;
26
+ const mergedStyles = { ...baseStyles, ...variantStyles };
27
+ const fontWeightDefault = getDefaultOrUndefined(mergedStyles.fontWeight);
28
+ return {
29
+ activeVariant,
30
+ baseProps: {
31
+ color: getDefaultOrUndefined(mergedStyles.color),
32
+ backgroundColor: getDefaultOrUndefined(mergedStyles.backgroundColor),
33
+ border: getDefaultOrUndefined(mergedStyles.border),
34
+ borderRadius: getDefaultOrUndefined(mergedStyles.borderRadius),
35
+ fontFamily: getDefaultOrUndefined(mergedStyles.fontFamily),
36
+ fontSize: getDefaultOrUndefined(mergedStyles.fontSize),
37
+ fontWeight: fontWeightDefault !== undefined ? String(fontWeightDefault) : undefined,
38
+ lineHeight: getDefaultOrUndefined(mergedStyles.lineHeight),
39
+ innerPadding: getDefaultOrUndefined(mergedStyles.padding),
40
+ },
41
+ };
42
+ }
43
+ // The wrapped MJML button has no `background-image` prop, so a gradient goes in a `<style>`
44
+ // rule over the solid `backgroundColor`; variant rules come after the base so they override it.
45
+ // Only the static value lives here; responsive overrides flow through generateResponsiveButtonCss.
46
+ function generateStaticBackgroundImageCss(theme) {
47
+ const { variants, ...baseStyles } = theme.button;
48
+ const cssChunks = [];
49
+ const baseBackgroundImage = getDefaultOrUndefined(baseStyles.backgroundImage);
50
+ if (baseBackgroundImage !== undefined) {
51
+ cssChunks.push(css `
52
+ .mjmlButton a {
53
+ background-image: ${baseBackgroundImage} !important;
54
+ }
55
+ `);
56
+ }
57
+ if (variants) {
58
+ for (const [variantName, variantStyles] of Object.entries(variants)) {
59
+ const backgroundImage = getDefaultOrUndefined(variantStyles?.backgroundImage);
60
+ if (backgroundImage !== undefined) {
61
+ cssChunks.push(css `
62
+ .mjmlButton--${variantName} a {
63
+ background-image: ${backgroundImage} !important;
64
+ }
65
+ `);
66
+ }
67
+ }
68
+ }
69
+ return cssChunks.join("\n");
70
+ }
71
+ export function generateMjmlButtonStyles(theme) {
72
+ return [
73
+ generateStaticBackgroundImageCss(theme),
74
+ generateResponsiveButtonCss(theme, {
75
+ styleSelector: (variantName) => `.mjmlButton--${variantName} a`,
76
+ }),
77
+ ]
78
+ .filter(Boolean)
79
+ .join("\n");
80
+ }
81
+ registerStyles(generateMjmlButtonStyles);
82
+ // The cell is already full-width; this widens the anchor inside it so a gradient covers the
83
+ // whole bar and the whole bar is clickable. Inlined so it survives clients that drop `<style>`.
84
+ registerStyles(css `
85
+ .mjmlButton--fullWidth a {
86
+ display: block !important;
87
+ width: 100% !important;
88
+ box-sizing: border-box !important;
89
+ text-align: center !important;
90
+ }
91
+ `, { inline: true });
@@ -0,0 +1,35 @@
1
+ import type { AnchorHTMLAttributes } from "react";
2
+ import type { ButtonVariantName } from "../../theme/themeTypes.js";
3
+ export interface ButtonProps {
4
+ /**
5
+ * The component's variant to apply, as defined in the theme.
6
+ *
7
+ * Custom variants should be defined in the theme through module augmentation:
8
+ *
9
+ * ```ts
10
+ * declare module "@dextinity/mail-react" {
11
+ * interface ButtonVariants { primary: true; secondary: true }
12
+ * }
13
+ * ```
14
+ *
15
+ * ```ts
16
+ * const theme = createTheme({
17
+ * button: {
18
+ * variants: {
19
+ * primary: { backgroundColor: "#5B4FC7", color: "#FFFFFF" },
20
+ * secondary: { backgroundColor: "#EEEEEE", color: "#222222" },
21
+ * },
22
+ * },
23
+ * });
24
+ * ```
25
+ *
26
+ * @defaultValue The theme's `button.defaultVariant`, when set
27
+ */
28
+ variant?: ButtonVariantName;
29
+ /** When true, the button spans the full width of its container. */
30
+ fullWidth?: boolean;
31
+ /** @defaultValue "#" */
32
+ href?: string;
33
+ /** @defaultValue "_blank" */
34
+ target?: AnchorHTMLAttributes<HTMLAnchorElement>["target"];
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import type { ButtonStyles } from "../../theme/themeTypes.js";
2
+ export declare const defaultButtonStyles: ButtonStyles;
@@ -0,0 +1,9 @@
1
+ export const defaultButtonStyles = {
2
+ color: "#ffffff",
3
+ backgroundColor: "#414141",
4
+ border: "none",
5
+ borderRadius: "3px",
6
+ fontSize: "13px",
7
+ lineHeight: "120%",
8
+ padding: "10px 25px",
9
+ };
@@ -0,0 +1,8 @@
1
+ import type { Theme } from "../../theme/themeTypes.js";
2
+ interface GenerateResponsiveButtonCssOptions {
3
+ /** Selector for variant style overrides, given a variant name. */
4
+ styleSelector: (variantName: string) => string;
5
+ }
6
+ /** Generates responsive CSS media queries for button variant overrides. */
7
+ export declare function generateResponsiveButtonCss(theme: Theme, options: GenerateResponsiveButtonCssOptions): string;
8
+ export {};
@@ -0,0 +1,21 @@
1
+ import { generateResponsiveVariantCss } from "../../styles/generateResponsiveVariantCss.js";
2
+ const buttonProperties = [
3
+ "color",
4
+ "backgroundColor",
5
+ "backgroundImage",
6
+ "border",
7
+ "borderRadius",
8
+ "fontFamily",
9
+ "fontSize",
10
+ "fontWeight",
11
+ "lineHeight",
12
+ "padding",
13
+ ];
14
+ /** Generates responsive CSS media queries for button variant overrides. */
15
+ export function generateResponsiveButtonCss(theme, options) {
16
+ return generateResponsiveVariantCss({
17
+ breakpoints: theme.breakpoints,
18
+ variants: theme.button.variants,
19
+ groups: [{ selector: options.styleSelector, properties: buttonProperties }],
20
+ });
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from "react";
2
+ import type { Theme } from "../../theme/themeTypes.js";
3
+ import type { DividerProps } from "./dividerProps.js";
4
+ export type HtmlDividerProps = DividerProps;
5
+ /**
6
+ * Themed divider for use inside MJML ending tags or outside of the MJML context.
7
+ */
8
+ export declare function HtmlDivider({ variant: variantProp, height: heightProp, backgroundColor: backgroundColorProp, backgroundImage: backgroundImageProp, className, style, }: HtmlDividerProps): ReactNode;
9
+ export declare function generateHtmlDividerStyles(theme: Theme): string;