@comet/mail-react 9.0.0-beta.1 → 9.0.0-beta.2

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 (47) hide show
  1. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.d.ts +5 -0
  2. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.js +32 -0
  3. package/lib/__stories__/examples/NotificationEmail.stories.d.ts +5 -0
  4. package/lib/__stories__/examples/NotificationEmail.stories.js +25 -0
  5. package/lib/__stories__/examples/TextWithImageEmail.stories.d.ts +5 -0
  6. package/lib/__stories__/examples/TextWithImageEmail.stories.js +56 -0
  7. package/lib/components/inlineLink/HtmlInlineLink.d.ts +10 -0
  8. package/lib/components/inlineLink/HtmlInlineLink.js +35 -0
  9. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.d.ts +12 -0
  10. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.js +56 -0
  11. package/lib/components/mailRoot/MjmlMailRoot.js +1 -1
  12. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.d.ts +1 -0
  13. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.js +6 -1
  14. package/lib/components/section/MjmlSection.d.ts +1 -1
  15. package/lib/components/section/MjmlSection.js +3 -2
  16. package/lib/components/section/__stories__/MjmlSection.stories.d.ts +1 -0
  17. package/lib/components/section/__stories__/MjmlSection.stories.js +6 -0
  18. package/lib/components/text/HtmlText.d.ts +48 -0
  19. package/lib/components/text/HtmlText.js +47 -0
  20. package/lib/components/text/MjmlText.d.ts +37 -0
  21. package/lib/components/text/MjmlText.js +65 -0
  22. package/lib/components/text/OutlookTextStyleContext.d.ts +9 -0
  23. package/lib/components/text/OutlookTextStyleContext.js +10 -0
  24. package/lib/components/text/__stories__/HtmlText.stories.d.ts +12 -0
  25. package/lib/components/text/__stories__/HtmlText.stories.js +77 -0
  26. package/lib/components/text/__stories__/MjmlText.stories.d.ts +10 -0
  27. package/lib/components/text/__stories__/MjmlText.stories.js +71 -0
  28. package/lib/components/text/__tests__/HtmlText.test.d.ts +1 -0
  29. package/lib/components/text/__tests__/HtmlText.test.js +157 -0
  30. package/lib/components/text/__tests__/MjmlText.test.d.ts +1 -0
  31. package/lib/components/text/__tests__/MjmlText.test.js +112 -0
  32. package/lib/components/text/textStyles.d.ts +12 -0
  33. package/lib/components/text/textStyles.js +69 -0
  34. package/lib/index.d.ts +8 -2
  35. package/lib/index.js +4 -1
  36. package/lib/storybook/MailRendererDecorator.d.ts +7 -1
  37. package/lib/storybook/MailRendererDecorator.js +2 -2
  38. package/lib/theme/__stories__/ThemeProvider.stories.js +2 -2
  39. package/lib/theme/createTheme.d.ts +5 -1
  40. package/lib/theme/createTheme.js +6 -0
  41. package/lib/theme/createTheme.test.js +32 -0
  42. package/lib/theme/defaultTheme.js +12 -0
  43. package/lib/theme/responsiveValue.d.ts +6 -0
  44. package/lib/theme/responsiveValue.js +10 -0
  45. package/lib/theme/responsiveValue.test.js +18 -1
  46. package/lib/theme/themeTypes.d.ts +59 -0
  47. package/package.json +7 -4
@@ -0,0 +1,5 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ declare const config: Meta;
3
+ export default config;
4
+ type Story = StoryObj;
5
+ export declare const Default: Story;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MjmlColumn, MjmlSpacer, MjmlTable } from "@faire/mjml-react";
3
+ import { HtmlInlineLink } from "../../components/inlineLink/HtmlInlineLink.js";
4
+ import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
5
+ import { MjmlSection } from "../../components/section/MjmlSection.js";
6
+ import { HtmlText } from "../../components/text/HtmlText.js";
7
+ import { MjmlText } from "../../components/text/MjmlText.js";
8
+ import { createTheme } from "../../theme/createTheme.js";
9
+ import { ThemeProvider } from "../../theme/ThemeProvider.js";
10
+ const config = {
11
+ title: "Examples/CustomNestedFooterTheme",
12
+ parameters: { mailRoot: false },
13
+ };
14
+ export default config;
15
+ export const Default = {
16
+ render: () => {
17
+ const theme = createTheme({
18
+ text: {
19
+ defaultVariant: "body",
20
+ variants: {
21
+ heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
22
+ body: { fontSize: "14px", lineHeight: "20px" },
23
+ legal: { fontSize: "12px", lineHeight: "18px" },
24
+ },
25
+ },
26
+ });
27
+ const footerTheme = structuredClone(theme);
28
+ footerTheme.colors.background.content = "#2d4a6e";
29
+ footerTheme.text.color = "#c8d8e9";
30
+ return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "This is a notification email" }), _jsx(MjmlText, { bottomSpacing: true, children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia. Occaecati vel possimus similique reiciendis possimus iure rerum sit architecto." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsxs(ThemeProvider, { theme: footerTheme, children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { align: "center", bottomSpacing: true, children: "\u00A9 2026 Company Name \u2013 All rights reserved" }), _jsx(MjmlText, { variant: "legal", align: "center", bottomSpacing: true, children: "Legal text, corporis eos et quia. Assumenda eum maiores esse. Voluptas laudantium cupiditate aut repudiandae iste fugiat nam. Quas in debitis. Sed laudantium illum aut occaecati excepturi veniam harum reprehenderit." })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlTable, { width: "auto", align: "center", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/privacy", children: "Privacy Policy" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/imprint", children: "Imprint" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com", children: "Website" }) })] }) }) }), _jsx(MjmlSpacer, { height: 20 })] }) })] })] }));
31
+ },
32
+ };
@@ -0,0 +1,5 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ declare const config: Meta;
3
+ export default config;
4
+ type Story = StoryObj;
5
+ export declare const Default: Story;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MjmlColumn, MjmlSpacer } from "@faire/mjml-react";
3
+ import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
4
+ import { MjmlSection } from "../../components/section/MjmlSection.js";
5
+ import { MjmlText } from "../../components/text/MjmlText.js";
6
+ import { createTheme } from "../../theme/createTheme.js";
7
+ const config = {
8
+ title: "Examples/NotificationEmail",
9
+ parameters: { mailRoot: false },
10
+ };
11
+ export default config;
12
+ export const Default = {
13
+ render: () => {
14
+ const theme = createTheme({
15
+ text: {
16
+ defaultVariant: "body",
17
+ variants: {
18
+ heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
19
+ body: { fontSize: "16px", lineHeight: "24px" },
20
+ },
21
+ },
22
+ });
23
+ return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "This is a notification email" }), _jsx(MjmlText, { bottomSpacing: true, children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia. Occaecati vel possimus similique reiciendis possimus iure rerum sit architecto." }), _jsx(MjmlSpacer, { height: 30 })] }) })] }));
24
+ },
25
+ };
@@ -0,0 +1,5 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ declare const config: Meta;
3
+ export default config;
4
+ type Story = StoryObj;
5
+ export declare const Default: Story;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MjmlColumn, MjmlImage, MjmlSpacer } from "@faire/mjml-react";
3
+ import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
4
+ import { MjmlSection } from "../../components/section/MjmlSection.js";
5
+ import { MjmlText } from "../../components/text/MjmlText.js";
6
+ import { registerStyles } from "../../styles/registerStyles.js";
7
+ import { createTheme } from "../../theme/createTheme.js";
8
+ import { getDefaultFromResponsiveValue } from "../../theme/responsiveValue.js";
9
+ import { css } from "../../utils/css.js";
10
+ const config = {
11
+ title: "Examples/TextWithImageEmail",
12
+ parameters: { mailRoot: false },
13
+ };
14
+ export default config;
15
+ export const Default = {
16
+ render: () => {
17
+ const theme = createTheme({
18
+ text: {
19
+ defaultVariant: "body",
20
+ variants: {
21
+ heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
22
+ body: { fontSize: "16px", lineHeight: "24px" },
23
+ },
24
+ },
25
+ });
26
+ const IMAGE_WIDTH = 120;
27
+ const IMAGE_TEXT_GAP = 20;
28
+ registerStyles((theme) => css `
29
+ ${theme.breakpoints.default.belowMediaQuery} {
30
+ .textWithImageEmail__textColumn {
31
+ width: calc(100% - ${IMAGE_WIDTH}px) !important;
32
+ max-width: calc(100% - ${IMAGE_WIDTH}px) !important;
33
+ }
34
+ }
35
+
36
+ ${theme.breakpoints.mobile.belowMediaQuery} {
37
+ .textWithImageEmail__imageColumn {
38
+ margin-bottom: 10px;
39
+ }
40
+
41
+ .textWithImageEmail__textColumn {
42
+ width: 100% !important;
43
+ max-width: 100% !important;
44
+ }
45
+
46
+ .textWithImageEmail__textColumn > table > tbody > tr > td {
47
+ padding-left: 0 !important;
48
+ }
49
+ }
50
+ `);
51
+ const sectionIndent = getDefaultFromResponsiveValue(theme.sizes.contentIndentation);
52
+ const sectionInnerWidth = theme.sizes.bodyWidth - 2 * sectionIndent;
53
+ const textColumnWidth = sectionInnerWidth - IMAGE_WIDTH;
54
+ return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) }), _jsxs(MjmlSection, { indent: true, children: [_jsx(MjmlColumn, { className: "textWithImageEmail__imageColumn", width: `${IMAGE_WIDTH}px`, verticalAlign: "middle", children: _jsx(MjmlImage, { src: `https://picsum.photos/seed/1/${IMAGE_WIDTH}/150`, alt: "Featured image", align: "center", width: IMAGE_WIDTH }) }), _jsxs(MjmlColumn, { className: "textWithImageEmail__textColumn", width: `${textColumnWidth}px`, paddingLeft: `${IMAGE_TEXT_GAP}px`, verticalAlign: "middle", children: [_jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Responsive Text-Image" }), _jsx(MjmlText, { children: "Demonstrates a responsive text-image layout with a fixed-width image column and a text column taking up remaining space." })] })] }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] }));
55
+ },
56
+ };
@@ -0,0 +1,10 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ export type HtmlInlineLinkProps = ComponentProps<"a">;
3
+ /**
4
+ * Inline link styled to match the surrounding text, for use inside `HtmlText` or `MjmlText`.
5
+ *
6
+ * Applies explicit text styles from the parent text component's context so that
7
+ * Outlook Desktop (which overrides `<a>` tags with its built-in "Hyperlink" style)
8
+ * renders the link with the correct font and color.
9
+ */
10
+ export declare function HtmlInlineLink({ className, style, target, ...restProps }: HtmlInlineLinkProps): ReactNode;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { registerStyles } from "../../styles/registerStyles.js";
4
+ import { css } from "../../utils/css.js";
5
+ import { useOutlookTextStyle } from "../text/OutlookTextStyleContext.js";
6
+ /**
7
+ * Inline link styled to match the surrounding text, for use inside `HtmlText` or `MjmlText`.
8
+ *
9
+ * Applies explicit text styles from the parent text component's context so that
10
+ * Outlook Desktop (which overrides `<a>` tags with its built-in "Hyperlink" style)
11
+ * renders the link with the correct font and color.
12
+ */
13
+ export function HtmlInlineLink({ className, style, target = "_blank", ...restProps }) {
14
+ const outlookTextStyle = useOutlookTextStyle();
15
+ const baseStyle = {
16
+ fontFamily: outlookTextStyle?.fontFamily ?? "inherit",
17
+ fontSize: outlookTextStyle?.fontSize ?? "inherit",
18
+ lineHeight: outlookTextStyle?.lineHeight ?? "inherit",
19
+ fontWeight: outlookTextStyle?.fontWeight ?? "inherit",
20
+ color: outlookTextStyle?.color ?? "inherit",
21
+ textDecoration: "underline",
22
+ };
23
+ return _jsx("a", { className: clsx("htmlInlineLink", className), style: { ...baseStyle, ...style }, target: target, ...restProps });
24
+ }
25
+ registerStyles((theme) => css `
26
+ ${theme.breakpoints.default.belowMediaQuery} {
27
+ .htmlInlineLink {
28
+ font-family: inherit !important;
29
+ font-size: inherit !important;
30
+ line-height: inherit !important;
31
+ font-weight: inherit !important;
32
+ color: inherit !important;
33
+ }
34
+ }
35
+ `);
@@ -0,0 +1,12 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { HtmlInlineLink } from "../HtmlInlineLink.js";
3
+ type Story = StoryObj<typeof HtmlInlineLink>;
4
+ declare const config: Meta<typeof HtmlInlineLink>;
5
+ export default config;
6
+ export declare const InText: Story;
7
+ /**
8
+ * Using `!important` overrides the component's responsive `inherit !important`
9
+ * reset, ensuring the custom color persists on both desktop and mobile.
10
+ */
11
+ export declare const CustomColorOverride: Story;
12
+ export declare const WithTextVariants: Story;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { MjmlColumn } from "@faire/mjml-react";
3
+ import { createTheme } from "../../../theme/createTheme.js";
4
+ import { MjmlSection } from "../../section/MjmlSection.js";
5
+ import { MjmlText } from "../../text/MjmlText.js";
6
+ import { HtmlInlineLink } from "../HtmlInlineLink.js";
7
+ const config = {
8
+ title: "Components/HtmlInlineLink",
9
+ component: HtmlInlineLink,
10
+ tags: ["autodocs"],
11
+ };
12
+ export default config;
13
+ export const InText = {
14
+ parameters: {
15
+ theme: createTheme({
16
+ text: {
17
+ fontFamily: "Arial, sans-serif",
18
+ fontSize: "16px",
19
+ lineHeight: "24px",
20
+ color: "#333333",
21
+ },
22
+ }),
23
+ },
24
+ render: () => (_jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsxs(MjmlText, { children: ["Visit our ", _jsx(HtmlInlineLink, { href: "https://example.com", children: "website" }), " for more information."] }) }) })),
25
+ };
26
+ /**
27
+ * Using `!important` overrides the component's responsive `inherit !important`
28
+ * reset, ensuring the custom color persists on both desktop and mobile.
29
+ */
30
+ export const CustomColorOverride = {
31
+ parameters: {
32
+ theme: createTheme({
33
+ text: {
34
+ fontFamily: "Arial, sans-serif",
35
+ fontSize: "16px",
36
+ color: "#333333",
37
+ },
38
+ }),
39
+ },
40
+ render: () => (_jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsxs(MjmlText, { children: ["Click", " ", _jsx(HtmlInlineLink, { href: "https://example.com", style: { color: "#0066cc !important" }, children: "here" }), " ", "to continue."] }) }) })),
41
+ };
42
+ export const WithTextVariants = {
43
+ parameters: {
44
+ theme: createTheme({
45
+ text: {
46
+ fontFamily: "Arial, sans-serif",
47
+ color: "#333333",
48
+ variants: {
49
+ heading: { fontSize: "32px", fontWeight: 700, lineHeight: "40px" },
50
+ body: { fontSize: "16px", lineHeight: "24px" },
51
+ },
52
+ },
53
+ }),
54
+ },
55
+ render: () => (_jsx(MjmlSection, { children: _jsxs(MjmlColumn, { children: [_jsxs(MjmlText, { variant: "heading", bottomSpacing: true, children: ["Welcome to ", _jsx(HtmlInlineLink, { href: "https://example.com", children: "our platform" })] }), _jsxs(MjmlText, { variant: "body", children: ["Explore our ", _jsx(HtmlInlineLink, { href: "https://example.com/features", children: "features" }), " and start building today."] })] }) })),
56
+ };
@@ -16,5 +16,5 @@ import { ThemeProvider } from "../../theme/ThemeProvider.js";
16
16
  */
17
17
  export function MjmlMailRoot({ theme: themeProp, children }) {
18
18
  const theme = themeProp ?? createTheme();
19
- return (_jsx(ThemeProvider, { theme: theme, children: _jsxs(Mjml, { children: [_jsxs(MjmlHead, { children: [_jsx(MjmlAttributes, { children: _jsx(MjmlAll, { padding: "0" }) }), _jsx(MjmlBreakpoint, { width: `${theme.breakpoints.mobile.value}px` }), _jsx(Styles, {})] }), _jsx(MjmlBody, { width: theme.sizes.bodyWidth, children: children })] }) }));
19
+ return (_jsx(ThemeProvider, { theme: theme, children: _jsxs(Mjml, { children: [_jsxs(MjmlHead, { children: [_jsx(MjmlAttributes, { children: _jsx(MjmlAll, { padding: "0", fontFamily: theme.text.fontFamily }) }), _jsx(MjmlBreakpoint, { width: `${theme.breakpoints.mobile.value}px` }), _jsx(Styles, {})] }), _jsx(MjmlBody, { width: theme.sizes.bodyWidth, backgroundColor: theme.colors.background.body, children: children })] }) }));
20
20
  }
@@ -4,3 +4,4 @@ type Story = StoryObj<typeof MjmlMailRoot>;
4
4
  declare const config: Meta<typeof MjmlMailRoot>;
5
5
  export default config;
6
6
  export declare const Basic: Story;
7
+ export declare const CustomBodyBackground: Story;
@@ -1,13 +1,18 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { MjmlColumn, MjmlText } from "@faire/mjml-react";
3
+ import { createTheme } from "../../../theme/createTheme.js";
3
4
  import { MjmlSection } from "../../section/MjmlSection.js";
4
5
  import { MjmlMailRoot } from "../MjmlMailRoot.js";
5
6
  const config = {
6
7
  title: "Components/MjmlMailRoot",
7
8
  component: MjmlMailRoot,
8
9
  tags: ["autodocs"],
10
+ parameters: { mailRoot: false },
9
11
  };
10
12
  export default config;
11
13
  export const Basic = {
12
- render: () => (_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello from MjmlMailRoot" }) }) }) })),
14
+ render: () => (_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Hello from MjmlMailRoot" }) }) }) })),
15
+ };
16
+ export const CustomBodyBackground = {
17
+ render: () => (_jsx(MjmlMailRoot, { theme: createTheme({ colors: { background: { body: "#EAEAEA" } } }), children: _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Custom body background color" }) }) }) })),
13
18
  };
@@ -12,4 +12,4 @@ export type MjmlSectionProps = IMjmlSectionProps & {
12
12
  };
13
13
  };
14
14
  /** A section wrapper for email layouts. Must be a direct child of `MjmlBody`. */
15
- export declare function MjmlSection({ children, indent, disableResponsiveBehavior, slotProps, className, ...props }: MjmlSectionProps): ReactNode;
15
+ export declare function MjmlSection({ children, indent, disableResponsiveBehavior, slotProps, className, ...restProps }: MjmlSectionProps): ReactNode;
@@ -6,11 +6,12 @@ import { getDefaultFromResponsiveValue, getResponsiveOverrides } from "../../the
6
6
  import { useOptionalTheme } from "../../theme/ThemeProvider.js";
7
7
  import { css } from "../../utils/css.js";
8
8
  /** A section wrapper for email layouts. Must be a direct child of `MjmlBody`. */
9
- export function MjmlSection({ children, indent, disableResponsiveBehavior, slotProps, className, ...props }) {
9
+ export function MjmlSection({ children, indent, disableResponsiveBehavior, slotProps, className, ...restProps }) {
10
10
  const theme = useOptionalTheme();
11
11
  const indentProps = indent ? getIndentProps(theme) : {};
12
12
  const resolvedClassName = clsx("mjmlSection", indent && "mjmlSection--indented", className);
13
- return (_jsx(BaseMjmlSection, { className: resolvedClassName, ...indentProps, ...props, children: disableResponsiveBehavior ? _jsx(MjmlGroup, { ...slotProps?.group, children: children }) : _jsx(_Fragment, { children: children }) }));
13
+ const themeBackgroundProps = theme ? { backgroundColor: theme.colors.background.content } : {};
14
+ return (_jsx(BaseMjmlSection, { className: resolvedClassName, ...themeBackgroundProps, ...indentProps, ...restProps, children: disableResponsiveBehavior ? _jsx(MjmlGroup, { ...slotProps?.group, children: children }) : _jsx(_Fragment, { children: children }) }));
14
15
  }
15
16
  function getIndentProps(theme) {
16
17
  if (theme === null) {
@@ -5,4 +5,5 @@ declare const config: Meta<typeof MjmlSection>;
5
5
  export default config;
6
6
  export declare const Primary: Story;
7
7
  export declare const Indented: Story;
8
+ export declare const ExplicitBackgroundColor: Story;
8
9
  export declare const DisabledResponsiveBehavior: Story;
@@ -16,6 +16,12 @@ export const Indented = {
16
16
  },
17
17
  render: (args) => (_jsx(MjmlSection, { ...args, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Indented section content" }) }) })),
18
18
  };
19
+ export const ExplicitBackgroundColor = {
20
+ args: {
21
+ backgroundColor: "#FF0000",
22
+ },
23
+ render: (args) => (_jsx(MjmlSection, { ...args, children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "Explicit backgroundColor overrides theme default" }) }) })),
24
+ };
19
25
  export const DisabledResponsiveBehavior = {
20
26
  args: {
21
27
  disableResponsiveBehavior: true,
@@ -0,0 +1,48 @@
1
+ import { type ComponentPropsWithoutRef, type JSX, type ReactNode, type TdHTMLAttributes } from "react";
2
+ import type { VariantName } from "../../theme/themeTypes.js";
3
+ interface HtmlTextOwnProps {
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 "@comet/mail-react" {
11
+ * interface TextVariants { heading: true; body: true }
12
+ * }
13
+ * ```
14
+ *
15
+ * ```ts
16
+ * const theme = createTheme({
17
+ * text: {
18
+ * variants: {
19
+ * heading: { fontSize: "24px" },
20
+ * body: { fontSize: "16px" },
21
+ * },
22
+ * },
23
+ * });
24
+ */
25
+ variant?: VariantName;
26
+ /** When true, applies spacing below the text. */
27
+ bottomSpacing?: boolean;
28
+ }
29
+ export type HtmlTextProps<E extends keyof JSX.IntrinsicElements = "td"> = HtmlTextOwnProps & {
30
+ /**
31
+ * The HTML element to render instead of the default `<td>`.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * <HtmlText element="div">Rendered as a div</HtmlText>
36
+ * <HtmlText element="a" href="/link">Rendered as an anchor</HtmlText>
37
+ * ```
38
+ */
39
+ element?: E;
40
+ } & Omit<ComponentPropsWithoutRef<E>, keyof HtmlTextOwnProps | "element">;
41
+ /**
42
+ * Themed text component for use inside MJML ending tags or outside of the MJML context.
43
+ */
44
+ export declare function HtmlText<E extends keyof JSX.IntrinsicElements>(props: HtmlTextOwnProps & {
45
+ element: E;
46
+ } & Omit<ComponentPropsWithoutRef<E>, keyof HtmlTextOwnProps | "element">): ReactNode;
47
+ export declare function HtmlText(props: HtmlTextOwnProps & Omit<TdHTMLAttributes<HTMLTableCellElement>, keyof HtmlTextOwnProps>): ReactNode;
48
+ export {};
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { registerStyles } from "../../styles/registerStyles.js";
4
+ import { getDefaultOrUndefined } from "../../theme/responsiveValue.js";
5
+ import { useTheme } from "../../theme/ThemeProvider.js";
6
+ import { OutlookTextStyleProvider } from "./OutlookTextStyleContext.js";
7
+ import { generateResponsiveTextCss } from "./textStyles.js";
8
+ export function HtmlText({ element: Element = "td", variant: variantProp, bottomSpacing, className, style, children, ...restProps }) {
9
+ const theme = useTheme();
10
+ const { defaultVariant, variants, ...baseStyles } = theme.text;
11
+ const activeVariant = variantProp ?? defaultVariant;
12
+ const variantStyles = activeVariant ? variants?.[activeVariant] : undefined;
13
+ const mergedStyles = variantStyles ? { ...baseStyles, ...variantStyles } : baseStyles;
14
+ const themeStyle = {
15
+ fontFamily: getDefaultOrUndefined(mergedStyles.fontFamily),
16
+ fontSize: getDefaultOrUndefined(mergedStyles.fontSize),
17
+ fontWeight: getDefaultOrUndefined(mergedStyles.fontWeight),
18
+ fontStyle: getDefaultOrUndefined(mergedStyles.fontStyle),
19
+ lineHeight: getDefaultOrUndefined(mergedStyles.lineHeight),
20
+ letterSpacing: getDefaultOrUndefined(mergedStyles.letterSpacing),
21
+ textDecoration: getDefaultOrUndefined(mergedStyles.textDecoration),
22
+ textTransform: getDefaultOrUndefined(mergedStyles.textTransform),
23
+ color: getDefaultOrUndefined(mergedStyles.color),
24
+ ...(getDefaultOrUndefined(mergedStyles.lineHeight) !== undefined && { msoLineHeightRule: "exactly" }),
25
+ ...(bottomSpacing && { paddingBottom: getDefaultOrUndefined(mergedStyles.bottomSpacing) }),
26
+ };
27
+ const outlookTextStyleValues = {
28
+ fontFamily: themeStyle.fontFamily,
29
+ fontSize: themeStyle.fontSize,
30
+ lineHeight: themeStyle.lineHeight,
31
+ fontWeight: themeStyle.fontWeight,
32
+ color: themeStyle.color,
33
+ ...(style?.fontFamily !== undefined && { fontFamily: style.fontFamily }),
34
+ ...(style?.fontSize !== undefined && { fontSize: style.fontSize }),
35
+ ...(style?.lineHeight !== undefined && { lineHeight: style.lineHeight }),
36
+ ...(style?.fontWeight !== undefined && { fontWeight: style.fontWeight }),
37
+ ...(style?.color !== undefined && { color: style.color }),
38
+ };
39
+ return (_jsx(Element, { ...restProps, className: clsx("htmlText", activeVariant && `htmlText--${activeVariant}`, bottomSpacing && "htmlText--bottomSpacing", className), style: { ...themeStyle, ...style }, children: _jsx(OutlookTextStyleProvider, { value: outlookTextStyleValues, children: children }) }));
40
+ }
41
+ function generateHtmlTextStyles(theme) {
42
+ return generateResponsiveTextCss(theme, {
43
+ styleSelector: (variantName) => `.htmlText--${variantName}`,
44
+ spacingSelector: (variantName) => `.htmlText--bottomSpacing.htmlText--${variantName}`,
45
+ });
46
+ }
47
+ registerStyles(generateHtmlTextStyles);
@@ -0,0 +1,37 @@
1
+ import { type IMjmlTextProps } from "@faire/mjml-react";
2
+ import type { ReactNode } from "react";
3
+ import type { Theme, VariantName } from "../../theme/themeTypes.js";
4
+ export type MjmlTextProps = IMjmlTextProps & {
5
+ /**
6
+ * The component's variant to apply, as defined in the theme.
7
+ *
8
+ * Custom variants should be defined in the theme, through module augmentation.
9
+ *
10
+ * ```ts
11
+ * declare module "@comet/mail-react" {
12
+ * interface TextVariants { heading: true; body: true }
13
+ * }
14
+ * ```
15
+ *
16
+ * ```ts
17
+ * const theme = createTheme({
18
+ * text: {
19
+ * variants: {
20
+ * heading: { fontSize: "24px" },
21
+ * body: { fontSize: "16px" },
22
+ * },
23
+ * },
24
+ * });
25
+ */
26
+ variant?: VariantName;
27
+ /** When true, applies spacing below the text. */
28
+ bottomSpacing?: boolean;
29
+ };
30
+ /**
31
+ * Text component that can be styled using the theme, optionally using a variant.
32
+ *
33
+ * Works without a `ThemeProvider` as a plain pass-through to the base MJML text component.
34
+ * The `variant` and `bottomSpacing` props require a `ThemeProvider` (or `MjmlMailRoot`).
35
+ */
36
+ export declare function MjmlText({ variant: variantProp, bottomSpacing, className, children, ...restProps }: MjmlTextProps): ReactNode;
37
+ export declare function generateTextStyles(theme: Theme): string;
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MjmlText as BaseMjmlText } 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 { OutlookTextStyleProvider } from "./OutlookTextStyleContext.js";
8
+ import { generateResponsiveTextCss } from "./textStyles.js";
9
+ /**
10
+ * Text component that can be styled using the theme, optionally using a variant.
11
+ *
12
+ * Works without a `ThemeProvider` as a plain pass-through to the base MJML text component.
13
+ * The `variant` and `bottomSpacing` props require a `ThemeProvider` (or `MjmlMailRoot`).
14
+ */
15
+ export function MjmlText({ variant: variantProp, bottomSpacing, className, children, ...restProps }) {
16
+ const theme = useOptionalTheme();
17
+ const themedProps = getThemedProps(theme, variantProp, bottomSpacing, restProps);
18
+ const resolvedClassName = clsx("mjmlText", themedProps.activeVariant && `mjmlText--${themedProps.activeVariant}`, bottomSpacing && "mjmlText--bottomSpacing", className);
19
+ return (_jsx(BaseMjmlText, { ...themedProps.baseProps, className: resolvedClassName, ...restProps, children: themedProps.outlookTextStyleValues !== null ? (_jsx(OutlookTextStyleProvider, { value: themedProps.outlookTextStyleValues, children: children })) : (children) }));
20
+ }
21
+ function getThemedProps(theme, variantProp, bottomSpacing, explicitProps) {
22
+ if (theme === null) {
23
+ if (variantProp !== undefined) {
24
+ throw new Error("The `variant` prop requires being wrapped in a ThemeProvider or MjmlMailRoot.");
25
+ }
26
+ if (bottomSpacing) {
27
+ throw new Error("The `bottomSpacing` prop requires being wrapped in a ThemeProvider or MjmlMailRoot.");
28
+ }
29
+ return { activeVariant: undefined, baseProps: {}, outlookTextStyleValues: null };
30
+ }
31
+ const { defaultVariant, variants, ...baseStyles } = theme.text;
32
+ const activeVariant = variantProp ?? defaultVariant;
33
+ const variantStyles = activeVariant ? variants?.[activeVariant] : undefined;
34
+ const mergedStyles = variantStyles ? { ...baseStyles, ...variantStyles } : baseStyles;
35
+ const fontWeightDefault = getDefaultOrUndefined(mergedStyles.fontWeight);
36
+ return {
37
+ activeVariant,
38
+ baseProps: {
39
+ fontFamily: getDefaultOrUndefined(mergedStyles.fontFamily),
40
+ fontSize: getDefaultOrUndefined(mergedStyles.fontSize),
41
+ fontWeight: fontWeightDefault !== undefined ? String(fontWeightDefault) : undefined,
42
+ fontStyle: getDefaultOrUndefined(mergedStyles.fontStyle),
43
+ lineHeight: getDefaultOrUndefined(mergedStyles.lineHeight),
44
+ letterSpacing: getDefaultOrUndefined(mergedStyles.letterSpacing),
45
+ textDecoration: getDefaultOrUndefined(mergedStyles.textDecoration),
46
+ textTransform: getDefaultOrUndefined(mergedStyles.textTransform),
47
+ color: getDefaultOrUndefined(mergedStyles.color),
48
+ paddingBottom: bottomSpacing ? getDefaultOrUndefined(mergedStyles.bottomSpacing) : undefined,
49
+ },
50
+ outlookTextStyleValues: {
51
+ fontFamily: explicitProps.fontFamily ?? getDefaultOrUndefined(mergedStyles.fontFamily),
52
+ fontSize: explicitProps.fontSize ?? getDefaultOrUndefined(mergedStyles.fontSize),
53
+ fontWeight: explicitProps.fontWeight ?? fontWeightDefault,
54
+ lineHeight: explicitProps.lineHeight ?? getDefaultOrUndefined(mergedStyles.lineHeight),
55
+ color: explicitProps.color ?? getDefaultOrUndefined(mergedStyles.color),
56
+ },
57
+ };
58
+ }
59
+ export function generateTextStyles(theme) {
60
+ return generateResponsiveTextCss(theme, {
61
+ styleSelector: (variantName) => `.mjmlText--${variantName} > div`,
62
+ spacingSelector: (variantName) => `.mjmlText--bottomSpacing.mjmlText--${variantName}`,
63
+ });
64
+ }
65
+ registerStyles(generateTextStyles);
@@ -0,0 +1,9 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ type OutlookTextStyleValues = Pick<CSSProperties, "fontFamily" | "fontSize" | "lineHeight" | "fontWeight" | "color">;
3
+ declare function OutlookTextStyleProvider({ value, children }: {
4
+ value: OutlookTextStyleValues;
5
+ children: ReactNode;
6
+ }): ReactNode;
7
+ declare function useOutlookTextStyle(): OutlookTextStyleValues | null;
8
+ export { OutlookTextStyleProvider, useOutlookTextStyle };
9
+ export type { OutlookTextStyleValues };
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const OutlookTextStyleContext = createContext(null);
4
+ function OutlookTextStyleProvider({ value, children }) {
5
+ return _jsx(OutlookTextStyleContext, { value: value, children: children });
6
+ }
7
+ function useOutlookTextStyle() {
8
+ return useContext(OutlookTextStyleContext);
9
+ }
10
+ export { OutlookTextStyleProvider, useOutlookTextStyle };
@@ -0,0 +1,12 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { HtmlText } from "../HtmlText.js";
3
+ type Story = StoryObj<typeof HtmlText>;
4
+ declare const config: Meta<typeof HtmlText>;
5
+ export default config;
6
+ export declare const Default: Story;
7
+ export declare const WithVariants: Story;
8
+ export declare const ResponsiveVariants: Story;
9
+ export declare const BottomSpacing: Story;
10
+ export declare const DefaultVariant: Story;
11
+ export declare const ElementPropAsDiv: Story;
12
+ export declare const ElementPropAsAnchor: Story;