@comet/mail-react 9.0.0-beta.5 → 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 (96) hide show
  1. package/README.md +12 -5
  2. package/lib/blocks.generated.d.ts +1 -0
  3. package/lib/components/button/HtmlButton.d.ts +9 -0
  4. package/lib/components/button/HtmlButton.js +58 -0
  5. package/lib/components/button/MjmlButton.d.ts +10 -0
  6. package/lib/components/button/MjmlButton.js +91 -0
  7. package/lib/components/button/buttonProps.d.ts +35 -0
  8. package/lib/components/button/defaultButtonStyles.d.ts +2 -0
  9. package/lib/components/button/defaultButtonStyles.js +9 -0
  10. package/lib/components/button/generateResponsiveButtonCss.d.ts +8 -0
  11. package/lib/components/button/generateResponsiveButtonCss.js +21 -0
  12. package/lib/components/divider/dividerProps.d.ts +16 -3
  13. package/lib/components/divider/generateResponsiveDividerCss.js +11 -51
  14. package/lib/components/inlineLink/HtmlInlineLink.d.ts +8 -1
  15. package/lib/components/mailRoot/MjmlMailRoot.d.ts +3 -2
  16. package/lib/components/section/MjmlSection.d.ts +7 -1
  17. package/lib/components/text/HtmlText.d.ts +3 -0
  18. package/lib/components/text/MjmlText.d.ts +3 -0
  19. package/lib/components/text/textStyles.js +20 -70
  20. package/lib/components/wrapper/MjmlWrapper.d.ts +9 -2
  21. package/lib/components/wrapper/MjmlWrapper.js +1 -1
  22. package/lib/index.d.ts +6 -2
  23. package/lib/index.js +3 -1
  24. package/lib/styles/generateResponsiveVariantCss.d.ts +26 -0
  25. package/lib/styles/generateResponsiveVariantCss.js +66 -0
  26. package/lib/theme/createTheme.d.ts +2 -1
  27. package/lib/theme/createTheme.js +1 -0
  28. package/lib/theme/defaultTheme.js +2 -0
  29. package/lib/theme/themeTypes.d.ts +57 -0
  30. package/package.json +13 -6
  31. package/lib/__stories__/examples/CustomBackgroundForFooter.stories.d.ts +0 -5
  32. package/lib/__stories__/examples/CustomBackgroundForFooter.stories.js +0 -32
  33. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.d.ts +0 -5
  34. package/lib/__stories__/examples/CustomNestedFooterTheme.stories.js +0 -32
  35. package/lib/__stories__/examples/NotificationEmail.stories.d.ts +0 -5
  36. package/lib/__stories__/examples/NotificationEmail.stories.js +0 -25
  37. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.d.ts +0 -6
  38. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.js +0 -108
  39. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.d.ts +0 -4
  40. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.js +0 -58
  41. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.d.ts +0 -5
  42. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.js +0 -104
  43. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.d.ts +0 -4
  44. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.js +0 -47
  45. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.d.ts +0 -7
  46. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.js +0 -54
  47. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.d.ts +0 -7
  48. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.js +0 -54
  49. package/lib/blocks/pixelImage/__stories__/exampleBlockData.d.ts +0 -2
  50. package/lib/blocks/pixelImage/__stories__/exampleBlockData.js +0 -19
  51. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.js +0 -21
  52. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockData.test.d.ts +0 -1
  53. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockData.test.js +0 -205
  54. package/lib/components/divider/__stories__/HtmlDivider.stories.d.ts +0 -10
  55. package/lib/components/divider/__stories__/HtmlDivider.stories.js +0 -60
  56. package/lib/components/divider/__stories__/MjmlDivider.stories.d.ts +0 -10
  57. package/lib/components/divider/__stories__/MjmlDivider.stories.js +0 -60
  58. package/lib/components/divider/__tests__/HtmlDivider.test.d.ts +0 -1
  59. package/lib/components/divider/__tests__/HtmlDivider.test.js +0 -144
  60. package/lib/components/divider/__tests__/MjmlDivider.test.d.ts +0 -1
  61. package/lib/components/divider/__tests__/MjmlDivider.test.js +0 -43
  62. package/lib/components/image/__stories__/HtmlImage.stories.d.ts +0 -7
  63. package/lib/components/image/__stories__/HtmlImage.stories.js +0 -32
  64. package/lib/components/image/__stories__/MjmlImage.stories.d.ts +0 -7
  65. package/lib/components/image/__stories__/MjmlImage.stories.js +0 -32
  66. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.d.ts +0 -12
  67. package/lib/components/inlineLink/__stories__/HtmlInlineLink.stories.js +0 -56
  68. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.d.ts +0 -7
  69. package/lib/components/mailRoot/__stories__/MjmlMailRoot.stories.js +0 -18
  70. package/lib/components/section/__stories__/MjmlSection.stories.d.ts +0 -9
  71. package/lib/components/section/__stories__/MjmlSection.stories.js +0 -30
  72. package/lib/components/text/__stories__/HtmlText.stories.d.ts +0 -12
  73. package/lib/components/text/__stories__/HtmlText.stories.js +0 -77
  74. package/lib/components/text/__stories__/MjmlText.stories.d.ts +0 -10
  75. package/lib/components/text/__stories__/MjmlText.stories.js +0 -71
  76. package/lib/components/text/__tests__/HtmlText.test.d.ts +0 -1
  77. package/lib/components/text/__tests__/HtmlText.test.js +0 -157
  78. package/lib/components/text/__tests__/MjmlText.test.d.ts +0 -1
  79. package/lib/components/text/__tests__/MjmlText.test.js +0 -112
  80. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.d.ts +0 -10
  81. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.js +0 -36
  82. package/lib/config/ConfigProvider.test.d.ts +0 -8
  83. package/lib/config/ConfigProvider.test.js +0 -30
  84. package/lib/server/renderMailHtml.test.d.ts +0 -1
  85. package/lib/server/renderMailHtml.test.js +0 -52
  86. package/lib/storybook/replaceImagesWithPublicUrl.test.d.ts +0 -1
  87. package/lib/storybook/replaceImagesWithPublicUrl.test.js +0 -55
  88. package/lib/theme/__stories__/ThemeProvider.stories.d.ts +0 -7
  89. package/lib/theme/__stories__/ThemeProvider.stories.js +0 -23
  90. package/lib/theme/createTheme.test.d.ts +0 -1
  91. package/lib/theme/createTheme.test.js +0 -52
  92. package/lib/theme/responsiveValue.test.d.ts +0 -1
  93. package/lib/theme/responsiveValue.test.js +0 -62
  94. package/lib/utils/css.test.d.ts +0 -1
  95. package/lib/utils/css.test.js +0 -26
  96. /package/lib/{blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.d.ts → components/button/buttonProps.js} +0 -0
package/README.md CHANGED
@@ -38,7 +38,10 @@ Themed components expose their styling through the theme: a flat base entry and
38
38
  - **Location.** Custom components live in `src/components/<concern>/` (e.g. `src/components/section/MjmlSection.tsx`).
39
39
  - **File order.** Imports → types/props → component → styles. `registerStyles` calls go below the component.
40
40
  - **Module format.** ESM only (`type: module`). Use `.js` extensions in imports.
41
- - **TSDoc.** Short TSDoc — one line where possible — on exported components and props.
41
+ - **TSDoc.** Short — one line where possible — on exported components and props.
42
+ - **Prop defaults.** Add `@defaultValue` when the default isn't obvious from the type — runtime defaults, and theme-derived ones the consumer can't see in the shipped types.
43
+ - **Phrasing a default.** Name where the value comes from, not the value itself. One theme token: name it in backticks, as in "The theme's `colors.background.content`". Computed from several: name what it depends on, as in "The theme's `divider` height for the active variant".
44
+ - **Defaults on inherited props.** Redeclare the prop locally (`Omit` it from the base, re-add it with the same type) so the tag is on the prop itself. Never document the default in the component's TSDoc.
42
45
 
43
46
  ### Storybook
44
47
 
@@ -97,9 +100,13 @@ Other sections should be rare — only when content is durable, feature-specific
97
100
 
98
101
  Feature READMEs (this one included) should stay current. Update them in the same PR as any change that makes them inaccurate or adds context worth recording.
99
102
 
100
- ## Consumer-facing documentation
103
+ ## Usage documentation
101
104
 
102
- - Docs: [docs/docs/3-features-modules/13-building-html-emails/](../../docs/docs/3-features-modules/13-building-html-emails/)
103
- - Agent skill: [skills/comet-mail-react/SKILL.md](../../skills/comet-mail-react/SKILL.md)
105
+ Two artifacts document how to _use_ this package, for two readers:
104
106
 
105
- When a change here affects usage patterns, component APIs, or styling conventions, update these alongside the library change.
107
+ - **Docs** [docs/docs/3-features-modules/13-building-html-emails/](../../docs/docs/3-features-modules/13-building-html-emails/) for people building emails with the package.
108
+ - **Agent skill** — [skills/comet-mail-react/SKILL.md](../../skills/comet-mail-react/SKILL.md) — the same guidance for AI agents working in a project that uses this package.
109
+
110
+ Both teach the same things: what the package is for, how to build emails with it, the patterns to reach for, and the email-client pitfalls — markup that's fine in a browser can render wrong in clients like Outlook. They differ in one respect — an agent already has the package's types and TSDoc in its project, so the skill doesn't restate type signatures, prop lists, or defaults; those live in the types and TSDoc, and it spends its words on everything they can't convey on their own.
111
+
112
+ When a change affects how the package is used — components added/removed/renamed, changed behavior, new usage patterns, or styling conventions — update both in the same PR; consider a separate docs commit so reviewers can take them on their own.
@@ -54,6 +54,7 @@ export interface EmailLinkBlockData {
54
54
  export interface ExternalLinkBlockData {
55
55
  targetUrl?: string;
56
56
  openInNewWindow: boolean;
57
+ noFollow: boolean;
57
58
  }
58
59
  export interface InternalLinkBlockData {
59
60
  targetPageAnchor?: string;
@@ -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 "@comet/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,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
+ }
@@ -2,7 +2,8 @@ import type { CSSProperties } from "react";
2
2
  import type { DividerVariantName } from "../../theme/themeTypes.js";
3
3
  export interface DividerProps {
4
4
  /**
5
- * The component's variant to apply, as defined in the theme.
5
+ * The component's variant to apply, as defined in the theme. Requires a
6
+ * theme (`ThemeProvider` or `MjmlMailRoot`) when set.
6
7
  *
7
8
  * Custom variants should be defined in the theme through module augmentation:
8
9
  *
@@ -22,15 +23,27 @@ export interface DividerProps {
22
23
  * },
23
24
  * });
24
25
  * ```
26
+ *
27
+ * @defaultValue The theme's `divider.defaultVariant`, when set
25
28
  */
26
29
  variant?: DividerVariantName;
27
- /** Height of the divider in pixels. */
30
+ /**
31
+ * Height of the divider in pixels.
32
+ *
33
+ * @defaultValue The theme's `divider` height for the active variant
34
+ */
28
35
  height?: number;
29
- /** Background color of the divider (e.g. `"#FF0000"`). */
36
+ /**
37
+ * Background color of the divider (e.g. `"#FF0000"`).
38
+ *
39
+ * @defaultValue The theme's `divider` background color for the active variant
40
+ */
30
41
  backgroundColor?: string;
31
42
  /**
32
43
  * Background image for the divider — typically a gradient. Clients that
33
44
  * don't render `background-image` fall back to the solid `backgroundColor`.
45
+ *
46
+ * @defaultValue The theme's `divider` background image for the active variant
34
47
  */
35
48
  backgroundImage?: string;
36
49
  className?: string;
@@ -1,55 +1,15 @@
1
- import { getResponsiveOverrides } from "../../theme/responsiveValue.js";
2
- import { css } from "../../utils/css.js";
3
- const colorCssProperties = [
4
- ["backgroundColor", "background-color"],
5
- ["backgroundImage", "background-image"],
1
+ import { generateResponsiveVariantCss } from "../../styles/generateResponsiveVariantCss.js";
2
+ const dividerProperties = [
3
+ // line-height matches height so Outlook honors the declared cell height.
4
+ { themeKey: "height", cssProperties: ["height", "line-height"], unit: "px" },
5
+ "backgroundColor",
6
+ "backgroundImage",
6
7
  ];
7
8
  /** Generates responsive CSS media queries for divider variant overrides. */
8
9
  export function generateResponsiveDividerCss(theme, options) {
9
- const { variants } = theme.divider;
10
- if (!variants) {
11
- return css ``;
12
- }
13
- const cssChunks = [];
14
- for (const [variantName, variantStyles] of Object.entries(variants)) {
15
- if (!variantStyles) {
16
- continue;
17
- }
18
- const overrides = new Map();
19
- const heightValue = variantStyles.height;
20
- if (heightValue !== undefined) {
21
- for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(heightValue)) {
22
- const declarations = overrides.get(breakpointKey) ?? [];
23
- // line-height matches height so Outlook honors the declared cell height.
24
- declarations.push(`height: ${breakpointValue}px !important`);
25
- declarations.push(`line-height: ${breakpointValue}px !important`);
26
- overrides.set(breakpointKey, declarations);
27
- }
28
- }
29
- for (const [themeKey, cssProperty] of colorCssProperties) {
30
- const value = variantStyles[themeKey];
31
- if (value === undefined) {
32
- continue;
33
- }
34
- for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(value)) {
35
- const declarations = overrides.get(breakpointKey) ?? [];
36
- declarations.push(`${cssProperty}: ${breakpointValue} !important`);
37
- overrides.set(breakpointKey, declarations);
38
- }
39
- }
40
- for (const [breakpointKey, declarations] of overrides) {
41
- const breakpoint = theme.breakpoints[breakpointKey];
42
- if (!breakpoint) {
43
- continue;
44
- }
45
- cssChunks.push(css `
46
- ${breakpoint.belowMediaQuery} {
47
- ${options.styleSelector(variantName)} {
48
- ${declarations.join(";\n")}
49
- }
50
- }
51
- `);
52
- }
53
- }
54
- return cssChunks.join("\n");
10
+ return generateResponsiveVariantCss({
11
+ breakpoints: theme.breakpoints,
12
+ variants: theme.divider.variants,
13
+ groups: [{ selector: options.styleSelector, properties: dividerProperties }],
14
+ });
55
15
  }
@@ -1,5 +1,12 @@
1
1
  import type { ComponentProps, ReactNode } from "react";
2
- export type HtmlInlineLinkProps = ComponentProps<"a">;
2
+ export type HtmlInlineLinkProps = Omit<ComponentProps<"a">, "target"> & {
3
+ /**
4
+ * Where to open the linked document.
5
+ *
6
+ * @defaultValue `"_blank"`
7
+ */
8
+ target?: ComponentProps<"a">["target"];
9
+ };
3
10
  /**
4
11
  * Inline link styled to match the surrounding text, for use inside `HtmlText` or `MjmlText`.
5
12
  *
@@ -3,8 +3,9 @@ import { type Config } from "../../config/ConfigProvider.js";
3
3
  import type { Theme } from "../../theme/themeTypes.js";
4
4
  type MjmlMailRootProps = PropsWithChildren<{
5
5
  /**
6
- * Theme to use for the email. When omitted, the default theme
7
- * (equivalent to `createTheme()`) is used.
6
+ * Theme to use for the email.
7
+ *
8
+ * @defaultValue `createTheme()`
8
9
  */
9
10
  theme?: Theme;
10
11
  /** Extra content appended inside the built-in `<MjmlAttributes>`, after the default `<MjmlAll>`. */
@@ -1,6 +1,12 @@
1
1
  import { type IMjmlGroupProps, type IMjmlSectionProps } from "@faire/mjml-react";
2
2
  import type { ReactNode } from "react";
3
- export type MjmlSectionProps = IMjmlSectionProps & {
3
+ export type MjmlSectionProps = Omit<IMjmlSectionProps, "backgroundColor"> & {
4
+ /**
5
+ * Background color of the section.
6
+ *
7
+ * @defaultValue The theme's `colors.background.content`, unless inside an `MjmlWrapper`
8
+ */
9
+ backgroundColor?: IMjmlSectionProps["backgroundColor"];
4
10
  /** Applies theme-based content indentation with responsive overrides. */
5
11
  indent?: boolean;
6
12
  /** When true, child columns remain side-by-side on mobile instead of stacking vertically. */
@@ -21,6 +21,9 @@ interface HtmlTextOwnProps {
21
21
  * },
22
22
  * },
23
23
  * });
24
+ * ```
25
+ *
26
+ * @defaultValue The theme's `text.defaultVariant`, when set
24
27
  */
25
28
  variant?: VariantName;
26
29
  /** When true, applies spacing below the text. */
@@ -22,6 +22,9 @@ export type MjmlTextProps = IMjmlTextProps & {
22
22
  * },
23
23
  * },
24
24
  * });
25
+ * ```
26
+ *
27
+ * @defaultValue The theme's `text.defaultVariant`, when set
25
28
  */
26
29
  variant?: VariantName;
27
30
  /** When true, applies spacing below the text. */
@@ -1,74 +1,24 @@
1
- import { getResponsiveOverrides } from "../../theme/responsiveValue.js";
2
- import { css } from "../../utils/css.js";
3
- const textStyleCssProperties = [
4
- ["fontFamily", "font-family"],
5
- ["fontSize", "font-size"],
6
- ["fontWeight", "font-weight"],
7
- ["fontStyle", "font-style"],
8
- ["lineHeight", "line-height"],
9
- ["letterSpacing", "letter-spacing"],
10
- ["textDecoration", "text-decoration"],
11
- ["textTransform", "text-transform"],
12
- ["color", "color"],
1
+ import { generateResponsiveVariantCss } from "../../styles/generateResponsiveVariantCss.js";
2
+ const textStyleProperties = [
3
+ "fontFamily",
4
+ "fontSize",
5
+ "fontWeight",
6
+ "fontStyle",
7
+ "lineHeight",
8
+ "letterSpacing",
9
+ "textDecoration",
10
+ "textTransform",
11
+ "color",
13
12
  ];
13
+ const spacingProperties = [{ themeKey: "bottomSpacing", cssProperties: "padding-bottom" }];
14
14
  /** Generates responsive CSS media queries for text variant overrides. */
15
15
  export function generateResponsiveTextCss(theme, options) {
16
- const { variants } = theme.text;
17
- if (!variants) {
18
- return css ``;
19
- }
20
- const cssChunks = [];
21
- for (const [variantName, variantStyles] of Object.entries(variants)) {
22
- if (!variantStyles) {
23
- continue;
24
- }
25
- const styleOverrides = new Map();
26
- const spacingOverrides = new Map();
27
- for (const [themeKey, cssProperty] of textStyleCssProperties) {
28
- const value = variantStyles[themeKey];
29
- if (value === undefined) {
30
- continue;
31
- }
32
- for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(value)) {
33
- const declarations = styleOverrides.get(breakpointKey) ?? [];
34
- declarations.push(`${cssProperty}: ${String(breakpointValue)} !important`);
35
- styleOverrides.set(breakpointKey, declarations);
36
- }
37
- }
38
- const bottomSpacingValue = variantStyles.bottomSpacing;
39
- if (bottomSpacingValue !== undefined) {
40
- for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(bottomSpacingValue)) {
41
- const declarations = spacingOverrides.get(breakpointKey) ?? [];
42
- declarations.push(`padding-bottom: ${String(breakpointValue)} !important`);
43
- spacingOverrides.set(breakpointKey, declarations);
44
- }
45
- }
46
- for (const [breakpointKey, declarations] of styleOverrides) {
47
- const breakpoint = theme.breakpoints[breakpointKey];
48
- if (!breakpoint) {
49
- continue;
50
- }
51
- cssChunks.push(css `
52
- ${breakpoint.belowMediaQuery} {
53
- ${options.styleSelector(variantName)} {
54
- ${declarations.join(";\n")}
55
- }
56
- }
57
- `);
58
- }
59
- for (const [breakpointKey, declarations] of spacingOverrides) {
60
- const breakpoint = theme.breakpoints[breakpointKey];
61
- if (!breakpoint) {
62
- continue;
63
- }
64
- cssChunks.push(css `
65
- ${breakpoint.belowMediaQuery} {
66
- ${options.spacingSelector(variantName)} {
67
- ${declarations.join(";\n")}
68
- }
69
- }
70
- `);
71
- }
72
- }
73
- return cssChunks.join("\n");
16
+ return generateResponsiveVariantCss({
17
+ breakpoints: theme.breakpoints,
18
+ variants: theme.text.variants,
19
+ groups: [
20
+ { selector: options.styleSelector, properties: textStyleProperties },
21
+ { selector: options.spacingSelector, properties: spacingProperties },
22
+ ],
23
+ });
74
24
  }
@@ -1,7 +1,14 @@
1
1
  import { type IMjmlWrapperProps } from "@faire/mjml-react";
2
2
  import type { ReactNode } from "react";
3
- export type MjmlWrapperProps = IMjmlWrapperProps;
3
+ export type MjmlWrapperProps = Omit<IMjmlWrapperProps, "backgroundColor"> & {
4
+ /**
5
+ * Background color of the wrapper.
6
+ *
7
+ * @defaultValue The theme's `colors.background.content`
8
+ */
9
+ backgroundColor?: IMjmlWrapperProps["backgroundColor"];
10
+ };
4
11
  /**
5
- * A wrapper that groups multiple sections sharing a background. Must be a direct child of MjmlBody.
12
+ * A wrapper that groups multiple sections sharing a background. Must be a direct child of `MjmlBody`.
6
13
  */
7
14
  export declare function MjmlWrapper({ children, ...restProps }: MjmlWrapperProps): ReactNode;
@@ -3,7 +3,7 @@ import { MjmlWrapper as BaseMjmlWrapper } from "@faire/mjml-react";
3
3
  import { useOptionalTheme } from "../../theme/ThemeProvider.js";
4
4
  import { InsideMjmlWrapperContext } from "./InsideMjmlWrapperContext.js";
5
5
  /**
6
- * A wrapper that groups multiple sections sharing a background. Must be a direct child of MjmlBody.
6
+ * A wrapper that groups multiple sections sharing a background. Must be a direct child of `MjmlBody`.
7
7
  */
8
8
  export function MjmlWrapper({ children, ...restProps }) {
9
9
  const theme = useOptionalTheme();
package/lib/index.d.ts CHANGED
@@ -8,6 +8,10 @@ export type { HtmlPixelImageBlockProps } from "./blocks/pixelImage/HtmlPixelImag
8
8
  export { HtmlPixelImageBlock } from "./blocks/pixelImage/HtmlPixelImageBlock.js";
9
9
  export type { MjmlPixelImageBlockProps } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
10
10
  export { MjmlPixelImageBlock } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
11
+ export type { HtmlButtonProps } from "./components/button/HtmlButton.js";
12
+ export { HtmlButton } from "./components/button/HtmlButton.js";
13
+ export type { MjmlButtonProps } from "./components/button/MjmlButton.js";
14
+ export { MjmlButton } from "./components/button/MjmlButton.js";
11
15
  export type { HtmlDividerProps } from "./components/divider/HtmlDivider.js";
12
16
  export { HtmlDivider } from "./components/divider/HtmlDivider.js";
13
17
  export type { MjmlDividerProps } from "./components/divider/MjmlDivider.js";
@@ -34,8 +38,8 @@ export { createTheme } from "./theme/createTheme.js";
34
38
  export type { ResponsiveValue } from "./theme/responsiveValue.js";
35
39
  export { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./theme/responsiveValue.js";
36
40
  export { ThemeProvider, useTheme } from "./theme/ThemeProvider.js";
37
- export type { DividerStyles, DividerVariants, DividerVariantStyles, TextStyles, TextVariants, TextVariantStyles, Theme, ThemeBackgroundColors, ThemeBreakpoint, ThemeBreakpoints, ThemeColors, ThemeDivider, ThemeSizes, ThemeText, } from "./theme/themeTypes.js";
41
+ export type { ButtonStyles, ButtonVariants, ButtonVariantStyles, DividerStyles, DividerVariants, DividerVariantStyles, TextStyles, TextVariants, TextVariantStyles, Theme, ThemeBackgroundColors, ThemeBreakpoint, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeSizes, ThemeText, } from "./theme/themeTypes.js";
38
42
  export { css } from "./utils/css.js";
39
- export { Mjml, MjmlAccordion, MjmlAccordionElement, type IMjmlAccordionElementProps as MjmlAccordionElementProps, type IMjmlAccordionProps as MjmlAccordionProps, MjmlAccordionText, type IMjmlAccordionTextProps as MjmlAccordionTextProps, MjmlAccordionTitle, type IMjmlAccordionTitleProps as MjmlAccordionTitleProps, MjmlAll, type IMjmlAllProps as MjmlAllProps, MjmlAttributes, type IMjmlAttributesProps as MjmlAttributesProps, MjmlBody, type IMjmlBodyProps as MjmlBodyProps, MjmlBreakpoint, type IMjmlBreakpointProps as MjmlBreakpointProps, MjmlButton, type IMjmlButtonProps as MjmlButtonProps, MjmlCarousel, MjmlCarouselImage, type IMjmlCarouselImageProps as MjmlCarouselImageProps, type IMjmlCarouselProps as MjmlCarouselProps, MjmlClass, type IMjmlClassProps as MjmlClassProps, MjmlColumn, type IMjmlColumnProps as MjmlColumnProps, MjmlFont, type IMjmlFontProps as MjmlFontProps, MjmlGroup, type IMjmlGroupProps as MjmlGroupProps, MjmlHead, type IMjmlHeadProps as MjmlHeadProps, MjmlHero, type IMjmlHeroProps as MjmlHeroProps, MjmlHtmlAttribute, type IMjmlHtmlAttributeProps as MjmlHtmlAttributeProps, MjmlHtmlAttributes, type IMjmlHtmlAttributesProps as MjmlHtmlAttributesProps, MjmlInclude, type IMjmlIncludeProps as MjmlIncludeProps, MjmlNavbar, MjmlNavbarLink, type IMjmlNavbarLinkProps as MjmlNavbarLinkProps, type IMjmlNavbarProps as MjmlNavbarProps, MjmlPreview, type IMjmlPreviewProps as MjmlPreviewProps, type IMjmlProps as MjmlProps, MjmlRaw, type IMjmlRawProps as MjmlRawProps, MjmlSelector, type IMjmlSelectorProps as MjmlSelectorProps, MjmlSocial, MjmlSocialElement, type IMjmlSocialElementProps as MjmlSocialElementProps, type IMjmlSocialProps as MjmlSocialProps, MjmlSpacer, type IMjmlSpacerProps as MjmlSpacerProps, MjmlStyle, type IMjmlStyleProps as MjmlStyleProps, MjmlTable, type IMjmlTableProps as MjmlTableProps, MjmlTitle, type IMjmlTitleProps as MjmlTitleProps, } from "@faire/mjml-react";
43
+ export { Mjml, MjmlAccordion, MjmlAccordionElement, type IMjmlAccordionElementProps as MjmlAccordionElementProps, type IMjmlAccordionProps as MjmlAccordionProps, MjmlAccordionText, type IMjmlAccordionTextProps as MjmlAccordionTextProps, MjmlAccordionTitle, type IMjmlAccordionTitleProps as MjmlAccordionTitleProps, MjmlAll, type IMjmlAllProps as MjmlAllProps, MjmlAttributes, type IMjmlAttributesProps as MjmlAttributesProps, MjmlBody, type IMjmlBodyProps as MjmlBodyProps, MjmlBreakpoint, type IMjmlBreakpointProps as MjmlBreakpointProps, MjmlCarousel, MjmlCarouselImage, type IMjmlCarouselImageProps as MjmlCarouselImageProps, type IMjmlCarouselProps as MjmlCarouselProps, MjmlClass, type IMjmlClassProps as MjmlClassProps, MjmlColumn, type IMjmlColumnProps as MjmlColumnProps, MjmlFont, type IMjmlFontProps as MjmlFontProps, MjmlGroup, type IMjmlGroupProps as MjmlGroupProps, MjmlHead, type IMjmlHeadProps as MjmlHeadProps, MjmlHero, type IMjmlHeroProps as MjmlHeroProps, MjmlHtmlAttribute, type IMjmlHtmlAttributeProps as MjmlHtmlAttributeProps, MjmlHtmlAttributes, type IMjmlHtmlAttributesProps as MjmlHtmlAttributesProps, MjmlInclude, type IMjmlIncludeProps as MjmlIncludeProps, MjmlNavbar, MjmlNavbarLink, type IMjmlNavbarLinkProps as MjmlNavbarLinkProps, type IMjmlNavbarProps as MjmlNavbarProps, MjmlPreview, type IMjmlPreviewProps as MjmlPreviewProps, type IMjmlProps as MjmlProps, MjmlRaw, type IMjmlRawProps as MjmlRawProps, MjmlSelector, type IMjmlSelectorProps as MjmlSelectorProps, MjmlSocial, MjmlSocialElement, type IMjmlSocialElementProps as MjmlSocialElementProps, type IMjmlSocialProps as MjmlSocialProps, MjmlSpacer, type IMjmlSpacerProps as MjmlSpacerProps, MjmlStyle, type IMjmlStyleProps as MjmlStyleProps, MjmlTable, type IMjmlTableProps as MjmlTableProps, MjmlTitle, type IMjmlTitleProps as MjmlTitleProps, } from "@faire/mjml-react";
40
44
  export { MjmlComment, MjmlConditionalComment, MjmlHtml, MjmlTrackingPixel, MjmlYahooStyle } from "@faire/mjml-react/extensions/index.js";
41
45
  export { renderToMjml } from "@faire/mjml-react/utils/renderToMjml.js";