@comet/mail-react 9.0.0-beta.4 → 9.0.0-beta.6

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 (99) hide show
  1. package/README.md +68 -9
  2. package/lib/blocks/pixelImage/HtmlPixelImageBlock.js +2 -11
  3. package/lib/blocks/pixelImage/MjmlPixelImageBlock.d.ts +2 -2
  4. package/lib/blocks/pixelImage/MjmlPixelImageBlock.js +1 -10
  5. package/lib/blocks.generated.d.ts +1 -0
  6. package/lib/components/button/HtmlButton.d.ts +9 -0
  7. package/lib/components/button/HtmlButton.js +58 -0
  8. package/lib/components/button/MjmlButton.d.ts +10 -0
  9. package/lib/components/button/MjmlButton.js +91 -0
  10. package/lib/components/button/buttonProps.d.ts +35 -0
  11. package/lib/components/button/defaultButtonStyles.d.ts +2 -0
  12. package/lib/components/button/defaultButtonStyles.js +9 -0
  13. package/lib/components/button/generateResponsiveButtonCss.d.ts +8 -0
  14. package/lib/components/button/generateResponsiveButtonCss.js +21 -0
  15. package/lib/components/divider/HtmlDivider.d.ts +9 -0
  16. package/lib/components/divider/HtmlDivider.js +43 -0
  17. package/lib/components/divider/MjmlDivider.d.ts +7 -0
  18. package/lib/components/divider/MjmlDivider.js +13 -0
  19. package/lib/components/divider/defaultDividerStyles.d.ts +2 -0
  20. package/lib/components/divider/defaultDividerStyles.js +4 -0
  21. package/lib/components/divider/dividerProps.d.ts +51 -0
  22. package/lib/components/divider/generateResponsiveDividerCss.d.ts +8 -0
  23. package/lib/components/divider/generateResponsiveDividerCss.js +15 -0
  24. package/lib/components/image/HtmlImage.d.ts +11 -0
  25. package/lib/components/image/HtmlImage.js +23 -0
  26. package/lib/components/image/MjmlImage.d.ts +10 -0
  27. package/lib/components/image/MjmlImage.js +22 -0
  28. package/lib/components/inlineLink/HtmlInlineLink.d.ts +8 -1
  29. package/lib/components/mailRoot/MjmlMailRoot.d.ts +3 -2
  30. package/lib/components/section/MjmlSection.d.ts +7 -1
  31. package/lib/components/text/HtmlText.d.ts +3 -0
  32. package/lib/components/text/MjmlText.d.ts +3 -0
  33. package/lib/components/text/textStyles.d.ts +1 -3
  34. package/lib/components/text/textStyles.js +20 -70
  35. package/lib/components/wrapper/MjmlWrapper.d.ts +9 -2
  36. package/lib/components/wrapper/MjmlWrapper.js +1 -1
  37. package/lib/index.d.ts +14 -2
  38. package/lib/index.js +7 -1
  39. package/lib/styles/generateResponsiveVariantCss.d.ts +26 -0
  40. package/lib/styles/generateResponsiveVariantCss.js +66 -0
  41. package/lib/theme/createTheme.d.ts +3 -1
  42. package/lib/theme/createTheme.js +2 -0
  43. package/lib/theme/defaultTheme.js +4 -0
  44. package/lib/theme/themeTypes.d.ts +98 -0
  45. package/package.json +14 -7
  46. package/lib/__stories__/examples/CustomBackgroundForFooter.stories.d.ts +0 -5
  47. package/lib/__stories__/examples/CustomBackgroundForFooter.stories.js +0 -32
  48. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.d.ts +0 -5
  49. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.js +0 -32
  50. package/lib/__stories__/examples/NotificationEmail.stories.d.ts +0 -5
  51. package/lib/__stories__/examples/NotificationEmail.stories.js +0 -25
  52. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.d.ts +0 -6
  53. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.js +0 -107
  54. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.d.ts +0 -4
  55. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.js +0 -58
  56. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.d.ts +0 -5
  57. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.js +0 -104
  58. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.d.ts +0 -4
  59. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.js +0 -47
  60. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.d.ts +0 -7
  61. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.js +0 -54
  62. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.d.ts +0 -7
  63. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.js +0 -54
  64. package/lib/blocks/pixelImage/__stories__/exampleBlockData.d.ts +0 -2
  65. package/lib/blocks/pixelImage/__stories__/exampleBlockData.js +0 -19
  66. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.js +0 -21
  67. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockData.test.js +0 -205
  68. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.d.ts +0 -12
  69. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.js +0 -56
  70. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.d.ts +0 -7
  71. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.js +0 -18
  72. package/lib/components/section/__stories__/MjmlSection.stories.d.ts +0 -9
  73. package/lib/components/section/__stories__/MjmlSection.stories.js +0 -30
  74. package/lib/components/text/__stories__/HtmlText.stories.d.ts +0 -12
  75. package/lib/components/text/__stories__/HtmlText.stories.js +0 -77
  76. package/lib/components/text/__stories__/MjmlText.stories.d.ts +0 -10
  77. package/lib/components/text/__stories__/MjmlText.stories.js +0 -71
  78. package/lib/components/text/__tests__/HtmlText.test.d.ts +0 -1
  79. package/lib/components/text/__tests__/HtmlText.test.js +0 -157
  80. package/lib/components/text/__tests__/MjmlText.test.d.ts +0 -1
  81. package/lib/components/text/__tests__/MjmlText.test.js +0 -112
  82. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.d.ts +0 -10
  83. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.js +0 -36
  84. package/lib/config/ConfigProvider.test.d.ts +0 -8
  85. package/lib/config/ConfigProvider.test.js +0 -30
  86. package/lib/server/renderMailHtml.test.d.ts +0 -1
  87. package/lib/server/renderMailHtml.test.js +0 -52
  88. package/lib/storybook/replaceImagesWithPublicUrl.test.d.ts +0 -1
  89. package/lib/storybook/replaceImagesWithPublicUrl.test.js +0 -55
  90. package/lib/theme/__stories__/ThemeProvider.stories.d.ts +0 -7
  91. package/lib/theme/__stories__/ThemeProvider.stories.js +0 -23
  92. package/lib/theme/createTheme.test.d.ts +0 -1
  93. package/lib/theme/createTheme.test.js +0 -52
  94. package/lib/theme/responsiveValue.test.d.ts +0 -1
  95. package/lib/theme/responsiveValue.test.js +0 -62
  96. package/lib/utils/css.test.d.ts +0 -1
  97. package/lib/utils/css.test.js +0 -26
  98. /package/lib/{blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.d.ts → components/button/buttonProps.js} +0 -0
  99. /package/lib/{blocks/pixelImage/__tests__/usePixelImageBlockData.test.d.ts → components/divider/dividerProps.js} +0 -0
@@ -1,157 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { describe, expect, it } from "vitest";
4
- import { createTheme } from "../../../theme/createTheme.js";
5
- import { ThemeProvider } from "../../../theme/ThemeProvider.js";
6
- import { HtmlText } from "../HtmlText.js";
7
- // Type-level tests — validated by tsc, not at runtime.
8
- // An unused @ts-expect-error directive causes a compile error if the overloads
9
- // stop rejecting the invalid usage, so these act as regression guards.
10
- // @ts-expect-error href is not valid on the default <td>
11
- void (_jsx(HtmlText, { href: "/foo", children: "text" }));
12
- void ((
13
- // @ts-expect-error colSpan is not valid on <a>
14
- _jsx(HtmlText, { element: "a", colSpan: 2, children: "text" })));
15
- // Own props are always accepted regardless of element — no error expected
16
- void (_jsx(HtmlText, { element: "div", variant: "heading", bottomSpacing: true, children: "text" }));
17
- void (_jsx(HtmlText, { variant: "heading", bottomSpacing: true, children: "text" }));
18
- function renderHtmlText(element) {
19
- return renderToStaticMarkup(element);
20
- }
21
- describe("HtmlText", () => {
22
- it("renders a <td> with base theme styles as inline styles", () => {
23
- const theme = createTheme();
24
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { children: "Hello" }) }));
25
- expect(html).toContain("<td");
26
- expect(html).toContain("Hello");
27
- expect(html).toContain("font-family:Arial, sans-serif");
28
- expect(html).toContain("font-size:16px");
29
- expect(html).toContain("line-height:20px");
30
- });
31
- it("applies variant styles over base styles", () => {
32
- const theme = createTheme({
33
- text: {
34
- variants: {
35
- heading: { fontSize: { default: "32px", mobile: "24px" }, fontWeight: 700 },
36
- },
37
- },
38
- });
39
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { variant: "heading", children: "Title" }) }));
40
- expect(html).toContain("font-size:32px");
41
- expect(html).toContain("font-weight:700");
42
- });
43
- it("inherits base styles not overridden by variant", () => {
44
- const theme = createTheme({
45
- text: {
46
- fontFamily: "Georgia, serif",
47
- variants: {
48
- heading: { fontSize: "32px" },
49
- },
50
- },
51
- });
52
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { variant: "heading", children: "Title" }) }));
53
- expect(html).toContain("font-family:Georgia, serif");
54
- expect(html).toContain("font-size:32px");
55
- });
56
- it("applies defaultVariant when no variant prop is specified", () => {
57
- const theme = createTheme({
58
- text: {
59
- defaultVariant: "body",
60
- variants: {
61
- body: { fontSize: "14px" },
62
- },
63
- },
64
- });
65
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { children: "Text" }) }));
66
- expect(html).toContain("font-size:14px");
67
- });
68
- it("applies bottomSpacing as padding-bottom", () => {
69
- const theme = createTheme({ text: { bottomSpacing: "16px" } });
70
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { bottomSpacing: true, children: "Text" }) }));
71
- expect(html).toContain("padding-bottom:16px");
72
- });
73
- it("uses variant bottomSpacing over base", () => {
74
- const theme = createTheme({
75
- text: {
76
- bottomSpacing: "16px",
77
- variants: {
78
- heading: { bottomSpacing: { default: "24px", mobile: "16px" } },
79
- },
80
- },
81
- });
82
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { variant: "heading", bottomSpacing: true, children: "Title" }) }));
83
- expect(html).toContain("padding-bottom:24px");
84
- });
85
- it("does not apply padding-bottom when bottomSpacing is not set", () => {
86
- const theme = createTheme({ text: { bottomSpacing: "16px" } });
87
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { children: "Text" }) }));
88
- expect(html).not.toContain("padding-bottom");
89
- });
90
- it("always applies the htmlText base class", () => {
91
- const theme = createTheme();
92
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { children: "Hello" }) }));
93
- expect(html).toContain('class="htmlText"');
94
- });
95
- it("applies variant modifier class", () => {
96
- const theme = createTheme({
97
- text: { variants: { heading: { fontSize: "32px" } } },
98
- });
99
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { variant: "heading", children: "Title" }) }));
100
- expect(html).toContain("htmlText--heading");
101
- });
102
- it("applies bottomSpacing modifier class", () => {
103
- const theme = createTheme();
104
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { bottomSpacing: true, children: "Text" }) }));
105
- expect(html).toContain("htmlText--bottomSpacing");
106
- });
107
- it("merges consumer className", () => {
108
- const theme = createTheme();
109
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { className: "custom", children: "Text" }) }));
110
- expect(html).toContain("htmlText");
111
- expect(html).toContain("custom");
112
- });
113
- it("lets user style prop override theme styles", () => {
114
- const theme = createTheme({ text: { fontFamily: "Arial, sans-serif" } });
115
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { style: { fontFamily: "Georgia" }, children: "Hello" }) }));
116
- expect(html).toContain("font-family:Georgia");
117
- });
118
- it("forwards standard td attributes", () => {
119
- const theme = createTheme();
120
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { colSpan: 2, align: "center", children: "Hello" }) }));
121
- expect(html).toContain('colSpan="2"');
122
- expect(html).toContain('align="center"');
123
- });
124
- it("renders a <div> when element is 'div'", () => {
125
- const theme = createTheme();
126
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { element: "div", children: "Hello" }) }));
127
- expect(html).toContain("<div");
128
- expect(html).not.toContain("<td");
129
- expect(html).toContain("Hello");
130
- expect(html).toContain("font-family:Arial, sans-serif");
131
- });
132
- it("renders an <a> with href when element is 'a'", () => {
133
- const theme = createTheme();
134
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { element: "a", href: "/link", children: "Click" }) }));
135
- expect(html).toContain("<a");
136
- expect(html).not.toContain("<td");
137
- expect(html).toContain('href="/link"');
138
- expect(html).toContain("Click");
139
- });
140
- it("applies theme styles and CSS classes to non-td elements", () => {
141
- const theme = createTheme({
142
- text: {
143
- variants: {
144
- heading: { fontSize: "32px", fontWeight: 700 },
145
- },
146
- },
147
- });
148
- const html = renderHtmlText(_jsx(ThemeProvider, { theme: theme, children: _jsx(HtmlText, { element: "div", variant: "heading", bottomSpacing: true, className: "custom", children: "Title" }) }));
149
- expect(html).toContain("<div");
150
- expect(html).toContain("font-size:32px");
151
- expect(html).toContain("font-weight:700");
152
- expect(html).toContain("htmlText");
153
- expect(html).toContain("htmlText--heading");
154
- expect(html).toContain("htmlText--bottomSpacing");
155
- expect(html).toContain("custom");
156
- });
157
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,112 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn } from "@faire/mjml-react";
3
- import { describe, expect, it } from "vitest";
4
- import { renderMailHtml } from "../../../server/renderMailHtml.js";
5
- import { createTheme } from "../../../theme/createTheme.js";
6
- import { MjmlMailRoot } from "../../mailRoot/MjmlMailRoot.js";
7
- import { MjmlSection } from "../../section/MjmlSection.js";
8
- import { generateTextStyles, MjmlText } from "../MjmlText.js";
9
- describe("MjmlText integration", () => {
10
- // Full render pipeline: mjml2html must report no errors for this MjmlText + theme setup.
11
- it("produces no MJML warnings with a variant theme", () => {
12
- const theme = createTheme({
13
- text: {
14
- variants: {
15
- heading: {
16
- fontSize: { default: "32px", mobile: "24px" },
17
- fontWeight: 700,
18
- lineHeight: { default: "40px", mobile: "30px" },
19
- },
20
- body: { fontSize: "14px", lineHeight: "22px" },
21
- },
22
- },
23
- });
24
- const { mjmlWarnings } = renderMailHtml(_jsx(MjmlMailRoot, { theme: theme, children: _jsx(MjmlSection, { children: _jsxs(MjmlColumn, { children: [_jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Heading" }), _jsx(MjmlText, { variant: "body", children: "Body text" }), _jsx(MjmlText, { children: "Base text" })] }) }) }));
25
- expect(mjmlWarnings).toEqual([]);
26
- });
27
- });
28
- describe("generateTextStyles", () => {
29
- it("returns empty CSS when no variants are defined", () => {
30
- const theme = createTheme();
31
- const result = generateTextStyles(theme);
32
- expect(result).toBe("");
33
- });
34
- it("emits style overrides with correct selector for a variant", () => {
35
- const theme = createTheme({
36
- text: {
37
- variants: {
38
- heading: {
39
- fontSize: { default: "32px", mobile: "24px" },
40
- },
41
- },
42
- },
43
- });
44
- const result = generateTextStyles(theme);
45
- expect(result).toContain(".mjmlText--heading > div");
46
- expect(result).toContain("font-size: 24px !important");
47
- });
48
- it("groups multiple properties into a single media query per breakpoint", () => {
49
- const theme = createTheme({
50
- text: {
51
- variants: {
52
- heading: {
53
- fontSize: { default: "32px", mobile: "24px" },
54
- lineHeight: { default: "40px", mobile: "30px" },
55
- },
56
- },
57
- },
58
- });
59
- const result = generateTextStyles(theme);
60
- const mobileMediaQuery = `@media (max-width: ${theme.breakpoints.mobile.value - 1}px)`;
61
- expect(result).toContain(mobileMediaQuery);
62
- // Both declarations should appear within the same media query block
63
- const mediaBlockStart = result.indexOf(mobileMediaQuery);
64
- const blockContent = result.slice(mediaBlockStart);
65
- expect(blockContent).toContain("font-size: 24px !important");
66
- expect(blockContent).toContain("line-height: 30px !important");
67
- });
68
- it("uses compound selector for responsive bottomSpacing", () => {
69
- const theme = createTheme({
70
- text: {
71
- variants: {
72
- heading: {
73
- bottomSpacing: { default: "24px", mobile: "16px" },
74
- },
75
- },
76
- },
77
- });
78
- const result = generateTextStyles(theme);
79
- expect(result).toContain(".mjmlText--bottomSpacing.mjmlText--heading");
80
- expect(result).toContain("padding-bottom: 16px !important");
81
- });
82
- it("emits no media queries for non-responsive variant", () => {
83
- const theme = createTheme({
84
- text: {
85
- variants: {
86
- body: { fontSize: "14px", lineHeight: "22px" },
87
- },
88
- },
89
- });
90
- const result = generateTextStyles(theme);
91
- expect(result).not.toContain("@media");
92
- });
93
- it("keeps style and bottomSpacing overrides separate", () => {
94
- const theme = createTheme({
95
- text: {
96
- variants: {
97
- heading: {
98
- fontSize: { default: "32px", mobile: "24px" },
99
- bottomSpacing: { default: "24px", mobile: "16px" },
100
- },
101
- },
102
- },
103
- });
104
- const result = generateTextStyles(theme);
105
- // Style overrides use "> div" selector
106
- expect(result).toContain(".mjmlText--heading > div");
107
- expect(result).toContain("font-size: 24px !important");
108
- // Spacing overrides use compound selector (no "> div")
109
- expect(result).toContain(".mjmlText--bottomSpacing.mjmlText--heading");
110
- expect(result).toContain("padding-bottom: 16px !important");
111
- });
112
- });
@@ -1,10 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { MjmlWrapper } from "../MjmlWrapper.js";
3
- type Story = StoryObj<typeof MjmlWrapper>;
4
- declare const config: Meta<typeof MjmlWrapper>;
5
- export default config;
6
- export declare const Primary: Story;
7
- export declare const ExplicitBackgroundColor: Story;
8
- export declare const MultipleWrappersWithDifferentBackgrounds: Story;
9
- export declare const TransparentBackground: Story;
10
- export declare const FullWidth: Story;
@@ -1,36 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer } from "@faire/mjml-react";
3
- import { MjmlSection } from "../../section/MjmlSection.js";
4
- import { MjmlText } from "../../text/MjmlText.js";
5
- import { MjmlWrapper } from "../MjmlWrapper.js";
6
- const config = {
7
- title: "Components/MjmlWrapper",
8
- component: MjmlWrapper,
9
- tags: ["autodocs"],
10
- };
11
- export default config;
12
- export const Primary = {
13
- render: (args) => (_jsxs(MjmlWrapper, { ...args, children: [_jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Section content inside a wrapper with the default theme background" }) }) }), _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "A second section; both share the wrapper's background" }) }) })] })),
14
- };
15
- export const ExplicitBackgroundColor = {
16
- args: {
17
- backgroundColor: "#2d4a6e",
18
- },
19
- render: (args) => (_jsx(MjmlWrapper, { ...args, children: _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { color: "#ffffff", bottomSpacing: true, children: "Explicit wrapper background; sections inside do not paint over it" }), _jsx(MjmlText, { color: "#ffffff", children: "A second section shares the wrapper background" }), _jsx(MjmlSpacer, { height: 20 })] }) }) })),
20
- };
21
- export const MultipleWrappersWithDifferentBackgrounds = {
22
- render: (args) => (_jsxs(_Fragment, { children: [_jsxs(MjmlWrapper, { ...args, backgroundColor: "#8fa5bf", children: [_jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 20 }) }) }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Section content inside a wrapper with a different background #1" }) }) }), _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 20 }) }) })] }), _jsx(MjmlWrapper, { ...args, backgroundColor: "#c0c1c4", children: _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { children: "Section content inside a wrapper with a different background #2" }), _jsx(MjmlSpacer, { height: 20 })] }) }) }), _jsx(MjmlWrapper, { ...args, backgroundColor: "#ffffff", children: _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { children: "Section content inside a wrapper with a different background #3" }), _jsx(MjmlSpacer, { height: 20 })] }) }) })] })),
23
- };
24
- export const TransparentBackground = {
25
- args: {
26
- backgroundColor: "transparent",
27
- },
28
- render: (args) => (_jsx(MjmlWrapper, { ...args, children: _jsx(MjmlSection, { children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { children: "Transparent wrapper; the body background shows through" }), _jsx(MjmlSpacer, { height: 20 })] }) }) })),
29
- };
30
- export const FullWidth = {
31
- args: {
32
- fullWidth: true,
33
- backgroundColor: "#2d4a6e",
34
- },
35
- render: (args) => (_jsxs(MjmlWrapper, { ...args, children: [_jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { color: "#ffffff", children: "Full-width wrapper background extends edge-to-edge" }) }) }), _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { color: "#ffffff", children: "The section inside does not override the wrapper's background" }) }) })] })),
36
- };
@@ -1,8 +0,0 @@
1
- declare module "./ConfigProvider.js" {
2
- interface Config {
3
- testKey?: {
4
- value: string;
5
- };
6
- }
7
- }
8
- export {};
@@ -1,30 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlText } from "@faire/mjml-react";
3
- import { renderToStaticMarkup } from "react-dom/server";
4
- import { describe, expect, it } from "vitest";
5
- import { MjmlMailRoot } from "../components/mailRoot/MjmlMailRoot.js";
6
- import { MjmlSection } from "../components/section/MjmlSection.js";
7
- import { renderMailHtml } from "../server/renderMailHtml.js";
8
- import { ConfigProvider, useConfig } from "./ConfigProvider.js";
9
- function Probe() {
10
- const config = useConfig();
11
- return _jsx("span", { "data-config-value": config.testKey?.value ?? "no-config" });
12
- }
13
- describe("useConfig", () => {
14
- it("returns an empty Config when no ConfigProvider is mounted", () => {
15
- const html = renderToStaticMarkup(_jsx(Probe, {}));
16
- expect(html).toContain('data-config-value="no-config"');
17
- });
18
- });
19
- describe("MjmlMailRoot config integration", () => {
20
- it("makes the config value available to descendants via useConfig", () => {
21
- const { html, mjmlWarnings } = renderMailHtml(_jsx(MjmlMailRoot, { config: { testKey: { value: "from-root" } }, children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: _jsx(Probe, {}) }) }) }) }));
22
- expect(mjmlWarnings).toEqual([]);
23
- expect(html).toContain('data-config-value="from-root"');
24
- });
25
- it("does not shadow an outer ConfigProvider when no config prop is passed", () => {
26
- const { html, mjmlWarnings } = renderMailHtml(_jsx(ConfigProvider, { config: { testKey: { value: "from-outer" } }, children: _jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: _jsx(Probe, {}) }) }) }) }) }));
27
- expect(mjmlWarnings).toEqual([]);
28
- expect(html).toContain('data-config-value="from-outer"');
29
- });
30
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,52 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { MjmlClass, MjmlColumn, MjmlText, MjmlTitle } from "@faire/mjml-react";
3
- import { describe, expect, it } from "vitest";
4
- import { MjmlMailRoot } from "../components/mailRoot/MjmlMailRoot.js";
5
- import { MjmlSection } from "../components/section/MjmlSection.js";
6
- import { registerStyles } from "../styles/registerStyles.js";
7
- import { css } from "../utils/css.js";
8
- import { renderMailHtml } from "./renderMailHtml.js";
9
- describe("server/renderMailHtml", () => {
10
- it("produces HTML with a doctype", () => {
11
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello" }) }) }) }));
12
- expect(html.toLowerCase()).toMatch(/^<!doctype html>/);
13
- });
14
- it("includes the passed-in text content", () => {
15
- const textContent = "Welcome to @comet/mail-react";
16
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: textContent }) }) }) }));
17
- expect(html).toContain(textContent);
18
- });
19
- it("includes registered styles in the rendered HTML", () => {
20
- registerStyles(css `
21
- .myComponent {
22
- color: red;
23
- }
24
- `);
25
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello" }) }) }) }));
26
- expect(html).toContain(".myComponent");
27
- expect(html).toContain("color: red");
28
- });
29
- it("includes function-style registered styles resolved with the theme", () => {
30
- registerStyles((theme) => css `
31
- .themed {
32
- width: ${theme.sizes.bodyWidth}px;
33
- }
34
- `);
35
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello" }) }) }) }));
36
- expect(html).toContain(".themed");
37
- expect(html).toContain("width: 600px");
38
- });
39
- it("renders the head slot content inside <mj-head>", () => {
40
- const titleText = "Welcome to Comet";
41
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { head: _jsx(MjmlTitle, { children: titleText }), children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello" }) }) }) }));
42
- expect(html).toContain(`<title>${titleText}</title>`);
43
- });
44
- it("renders the attributes slot content inside <mj-attributes>", () => {
45
- const { html } = renderMailHtml(_jsx(MjmlMailRoot, { attributes: _jsx(MjmlClass, { name: "cta", color: "#FF0000" }), children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { mjmlClass: "cta", children: "Hello" }) }) }) }));
46
- expect(html).toContain("color:#FF0000");
47
- });
48
- it("produces no MJML warnings for a valid component tree", () => {
49
- const { mjmlWarnings } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "No warnings expected" }) }) }) }));
50
- expect(mjmlWarnings).toEqual([]);
51
- });
52
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,55 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { replaceImagesWithPublicUrl } from "./replaceImagesWithPublicUrl.js";
3
- describe("replaceImagesWithPublicUrl", () => {
4
- it("replaces src with picsum URL using doubled dimensions", () => {
5
- const html = '<img src="original.jpg" width="300" height="200" />';
6
- const result = replaceImagesWithPublicUrl(html);
7
- expect(result).toBe('<img src="https://picsum.photos/seed/0/600/400" width="300" height="200" />');
8
- });
9
- it("handles height before width", () => {
10
- const html = '<img src="original.jpg" height="200" width="300" />';
11
- const result = replaceImagesWithPublicUrl(html);
12
- expect(result).toBe('<img src="https://picsum.photos/seed/0/600/400" height="200" width="300" />');
13
- });
14
- it("handles attributes before width and height", () => {
15
- const html = '<img alt="photo" src="original.jpg" width="100" height="50" />';
16
- const result = replaceImagesWithPublicUrl(html);
17
- expect(result).toBe('<img alt="photo" src="https://picsum.photos/seed/0/200/100" width="100" height="50" />');
18
- });
19
- it("handles attributes between width and height", () => {
20
- const html = '<img src="original.jpg" width="100" alt="photo" height="50" />';
21
- const result = replaceImagesWithPublicUrl(html);
22
- expect(result).toBe('<img src="https://picsum.photos/seed/0/200/100" width="100" alt="photo" height="50" />');
23
- });
24
- it("handles attributes after width and height", () => {
25
- const html = '<img src="original.jpg" width="100" height="50" alt="photo" />';
26
- const result = replaceImagesWithPublicUrl(html);
27
- expect(result).toBe('<img src="https://picsum.photos/seed/0/200/100" width="100" height="50" alt="photo" />');
28
- });
29
- it("leaves img tags without width untouched", () => {
30
- const html = '<img src="original.jpg" height="200" />';
31
- const result = replaceImagesWithPublicUrl(html);
32
- expect(result).toBe('<img src="original.jpg" height="200" />');
33
- });
34
- it("leaves img tags without height untouched", () => {
35
- const html = '<img src="original.jpg" width="300" />';
36
- const result = replaceImagesWithPublicUrl(html);
37
- expect(result).toBe('<img src="original.jpg" width="300" />');
38
- });
39
- it("increments seed for each image", () => {
40
- const html = '<img src="a.jpg" width="100" height="100" /><img src="b.jpg" width="200" height="150" />';
41
- const result = replaceImagesWithPublicUrl(html);
42
- expect(result).toBe('<img src="https://picsum.photos/seed/0/200/200" width="100" height="100" />' +
43
- '<img src="https://picsum.photos/seed/1/400/300" width="200" height="150" />');
44
- });
45
- it("returns unchanged html when there are no img tags", () => {
46
- const html = "<p>Hello world</p>";
47
- const result = replaceImagesWithPublicUrl(html);
48
- expect(result).toBe("<p>Hello world</p>");
49
- });
50
- it("handles non-self-closing img tags", () => {
51
- const html = '<img src="original.jpg" width="100" height="50">';
52
- const result = replaceImagesWithPublicUrl(html);
53
- expect(result).toBe('<img src="https://picsum.photos/seed/0/200/100" width="100" height="50">');
54
- });
55
- });
@@ -1,7 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { ThemeProvider } from "../ThemeProvider.js";
3
- type Story = StoryObj<typeof ThemeProvider>;
4
- declare const config: Meta<typeof ThemeProvider>;
5
- export default config;
6
- export declare const Basic: Story;
7
- export declare const CustomTheme: Story;
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlText } from "@faire/mjml-react";
3
- import { MjmlSection } from "../../components/section/MjmlSection.js";
4
- import { createBreakpoint } from "../createBreakpoint.js";
5
- import { createTheme } from "../createTheme.js";
6
- import { ThemeProvider } from "../ThemeProvider.js";
7
- const config = {
8
- title: "Components/ThemeProvider",
9
- component: ThemeProvider,
10
- tags: ["autodocs"],
11
- };
12
- export default config;
13
- export const Basic = {
14
- render: () => (_jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Using the default theme from the decorator." }) }) })),
15
- };
16
- const narrowTheme = createTheme({
17
- sizes: { bodyWidth: 400 },
18
- breakpoints: { mobile: createBreakpoint(360) },
19
- });
20
- export const CustomTheme = {
21
- parameters: { theme: narrowTheme },
22
- render: () => (_jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "This email uses a 400px body width and 360px mobile breakpoint." }) }) })),
23
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,52 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createTheme } from "./createTheme.js";
3
- import { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./responsiveValue.js";
4
- describe("createTheme text", () => {
5
- it("includes default text values when no overrides given", () => {
6
- const theme = createTheme();
7
- expect(theme.text.fontFamily).toBe("Arial, sans-serif");
8
- expect(theme.text.fontSize).toBe("16px");
9
- expect(theme.text.lineHeight).toBe("20px");
10
- expect(theme.text.bottomSpacing).toBe("16px");
11
- });
12
- it("shallow-merges text overrides with defaults", () => {
13
- const theme = createTheme({ text: { fontFamily: "Georgia, serif", fontSize: "18px" } });
14
- expect(theme.text.fontFamily).toBe("Georgia, serif");
15
- expect(theme.text.fontSize).toBe("18px");
16
- expect(theme.text.lineHeight).toBe("20px");
17
- expect(theme.text.bottomSpacing).toBe("16px");
18
- });
19
- it("passes through variant definitions", () => {
20
- const theme = createTheme({
21
- text: {
22
- variants: {
23
- heading: { fontSize: { default: "32px", mobile: "24px" } },
24
- },
25
- },
26
- });
27
- expect(theme.text.variants).toBeDefined();
28
- expect(theme.text.variants?.heading).toEqual({ fontSize: { default: "32px", mobile: "24px" } });
29
- });
30
- it("does not set defaultVariant or variants by default", () => {
31
- const theme = createTheme();
32
- expect(theme.text.defaultVariant).toBeUndefined();
33
- expect(theme.text.variants).toBeUndefined();
34
- });
35
- });
36
- describe("createTheme contentIndentation", () => {
37
- it("has default contentIndentation with responsive mobile value", () => {
38
- const theme = createTheme();
39
- expect(getDefaultFromResponsiveValue(theme.sizes.contentIndentation)).toBe(32);
40
- expect(getResponsiveOverrides(theme.sizes.contentIndentation)).toContainEqual({ breakpointKey: "mobile", value: 16 });
41
- });
42
- it("allows overriding contentIndentation with a plain number", () => {
43
- const theme = createTheme({ sizes: { contentIndentation: 30 } });
44
- expect(getDefaultFromResponsiveValue(theme.sizes.contentIndentation)).toBe(30);
45
- expect(getResponsiveOverrides(theme.sizes.contentIndentation)).toEqual([]);
46
- });
47
- it("allows overriding contentIndentation with an object", () => {
48
- const theme = createTheme({ sizes: { contentIndentation: { default: 30, mobile: 15 } } });
49
- expect(getDefaultFromResponsiveValue(theme.sizes.contentIndentation)).toBe(30);
50
- expect(getResponsiveOverrides(theme.sizes.contentIndentation)).toContainEqual({ breakpointKey: "mobile", value: 15 });
51
- });
52
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,62 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { getDefaultFromResponsiveValue, getDefaultOrUndefined, getResponsiveOverrides } from "./responsiveValue.js";
3
- describe("getDefaultFromResponsiveValue", () => {
4
- it("returns the value when given a plain number", () => {
5
- expect(getDefaultFromResponsiveValue(20)).toBe(20);
6
- });
7
- it("returns the default property from an object", () => {
8
- expect(getDefaultFromResponsiveValue({ default: 20, mobile: 10 })).toBe(20);
9
- });
10
- it("returns the default property when only default is present", () => {
11
- expect(getDefaultFromResponsiveValue({ default: 42 })).toBe(42);
12
- });
13
- it("works with string type", () => {
14
- expect(getDefaultFromResponsiveValue({ default: "24px", mobile: "20px" })).toBe("24px");
15
- });
16
- it("returns a plain string value", () => {
17
- expect(getDefaultFromResponsiveValue("24px")).toBe("24px");
18
- });
19
- });
20
- describe("getResponsiveOverrides", () => {
21
- it("returns an empty array for a plain number", () => {
22
- expect(getResponsiveOverrides(20)).toEqual([]);
23
- });
24
- it("returns an empty array for an object with only default", () => {
25
- expect(getResponsiveOverrides({ default: 42 })).toEqual([]);
26
- });
27
- it("returns overrides for non-default keys", () => {
28
- const result = getResponsiveOverrides({ default: 20, mobile: 10 });
29
- expect(result).toEqual([{ breakpointKey: "mobile", value: 10 }]);
30
- });
31
- it("returns multiple overrides for multiple breakpoint keys", () => {
32
- const value = { default: 30, mobile: 10 };
33
- const result = getResponsiveOverrides(value);
34
- expect(result).toContainEqual({ breakpointKey: "mobile", value: 10 });
35
- expect(result).not.toContainEqual(expect.objectContaining({ breakpointKey: "default" }));
36
- });
37
- it("never includes the default key in overrides", () => {
38
- const result = getResponsiveOverrides({ default: 20, mobile: 10 });
39
- expect(result.every((o) => o.breakpointKey !== "default")).toBe(true);
40
- });
41
- it("works with string type", () => {
42
- const result = getResponsiveOverrides({ default: "24px", mobile: "20px" });
43
- expect(result).toEqual([{ breakpointKey: "mobile", value: "20px" }]);
44
- });
45
- });
46
- describe("getDefaultOrUndefined", () => {
47
- it("returns undefined when given undefined", () => {
48
- expect(getDefaultOrUndefined(undefined)).toBeUndefined();
49
- });
50
- it("returns the value when given a plain value", () => {
51
- expect(getDefaultOrUndefined(20)).toBe(20);
52
- });
53
- it("returns the default property from an object", () => {
54
- expect(getDefaultOrUndefined({ default: 20, mobile: 10 })).toBe(20);
55
- });
56
- it("works with string type", () => {
57
- expect(getDefaultOrUndefined("24px")).toBe("24px");
58
- });
59
- it("returns default from a string responsive object", () => {
60
- expect(getDefaultOrUndefined({ default: "32px", mobile: "24px" })).toBe("32px");
61
- });
62
- });
@@ -1 +0,0 @@
1
- export {};