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

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 (61) hide show
  1. package/README.md +42 -15
  2. package/lib/__stories__/examples/CustomBackgroundForFooter.stories.js +32 -0
  3. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.d.ts +6 -0
  4. package/lib/__stories__/layout-patterns/AsymmetricTwoColumnLayout.stories.js +107 -0
  5. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.d.ts +4 -0
  6. package/lib/__stories__/layout-patterns/SymmetricFourColumnLayout.stories.js +58 -0
  7. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.d.ts +5 -0
  8. package/lib/__stories__/layout-patterns/SymmetricThreeColumnLayout.stories.js +104 -0
  9. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.d.ts +4 -0
  10. package/lib/__stories__/layout-patterns/SymmetricTwoColumnLayout.stories.js +47 -0
  11. package/lib/blocks/factories/BlocksBlock.d.ts +1 -1
  12. package/lib/blocks/factories/OneOfBlock.d.ts +2 -2
  13. package/lib/blocks/factories/OptionalBlock.d.ts +1 -1
  14. package/lib/blocks/factories/types.d.ts +1 -1
  15. package/lib/blocks/pixelImage/HtmlPixelImageBlock.d.ts +11 -0
  16. package/lib/blocks/pixelImage/HtmlPixelImageBlock.js +27 -0
  17. package/lib/blocks/pixelImage/MjmlPixelImageBlock.d.ts +9 -0
  18. package/lib/blocks/pixelImage/MjmlPixelImageBlock.js +24 -0
  19. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.d.ts +7 -0
  20. package/lib/blocks/pixelImage/__stories__/HtmlPixelImageBlock.stories.js +54 -0
  21. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.d.ts +7 -0
  22. package/lib/blocks/pixelImage/__stories__/MjmlPixelImageBlock.stories.js +54 -0
  23. package/lib/blocks/pixelImage/__stories__/exampleBlockData.d.ts +2 -0
  24. package/lib/blocks/pixelImage/__stories__/exampleBlockData.js +19 -0
  25. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.d.ts +1 -0
  26. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockConfig.test.js +21 -0
  27. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockData.test.d.ts +1 -0
  28. package/lib/blocks/pixelImage/__tests__/usePixelImageBlockData.test.js +205 -0
  29. package/lib/blocks/pixelImage/common.d.ts +18 -0
  30. package/lib/blocks/pixelImage/common.js +1 -0
  31. package/lib/blocks/pixelImage/usePixelImageBlockConfig.d.ts +5 -0
  32. package/lib/blocks/pixelImage/usePixelImageBlockConfig.js +11 -0
  33. package/lib/blocks/pixelImage/usePixelImageBlockData.d.ts +16 -0
  34. package/lib/blocks/pixelImage/usePixelImageBlockData.js +80 -0
  35. package/lib/client/renderMailHtml.d.ts +1 -1
  36. package/lib/components/mailRoot/MjmlMailRoot.d.ts +13 -4
  37. package/lib/components/mailRoot/MjmlMailRoot.js +10 -5
  38. package/lib/components/section/MjmlSection.d.ts +1 -1
  39. package/lib/components/section/MjmlSection.js +8 -4
  40. package/lib/components/text/HtmlText.d.ts +1 -1
  41. package/lib/components/text/textStyles.js +10 -5
  42. package/lib/components/wrapper/InsideMjmlWrapperContext.d.ts +3 -0
  43. package/lib/components/wrapper/InsideMjmlWrapperContext.js +6 -0
  44. package/lib/components/wrapper/MjmlWrapper.d.ts +7 -0
  45. package/lib/components/wrapper/MjmlWrapper.js +12 -0
  46. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.d.ts +10 -0
  47. package/lib/components/wrapper/__stories__/MjmlWrapper.stories.js +36 -0
  48. package/lib/config/ConfigProvider.d.ts +43 -0
  49. package/lib/config/ConfigProvider.js +16 -0
  50. package/lib/config/ConfigProvider.test.d.ts +8 -0
  51. package/lib/config/ConfigProvider.test.js +30 -0
  52. package/lib/index.d.ts +8 -1
  53. package/lib/index.js +5 -1
  54. package/lib/server/renderMailHtml.d.ts +1 -1
  55. package/lib/server/renderMailHtml.test.js +10 -1
  56. package/lib/storybook/preview.d.ts +34 -0
  57. package/lib/storybook/preview.js +22 -0
  58. package/lib/theme/responsiveValue.js +2 -1
  59. package/package.json +17 -13
  60. package/lib/__stories__/examples/TextWithImageEmail.stories.js +0 -56
  61. /package/lib/__stories__/examples/{TextWithImageEmail.stories.d.ts → CustomBackgroundForFooter.stories.d.ts} +0 -0
@@ -14,18 +14,21 @@ export const textStyleCssProperties = [
14
14
  /** Generates responsive CSS media queries for text variant overrides. */
15
15
  export function generateResponsiveTextCss(theme, options) {
16
16
  const { variants } = theme.text;
17
- if (!variants)
17
+ if (!variants) {
18
18
  return css ``;
19
+ }
19
20
  const cssChunks = [];
20
21
  for (const [variantName, variantStyles] of Object.entries(variants)) {
21
- if (!variantStyles)
22
+ if (!variantStyles) {
22
23
  continue;
24
+ }
23
25
  const styleOverrides = new Map();
24
26
  const spacingOverrides = new Map();
25
27
  for (const [themeKey, cssProperty] of textStyleCssProperties) {
26
28
  const value = variantStyles[themeKey];
27
- if (value === undefined)
29
+ if (value === undefined) {
28
30
  continue;
31
+ }
29
32
  for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(value)) {
30
33
  const declarations = styleOverrides.get(breakpointKey) ?? [];
31
34
  declarations.push(`${cssProperty}: ${String(breakpointValue)} !important`);
@@ -42,8 +45,9 @@ export function generateResponsiveTextCss(theme, options) {
42
45
  }
43
46
  for (const [breakpointKey, declarations] of styleOverrides) {
44
47
  const breakpoint = theme.breakpoints[breakpointKey];
45
- if (!breakpoint)
48
+ if (!breakpoint) {
46
49
  continue;
50
+ }
47
51
  cssChunks.push(css `
48
52
  ${breakpoint.belowMediaQuery} {
49
53
  ${options.styleSelector(variantName)} {
@@ -54,8 +58,9 @@ export function generateResponsiveTextCss(theme, options) {
54
58
  }
55
59
  for (const [breakpointKey, declarations] of spacingOverrides) {
56
60
  const breakpoint = theme.breakpoints[breakpointKey];
57
- if (!breakpoint)
61
+ if (!breakpoint) {
58
62
  continue;
63
+ }
59
64
  cssChunks.push(css `
60
65
  ${breakpoint.belowMediaQuery} {
61
66
  ${options.spacingSelector(variantName)} {
@@ -0,0 +1,3 @@
1
+ export declare const InsideMjmlWrapperContext: import("react").Context<boolean>;
2
+ /** Returns `true` when rendered inside a custom `MjmlWrapper` subtree. Internal use only. */
3
+ export declare function useIsInsideMjmlWrapper(): boolean;
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from "react";
2
+ export const InsideMjmlWrapperContext = createContext(false);
3
+ /** Returns `true` when rendered inside a custom `MjmlWrapper` subtree. Internal use only. */
4
+ export function useIsInsideMjmlWrapper() {
5
+ return useContext(InsideMjmlWrapperContext);
6
+ }
@@ -0,0 +1,7 @@
1
+ import { type IMjmlWrapperProps } from "@faire/mjml-react";
2
+ import type { ReactNode } from "react";
3
+ export type MjmlWrapperProps = IMjmlWrapperProps;
4
+ /**
5
+ * A wrapper that groups multiple sections sharing a background. Must be a direct child of MjmlBody.
6
+ */
7
+ export declare function MjmlWrapper({ children, ...restProps }: MjmlWrapperProps): ReactNode;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { MjmlWrapper as BaseMjmlWrapper } from "@faire/mjml-react";
3
+ import { useOptionalTheme } from "../../theme/ThemeProvider.js";
4
+ import { InsideMjmlWrapperContext } from "./InsideMjmlWrapperContext.js";
5
+ /**
6
+ * A wrapper that groups multiple sections sharing a background. Must be a direct child of MjmlBody.
7
+ */
8
+ export function MjmlWrapper({ children, ...restProps }) {
9
+ const theme = useOptionalTheme();
10
+ const themeBackgroundProps = theme ? { backgroundColor: theme.colors.background.content } : {};
11
+ return (_jsx(BaseMjmlWrapper, { ...themeBackgroundProps, ...restProps, children: _jsx(InsideMjmlWrapperContext.Provider, { value: true, children: children }) }));
12
+ }
@@ -0,0 +1,10 @@
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;
@@ -0,0 +1,36 @@
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
+ };
@@ -0,0 +1,43 @@
1
+ import { type PropsWithChildren, type ReactNode } from "react";
2
+ export interface PixelImageBlockConfig {
3
+ /**
4
+ * Image widths supported by the API, used to pick a render width.
5
+ * Generally derived from `cometConfig.images.imageSizes` and `cometConfig.images.deviceSizes`.
6
+ */
7
+ validSizes: number[];
8
+ /**
9
+ * Origin to prefix relative image URLs with, e.g. `http://localhost:3000`.
10
+ */
11
+ baseUrl: string;
12
+ }
13
+ /**
14
+ * Configuration context for mails.
15
+ *
16
+ * Add custom keys via TypeScript interface declaration merging:
17
+ *
18
+ * ```ts
19
+ * declare module "@comet/mail-react" {
20
+ * interface Config {
21
+ * myKey?: { foo: string };
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export interface Config {
27
+ /**
28
+ * Configuration consumed by `HtmlPixelImageBlock` and `MjmlPixelImageBlock`.
29
+ * Required when those components are rendered.
30
+ */
31
+ pixelImageBlock?: PixelImageBlockConfig;
32
+ }
33
+ /**
34
+ * Places a `Config` value into a React context, making it available to all
35
+ * descendants via `useConfig`.
36
+ */
37
+ export declare function ConfigProvider({ config, children }: PropsWithChildren<{
38
+ config: Config;
39
+ }>): ReactNode;
40
+ /**
41
+ * Returns the nearest `Config` from context, defined by `ConfigProvider` or by the `config` prop on `MjmlMailRoot`.
42
+ */
43
+ export declare function useConfig(): Config;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const ConfigContext = createContext({});
4
+ /**
5
+ * Places a `Config` value into a React context, making it available to all
6
+ * descendants via `useConfig`.
7
+ */
8
+ export function ConfigProvider({ config, children }) {
9
+ return _jsx(ConfigContext.Provider, { value: config, children: children });
10
+ }
11
+ /**
12
+ * Returns the nearest `Config` from context, defined by `ConfigProvider` or by the `config` prop on `MjmlMailRoot`.
13
+ */
14
+ export function useConfig() {
15
+ return useContext(ConfigContext);
16
+ }
@@ -0,0 +1,8 @@
1
+ declare module "./ConfigProvider.js" {
2
+ interface Config {
3
+ testKey?: {
4
+ value: string;
5
+ };
6
+ }
7
+ }
8
+ export {};
@@ -0,0 +1,30 @@
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
+ });
package/lib/index.d.ts CHANGED
@@ -4,6 +4,10 @@ export { OneOfBlock } from "./blocks/factories/OneOfBlock.js";
4
4
  export { OptionalBlock } from "./blocks/factories/OptionalBlock.js";
5
5
  export type { SupportedBlocks } from "./blocks/factories/types.js";
6
6
  export type { PropsWithData } from "./blocks/helpers/PropsWithData.js";
7
+ export type { HtmlPixelImageBlockProps } from "./blocks/pixelImage/HtmlPixelImageBlock.js";
8
+ export { HtmlPixelImageBlock } from "./blocks/pixelImage/HtmlPixelImageBlock.js";
9
+ export type { MjmlPixelImageBlockProps } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
10
+ export { MjmlPixelImageBlock } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
7
11
  export type { HtmlInlineLinkProps } from "./components/inlineLink/HtmlInlineLink.js";
8
12
  export { HtmlInlineLink } from "./components/inlineLink/HtmlInlineLink.js";
9
13
  export { MjmlMailRoot } from "./components/mailRoot/MjmlMailRoot.js";
@@ -13,6 +17,9 @@ export type { HtmlTextProps } from "./components/text/HtmlText.js";
13
17
  export { HtmlText } from "./components/text/HtmlText.js";
14
18
  export type { MjmlTextProps } from "./components/text/MjmlText.js";
15
19
  export { MjmlText } from "./components/text/MjmlText.js";
20
+ export type { MjmlWrapperProps } from "./components/wrapper/MjmlWrapper.js";
21
+ export { MjmlWrapper } from "./components/wrapper/MjmlWrapper.js";
22
+ export { type Config, ConfigProvider, type PixelImageBlockConfig, useConfig } from "./config/ConfigProvider.js";
16
23
  export { registerStyles } from "./styles/registerStyles.js";
17
24
  export { createBreakpoint } from "./theme/createBreakpoint.js";
18
25
  export { createTheme } from "./theme/createTheme.js";
@@ -21,6 +28,6 @@ export { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./theme/r
21
28
  export { ThemeProvider, useTheme } from "./theme/ThemeProvider.js";
22
29
  export type { TextStyles, TextVariants, TextVariantStyles, Theme, ThemeBackgroundColors, ThemeBreakpoint, ThemeBreakpoints, ThemeColors, ThemeSizes, ThemeText, } from "./theme/themeTypes.js";
23
30
  export { css } from "./utils/css.js";
24
- 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, MjmlDivider, type IMjmlDividerProps as MjmlDividerProps, 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, MjmlImage, type IMjmlImageProps as MjmlImageProps, 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, MjmlWrapper, type IMjmlWrapperProps as MjmlWrapperProps, } from "@faire/mjml-react";
31
+ 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, MjmlDivider, type IMjmlDividerProps as MjmlDividerProps, 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, MjmlImage, type IMjmlImageProps as MjmlImageProps, 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";
25
32
  export { MjmlComment, MjmlConditionalComment, MjmlHtml, MjmlTrackingPixel, MjmlYahooStyle } from "@faire/mjml-react/extensions/index.js";
26
33
  export { renderToMjml } from "@faire/mjml-react/utils/renderToMjml.js";
package/lib/index.js CHANGED
@@ -2,17 +2,21 @@ export { BlocksBlock } from "./blocks/factories/BlocksBlock.js";
2
2
  export { ListBlock } from "./blocks/factories/ListBlock.js";
3
3
  export { OneOfBlock } from "./blocks/factories/OneOfBlock.js";
4
4
  export { OptionalBlock } from "./blocks/factories/OptionalBlock.js";
5
+ export { HtmlPixelImageBlock } from "./blocks/pixelImage/HtmlPixelImageBlock.js";
6
+ export { MjmlPixelImageBlock } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
5
7
  export { HtmlInlineLink } from "./components/inlineLink/HtmlInlineLink.js";
6
8
  export { MjmlMailRoot } from "./components/mailRoot/MjmlMailRoot.js";
7
9
  export { MjmlSection } from "./components/section/MjmlSection.js";
8
10
  export { HtmlText } from "./components/text/HtmlText.js";
9
11
  export { MjmlText } from "./components/text/MjmlText.js";
12
+ export { MjmlWrapper } from "./components/wrapper/MjmlWrapper.js";
13
+ export { ConfigProvider, useConfig } from "./config/ConfigProvider.js";
10
14
  export { registerStyles } from "./styles/registerStyles.js";
11
15
  export { createBreakpoint } from "./theme/createBreakpoint.js";
12
16
  export { createTheme } from "./theme/createTheme.js";
13
17
  export { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./theme/responsiveValue.js";
14
18
  export { ThemeProvider, useTheme } from "./theme/ThemeProvider.js";
15
19
  export { css } from "./utils/css.js";
16
- export { Mjml, MjmlAccordion, MjmlAccordionElement, MjmlAccordionText, MjmlAccordionTitle, MjmlAll, MjmlAttributes, MjmlBody, MjmlBreakpoint, MjmlButton, MjmlCarousel, MjmlCarouselImage, MjmlClass, MjmlColumn, MjmlDivider, MjmlFont, MjmlGroup, MjmlHead, MjmlHero, MjmlHtmlAttribute, MjmlHtmlAttributes, MjmlImage, MjmlInclude, MjmlNavbar, MjmlNavbarLink, MjmlPreview, MjmlRaw, MjmlSelector, MjmlSocial, MjmlSocialElement, MjmlSpacer, MjmlStyle, MjmlTable, MjmlTitle, MjmlWrapper, } from "@faire/mjml-react";
20
+ export { Mjml, MjmlAccordion, MjmlAccordionElement, MjmlAccordionText, MjmlAccordionTitle, MjmlAll, MjmlAttributes, MjmlBody, MjmlBreakpoint, MjmlButton, MjmlCarousel, MjmlCarouselImage, MjmlClass, MjmlColumn, MjmlDivider, MjmlFont, MjmlGroup, MjmlHead, MjmlHero, MjmlHtmlAttribute, MjmlHtmlAttributes, MjmlImage, MjmlInclude, MjmlNavbar, MjmlNavbarLink, MjmlPreview, MjmlRaw, MjmlSelector, MjmlSocial, MjmlSocialElement, MjmlSpacer, MjmlStyle, MjmlTable, MjmlTitle, } from "@faire/mjml-react";
17
21
  export { MjmlComment, MjmlConditionalComment, MjmlHtml, MjmlTrackingPixel, MjmlYahooStyle } from "@faire/mjml-react/extensions/index.js";
18
22
  export { renderToMjml } from "@faire/mjml-react/utils/renderToMjml.js";
@@ -1,5 +1,5 @@
1
1
  import mjml2html from "mjml";
2
- import { type ReactElement } from "react";
2
+ import type { ReactElement } from "react";
3
3
  type MjmlOptions = Parameters<typeof mjml2html>[1];
4
4
  type MjmlWarning = ReturnType<typeof mjml2html>["errors"][number];
5
5
  export declare function renderMailHtml(element: ReactElement, options?: MjmlOptions): {
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlText } from "@faire/mjml-react";
2
+ import { MjmlClass, MjmlColumn, MjmlText, MjmlTitle } from "@faire/mjml-react";
3
3
  import { describe, expect, it } from "vitest";
4
4
  import { MjmlMailRoot } from "../components/mailRoot/MjmlMailRoot.js";
5
5
  import { MjmlSection } from "../components/section/MjmlSection.js";
@@ -36,6 +36,15 @@ describe("server/renderMailHtml", () => {
36
36
  expect(html).toContain(".themed");
37
37
  expect(html).toContain("width: 600px");
38
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
+ });
39
48
  it("produces no MJML warnings for a valid component tree", () => {
40
49
  const { mjmlWarnings } = renderMailHtml(_jsx(MjmlMailRoot, { children: _jsx(MjmlSection, { children: _jsx(MjmlColumn, { children: _jsx(MjmlText, { children: "No warnings expected" }) }) }) }));
41
50
  expect(mjmlWarnings).toEqual([]);
@@ -3,3 +3,37 @@ export declare const decorators: (typeof MailRendererDecorator)[];
3
3
  export declare const initialGlobals: {
4
4
  usePublicImageUrls: boolean;
5
5
  };
6
+ export declare const parameters: {
7
+ viewport: {
8
+ options: {
9
+ mobile: {
10
+ name: string;
11
+ styles: {
12
+ width: string;
13
+ height: string;
14
+ };
15
+ };
16
+ tablet: {
17
+ name: string;
18
+ styles: {
19
+ width: string;
20
+ height: string;
21
+ };
22
+ };
23
+ emailWidth: {
24
+ name: string;
25
+ styles: {
26
+ width: string;
27
+ height: string;
28
+ };
29
+ };
30
+ desktop: {
31
+ name: string;
32
+ styles: {
33
+ width: string;
34
+ height: string;
35
+ };
36
+ };
37
+ };
38
+ };
39
+ };
@@ -3,3 +3,25 @@ export const decorators = [MailRendererDecorator];
3
3
  export const initialGlobals = {
4
4
  usePublicImageUrls: false,
5
5
  };
6
+ export const parameters = {
7
+ viewport: {
8
+ options: {
9
+ mobile: {
10
+ name: "Mobile (375px)",
11
+ styles: { width: "375px", height: "1024px" },
12
+ },
13
+ tablet: {
14
+ name: "Tablet (500px)",
15
+ styles: { width: "500px", height: "1024px" },
16
+ },
17
+ emailWidth: {
18
+ name: "Email max-width (600px)",
19
+ styles: { width: "600px", height: "1024px" },
20
+ },
21
+ desktop: {
22
+ name: "Desktop (768px)",
23
+ styles: { width: "768px", height: "1024px" },
24
+ },
25
+ },
26
+ },
27
+ };
@@ -14,8 +14,9 @@ export function getDefaultFromResponsiveValue(value) {
14
14
  * theme properties.
15
15
  */
16
16
  export function getDefaultOrUndefined(value) {
17
- if (value === undefined)
17
+ if (value === undefined) {
18
18
  return undefined;
19
+ }
19
20
  return getDefaultFromResponsiveValue(value);
20
21
  }
21
22
  /**
package/package.json CHANGED
@@ -1,44 +1,49 @@
1
1
  {
2
2
  "name": "@comet/mail-react",
3
- "version": "9.0.0-beta.2",
3
+ "version": "9.0.0-beta.4",
4
4
  "description": "Utilities for building HTML emails with React",
5
5
  "license": "BSD-2-Clause",
6
6
  "type": "module",
7
+ "repository": {
8
+ "directory": "packages/mail-react",
9
+ "type": "git",
10
+ "url": "https://github.com/vivid-planet/comet"
11
+ },
7
12
  "exports": {
8
13
  ".": "./lib/index.js",
9
14
  "./client": "./lib/client/index.js",
10
15
  "./server": "./lib/server/index.js",
11
16
  "./storybook": "./lib/storybook/index.js"
12
17
  },
18
+ "sideEffects": false,
13
19
  "files": [
14
20
  "lib/*"
15
21
  ],
16
22
  "dependencies": {
17
- "@faire/mjml-react": "^3.5.3",
23
+ "@faire/mjml-react": "^3.5.4",
18
24
  "clsx": "^2.1.1",
19
25
  "mjml": "^4.18.0",
20
26
  "mjml-browser": "^4.18.0"
21
27
  },
22
28
  "devDependencies": {
23
- "@fission-ai/openspec": "^1.2.0",
24
- "@storybook/addon-docs": "^10.3.1",
25
- "@storybook/react-vite": "^10.3.1",
29
+ "@storybook/addon-docs": "^10.3.5",
30
+ "@storybook/react-vite": "^10.3.5",
26
31
  "@types/mjml": "^4.7.4",
27
32
  "@types/mjml-browser": "^4.15.0",
28
- "@types/react": "^19.2.10",
33
+ "@types/react": "^19.2.14",
29
34
  "@types/react-dom": "^19.2.3",
30
35
  "chokidar-cli": "^3.0.0",
31
- "eslint": "^9.39.2",
36
+ "eslint": "^9.39.4",
32
37
  "npm-run-all2": "^8.0.4",
33
38
  "prettier": "^3.6.2",
34
- "react": "^19.2.4",
35
- "react-dom": "^19.2.4",
39
+ "react": "^19.2.6",
40
+ "react-dom": "^19.2.6",
36
41
  "rimraf": "^6.1.2",
37
- "storybook": "^10.3.1",
42
+ "storybook": "^10.3.5",
38
43
  "typescript": "^5.9.3",
39
44
  "vitest": "^4.0.16",
40
- "@comet/cli": "9.0.0-beta.2",
41
- "@comet/eslint-config": "9.0.0-beta.2"
45
+ "@comet/cli": "9.0.0-beta.4",
46
+ "@comet/eslint-config": "9.0.0-beta.4"
42
47
  },
43
48
  "peerDependencies": {
44
49
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -71,7 +76,6 @@
71
76
  "lint:fix:prettier": "pnpm run lint:prettier --write",
72
77
  "lint:prettier": "pnpm exec prettier --check '*.{ts,js,json,md,yml,yaml}'",
73
78
  "lint:tsc": "tsc",
74
- "openspec:install-skills": "openspec init --tools github-copilot,cursor,claude",
75
79
  "storybook": "storybook dev -p 6066 --no-open",
76
80
  "test": "vitest --run",
77
81
  "test:watch": "vitest --watch"
@@ -1,56 +0,0 @@
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
- };