@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
@@ -1,104 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer } from "@faire/mjml-react";
3
- import { MjmlSection } from "../../components/section/MjmlSection.js";
4
- import { MjmlText } from "../../components/text/MjmlText.js";
5
- import { registerStyles } from "../../styles/registerStyles.js";
6
- import { createTheme } from "../../theme/createTheme.js";
7
- import { getDefaultFromResponsiveValue } from "../../theme/responsiveValue.js";
8
- import { css } from "../../utils/css.js";
9
- const config = {
10
- title: "Layout Patterns/Symmetric Three-Column Layout",
11
- };
12
- export default config;
13
- const theme = createTheme({
14
- text: {
15
- defaultVariant: "body",
16
- variants: {
17
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
18
- subheading: { fontSize: "16px", lineHeight: "22px", fontWeight: "bold" },
19
- body: { fontSize: "14px", lineHeight: "20px" },
20
- },
21
- },
22
- });
23
- registerStyles((theme) => css `
24
- ${theme.breakpoints.default.belowMediaQuery} {
25
- .symmetricThreeColumnsSection > table > tbody > tr > td {
26
- display: flex !important;
27
- gap: 20px !important;
28
- }
29
-
30
- .symmetricThreeColumnsSection__column {
31
- flex: 1 1 0% !important;
32
- width: auto !important;
33
- max-width: none !important;
34
- display: block !important;
35
- }
36
-
37
- .symmetricThreeColumnsSection__column > table > tbody > tr > td {
38
- padding-left: 0 !important;
39
- padding-right: 0 !important;
40
- }
41
- }
42
-
43
- ${theme.breakpoints.mobile.belowMediaQuery} {
44
- .symmetricThreeColumnsSection > table > tbody > tr > td {
45
- flex-direction: column !important;
46
- }
47
-
48
- .symmetricThreeColumnsSection__column {
49
- flex: none !important;
50
- width: 100% !important;
51
- max-width: 100% !important;
52
- }
53
- }
54
- `);
55
- export const Default = {
56
- parameters: { theme },
57
- render: () => {
58
- const SymmetricThreeColumnSection = () => {
59
- const columnGap = 20;
60
- const halfColumnGap = columnGap / 2;
61
- const availableContentWidth = theme.sizes.bodyWidth - 2 * getDefaultFromResponsiveValue(theme.sizes.contentIndentation);
62
- const contentWidthPerColumn = (availableContentWidth - 2 * columnGap) / 3;
63
- const outerColumnWidth = `${((contentWidthPerColumn + halfColumnGap) / availableContentWidth) * 100}%`;
64
- const innerColumnWidth = `${((contentWidthPerColumn + columnGap) / availableContentWidth) * 100}%`;
65
- return (_jsxs(MjmlSection, { indent: true, className: "symmetricThreeColumnsSection", children: [_jsxs(MjmlColumn, { width: outerColumnWidth, paddingRight: halfColumnGap, className: "symmetricThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "First column" }), _jsx(MjmlText, { children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est." })] }), _jsxs(MjmlColumn, { width: innerColumnWidth, paddingLeft: halfColumnGap, paddingRight: halfColumnGap, className: "symmetricThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Second column" }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus." })] }), _jsxs(MjmlColumn, { width: outerColumnWidth, paddingLeft: halfColumnGap, className: "symmetricThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Third column" }), _jsx(MjmlText, { children: "Occaecati vel possimus similique reiciendis iure rerum sit architecto. Voluptas laudantium cupiditate aut repudiandae iste." })] })] }));
66
- };
67
- return (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Three column layout" }), _jsx(MjmlText, { children: "Three columns require explicit width compensation. Inner columns have padding on both sides while outer columns only have it on one side, so inner columns are given a wider width to keep all content areas equal. On mobile, a flex reset neutralizes the desktop widths so columns can size equally and then stack." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsx(SymmetricThreeColumnSection, {}), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] }));
68
- },
69
- };
70
- registerStyles((theme) => css `
71
- ${theme.breakpoints.default.belowMediaQuery} {
72
- .neverStackingThreeColumnsSection > table > tbody > tr > td > div {
73
- display: flex !important;
74
- gap: 20px !important;
75
- }
76
-
77
- .neverStackingThreeColumnsSection__column {
78
- flex: 1 1 0% !important;
79
- width: auto !important;
80
- max-width: none !important;
81
- display: block !important;
82
- }
83
-
84
- .neverStackingThreeColumnsSection__column > table > tbody > tr > td {
85
- padding-left: 0 !important;
86
- padding-right: 0 !important;
87
- }
88
- }
89
- `);
90
- export const NeverStacks = {
91
- parameters: { theme },
92
- render: () => {
93
- const NeverStackingThreeColumnSection = () => {
94
- const columnGap = 20;
95
- const halfColumnGap = columnGap / 2;
96
- const availableContentWidth = theme.sizes.bodyWidth - 2 * getDefaultFromResponsiveValue(theme.sizes.contentIndentation);
97
- const contentWidthPerColumn = (availableContentWidth - 2 * columnGap) / 3;
98
- const outerColumnWidth = `${((contentWidthPerColumn + halfColumnGap) / availableContentWidth) * 100}%`;
99
- const innerColumnWidth = `${((contentWidthPerColumn + columnGap) / availableContentWidth) * 100}%`;
100
- return (_jsxs(MjmlSection, { indent: true, disableResponsiveBehavior: true, className: "neverStackingThreeColumnsSection", children: [_jsxs(MjmlColumn, { width: outerColumnWidth, paddingRight: halfColumnGap, className: "neverStackingThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Foo" }), _jsx(MjmlText, { children: "Lorem" })] }), _jsxs(MjmlColumn, { width: innerColumnWidth, paddingLeft: halfColumnGap, paddingRight: halfColumnGap, className: "neverStackingThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Bar" }), _jsx(MjmlText, { children: "Ipsum" })] }), _jsxs(MjmlColumn, { width: outerColumnWidth, paddingLeft: halfColumnGap, className: "neverStackingThreeColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Baz" }), _jsx(MjmlText, { children: "Dolor" })] })] }));
101
- };
102
- return (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Three columns that never stack" }), _jsx(MjmlText, { bottomSpacing: true, children: "Same width compensation and flex-reset mechanic as the default three-column layout, without the mobile stacking override so columns stay side-by-side at every viewport." }), _jsxs(MjmlText, { bottomSpacing: true, children: [_jsx("code", { children: "disableResponsiveBehavior" }), " wraps the columns in an ", _jsx("code", { children: "MjmlGroup" }), " internally so MJML's own mobile auto-stack is suppressed even in clients that ignore the flex CSS. Because of that wrapper, the flex reset targets one level deeper (", _jsx("code", { children: "\u2026 > td > div" }), ") than in the default layout."] }), _jsx(MjmlText, { children: "Suitable for short, fixed-value rows \u2014 metrics, numeric summaries, icon strips \u2014 that remain readable even when narrow." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsx(NeverStackingThreeColumnSection, {}), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] }));
103
- },
104
- };
@@ -1,4 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- declare const config: Meta;
3
- export default config;
4
- export declare const Default: StoryObj;
@@ -1,47 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer } from "@faire/mjml-react";
3
- import { MjmlSection } from "../../components/section/MjmlSection.js";
4
- import { MjmlText } from "../../components/text/MjmlText.js";
5
- import { registerStyles } from "../../styles/registerStyles.js";
6
- import { createTheme } from "../../theme/createTheme.js";
7
- import { css } from "../../utils/css.js";
8
- const config = {
9
- title: "Layout Patterns/Symmetric Two-Column Layout",
10
- };
11
- export default config;
12
- const theme = createTheme({
13
- text: {
14
- defaultVariant: "body",
15
- variants: {
16
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
17
- subheading: { fontSize: "16px", lineHeight: "22px", fontWeight: "bold" },
18
- body: { fontSize: "14px", lineHeight: "20px" },
19
- },
20
- },
21
- });
22
- registerStyles((theme) => css `
23
- ${theme.breakpoints.mobile.belowMediaQuery} {
24
- .twoColumnsSection__leftColumn > table > tbody > tr > td {
25
- padding-right: 0 !important;
26
- }
27
-
28
- .twoColumnsSection__rightColumn > table > tbody > tr > td {
29
- padding-left: 0 !important;
30
- }
31
-
32
- .twoColumnsSection__leftColumn {
33
- margin-bottom: 20px;
34
- }
35
- }
36
- `);
37
- export const Default = {
38
- parameters: { theme },
39
- render: () => {
40
- const TwoColumnsSection = () => {
41
- const columnGap = 20;
42
- const halfGap = columnGap / 2;
43
- return (_jsxs(MjmlSection, { indent: true, className: "twoColumnsSection", children: [_jsxs(MjmlColumn, { className: "twoColumnsSection__leftColumn", paddingRight: halfGap, children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Left column" }), _jsx(MjmlText, { children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." })] }), _jsxs(MjmlColumn, { className: "twoColumnsSection__rightColumn", paddingLeft: halfGap, children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Right column" }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia." })] })] }));
44
- };
45
- return (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Two column layout" }), _jsx(MjmlText, { children: "Two columns with equal widths split evenly by MJML. Each column gets half-gap padding on its inner side to create the 20px gap. No explicit width calculation is needed." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsx(TwoColumnsSection, {}), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] }));
46
- },
47
- };
@@ -1,7 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { HtmlPixelImageBlock } from "../HtmlPixelImageBlock.js";
3
- type Story = StoryObj<typeof HtmlPixelImageBlock>;
4
- declare const config: Meta<typeof HtmlPixelImageBlock>;
5
- export default config;
6
- export declare const Default: Story;
7
- export declare const AspectRatioOverride: Story;
@@ -1,54 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlRaw } from "@faire/mjml-react";
3
- import { MjmlMailRoot } from "../../../components/mailRoot/MjmlMailRoot.js";
4
- import { MjmlSection } from "../../../components/section/MjmlSection.js";
5
- import { HtmlPixelImageBlock } from "../HtmlPixelImageBlock.js";
6
- import { exampleBlockData } from "./exampleBlockData.js";
7
- const config = {
8
- title: "Components/Blocks/HtmlPixelImageBlock",
9
- component: HtmlPixelImageBlock,
10
- tags: ["autodocs"],
11
- parameters: {
12
- mailRoot: false,
13
- docs: {
14
- description: {
15
- component: "Renders a pixel-image from the DAM as a raw `<img>` tag, for use in HTML-only emails or inside MJML ending tags such as `MjmlRaw`.\n\n_Note: this story may fail to load the actual image when the API fixtures don't include the referenced DAM file._",
16
- },
17
- },
18
- },
19
- argTypes: {
20
- data: { control: false },
21
- aspectRatio: {
22
- control: "select",
23
- options: ["inherit", "16x9", "4x3", "3x2", "3x1", "2x1", "1x1", "1x2", "1x3", "2x3", "3x4", "9x16"],
24
- },
25
- },
26
- args: {
27
- data: exampleBlockData,
28
- width: 600,
29
- },
30
- };
31
- export default config;
32
- export const Default = {
33
- render: (args) => (_jsx(MjmlMailRoot, { config: {
34
- pixelImageBlock: {
35
- validSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560, 3200, 3840],
36
- baseUrl: "",
37
- },
38
- }, children: _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlRaw, { children: _jsx(HtmlPixelImageBlock, { ...args }) }) }) }) })),
39
- };
40
- export const AspectRatioOverride = {
41
- parameters: {
42
- docs: {
43
- description: {
44
- story: "Renders the same DAM image at its native aspect ratio and overridden to `16x9`, demonstrating how the `aspectRatio` prop reframes the rendered image without changing the source DAM record.",
45
- },
46
- },
47
- },
48
- render: (args) => (_jsx(MjmlMailRoot, { config: {
49
- pixelImageBlock: {
50
- validSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560, 3200, 3840],
51
- baseUrl: "",
52
- },
53
- }, children: _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsxs(MjmlRaw, { children: [_jsx(HtmlPixelImageBlock, { ...args }), _jsx(HtmlPixelImageBlock, { ...args, aspectRatio: "16x9" })] }) }) }) })),
54
- };
@@ -1,7 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { MjmlPixelImageBlock } from "../MjmlPixelImageBlock.js";
3
- type Story = StoryObj<typeof MjmlPixelImageBlock>;
4
- declare const config: Meta<typeof MjmlPixelImageBlock>;
5
- export default config;
6
- export declare const Default: Story;
7
- export declare const AspectRatioOverride: Story;
@@ -1,54 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn } from "@faire/mjml-react";
3
- import { MjmlMailRoot } from "../../../components/mailRoot/MjmlMailRoot.js";
4
- import { MjmlSection } from "../../../components/section/MjmlSection.js";
5
- import { MjmlPixelImageBlock } from "../MjmlPixelImageBlock.js";
6
- import { exampleBlockData } from "./exampleBlockData.js";
7
- const config = {
8
- title: "Components/Blocks/MjmlPixelImageBlock",
9
- component: MjmlPixelImageBlock,
10
- tags: ["autodocs"],
11
- parameters: {
12
- mailRoot: false,
13
- docs: {
14
- description: {
15
- component: "Renders a pixel-image from the DAM as `MjmlImage`. Must be placed within an `MjmlColumn`.\n\n_Note: this story may fail to load the actual image when the API fixtures don't include the referenced DAM file._",
16
- },
17
- },
18
- },
19
- argTypes: {
20
- data: { control: false },
21
- aspectRatio: {
22
- control: "select",
23
- options: ["inherit", "16x9", "4x3", "3x2", "3x1", "2x1", "1x1", "1x2", "1x3", "2x3", "3x4", "9x16"],
24
- },
25
- },
26
- args: {
27
- data: exampleBlockData,
28
- width: 600,
29
- },
30
- };
31
- export default config;
32
- export const Default = {
33
- render: (args) => (_jsx(MjmlMailRoot, { config: {
34
- pixelImageBlock: {
35
- validSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560, 3200, 3840],
36
- baseUrl: "",
37
- },
38
- }, children: _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlPixelImageBlock, { ...args }) }) }) })),
39
- };
40
- export const AspectRatioOverride = {
41
- parameters: {
42
- docs: {
43
- description: {
44
- story: "Renders the same DAM image at its native aspect ratio and overridden to `16x9`, demonstrating how the `aspectRatio` prop reframes the rendered image without changing the source DAM record.",
45
- },
46
- },
47
- },
48
- render: (args) => (_jsx(MjmlMailRoot, { config: {
49
- pixelImageBlock: {
50
- validSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 2560, 3200, 3840],
51
- baseUrl: "",
52
- },
53
- }, children: _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlPixelImageBlock, { ...args }), _jsx(MjmlPixelImageBlock, { ...args, aspectRatio: "16x9" })] }) }) })),
54
- };
@@ -1,2 +0,0 @@
1
- import type { PixelImageBlockData } from "../../../blocks.generated.js";
2
- export declare const exampleBlockData: PixelImageBlockData;
@@ -1,19 +0,0 @@
1
- export const exampleBlockData = {
2
- damFile: {
3
- id: "example-image",
4
- name: "example-image.jpg",
5
- size: 1000000,
6
- mimetype: "image/jpeg",
7
- contentHash: "example-hash",
8
- archived: false,
9
- scope: { domain: "at" },
10
- fileUrl: "https://picsum.photos/seed/comet-pixel-image/1000/1000",
11
- image: {
12
- width: 1000,
13
- height: 1000,
14
- cropArea: { focalPoint: "SMART" },
15
- dominantColor: "#000000",
16
- },
17
- },
18
- urlTemplate: "https://picsum.photos/seed/comet-pixel-image/$resizeWidth/$resizeHeight",
19
- };
@@ -1,21 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { describe, expect, it } from "vitest";
4
- import { ConfigProvider } from "../../../config/ConfigProvider.js";
5
- import { usePixelImageBlockConfig } from "../usePixelImageBlockConfig.js";
6
- function ConfigProbe() {
7
- usePixelImageBlockConfig();
8
- return null;
9
- }
10
- describe("usePixelImageBlockConfig", () => {
11
- it("throws when config.pixelImageBlock is unset", () => {
12
- expect(() => renderToStaticMarkup(_jsx(ConfigProbe, {}))).toThrowError(/`pixelImageBlock` must be set/);
13
- });
14
- it("error message points at MjmlMailRoot and ConfigProvider", () => {
15
- expect(() => renderToStaticMarkup(_jsx(ConfigProbe, {}))).toThrowError(/MjmlMailRoot/);
16
- expect(() => renderToStaticMarkup(_jsx(ConfigProbe, {}))).toThrowError(/ConfigProvider/);
17
- });
18
- it("does not throw when config.pixelImageBlock is provided", () => {
19
- expect(() => renderToStaticMarkup(_jsx(ConfigProvider, { config: { pixelImageBlock: { validSizes: [640, 1280], baseUrl: "http://localhost:3000" } }, children: _jsx(ConfigProbe, {}) }))).not.toThrow();
20
- });
21
- });
@@ -1,205 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { renderToStaticMarkup } from "react-dom/server";
3
- import { describe, expect, it } from "vitest";
4
- import { ConfigProvider } from "../../../config/ConfigProvider.js";
5
- import { createTheme } from "../../../theme/createTheme.js";
6
- import { ThemeProvider } from "../../../theme/ThemeProvider.js";
7
- import { usePixelImageBlockData } from "../usePixelImageBlockData.js";
8
- function captureUsePixelImageData({ data, defaultRenderWidth, largestPossibleRenderWidth, aspectRatio, config }) {
9
- const captured = { value: undefined };
10
- function Probe() {
11
- captured.value = usePixelImageBlockData({ data, defaultRenderWidth, largestPossibleRenderWidth, aspectRatio });
12
- return null;
13
- }
14
- renderToStaticMarkup(_jsx(ThemeProvider, { theme: createTheme(), children: _jsx(ConfigProvider, { config: config, children: _jsx(Probe, {}) }) }));
15
- if (captured.value === undefined) {
16
- throw new Error("Probe did not run");
17
- }
18
- return captured.value;
19
- }
20
- function expectNonNull(value) {
21
- if (value === null) {
22
- throw new Error("Expected non-null result from usePixelImageBlockData");
23
- }
24
- return value;
25
- }
26
- const validSizes = [320, 640, 1280, 2048];
27
- const baseUrl = "http://localhost:3000";
28
- const config = { pixelImageBlock: { validSizes, baseUrl } };
29
- const smartUrlTemplate = "/dam/images/abc/resize:$resizeWidth:$resizeHeight/photo.jpg";
30
- const smartImageData = {
31
- damFile: {
32
- id: "id-1",
33
- name: "photo.jpg",
34
- size: 1000,
35
- mimetype: "image/jpeg",
36
- contentHash: "hash",
37
- archived: false,
38
- fileUrl: "/dam/files/id-1.jpg",
39
- image: {
40
- width: 4000,
41
- height: 2000,
42
- cropArea: { focalPoint: "SMART" },
43
- },
44
- },
45
- urlTemplate: smartUrlTemplate,
46
- };
47
- describe("usePixelImageBlockData — width selection", () => {
48
- it("picks the smallest validSizes entry >= defaultRenderWidth × 2", () => {
49
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, config }));
50
- expect(result.imageUrl).toContain(":640:");
51
- });
52
- it("picks an exact validSizes match when present", () => {
53
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 320, config }));
54
- expect(result.imageUrl).toContain(":640:");
55
- });
56
- it("falls back to the largest validSizes entry when none qualifies", () => {
57
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 2000, config }));
58
- expect(result.imageUrl).toContain(":2048:");
59
- });
60
- it("uses largestPossibleRenderWidth × 2 when defaultRenderWidth equals largestPossibleRenderWidth", () => {
61
- const result = expectNonNull(captureUsePixelImageData({
62
- data: smartImageData,
63
- defaultRenderWidth: 600,
64
- largestPossibleRenderWidth: 600,
65
- config,
66
- }));
67
- expect(result.imageUrl).toContain(":1200:");
68
- });
69
- it("defaults largestPossibleRenderWidth to theme.sizes.bodyWidth (600) and triggers DPR-2 fixed path at width 600", () => {
70
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 600, config }));
71
- expect(result.imageUrl).toContain(":1200:");
72
- });
73
- });
74
- describe("usePixelImageBlockData — URL prefixing", () => {
75
- it("prefixes a relative URL with config.pixelImageBlock.baseUrl", () => {
76
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, config }));
77
- expect(result.imageUrl.startsWith(`${baseUrl}/dam/images/abc/`)).toBe(true);
78
- });
79
- it("does not prefix an absolute URL", () => {
80
- const absoluteData = {
81
- ...smartImageData,
82
- urlTemplate: "https://cdn.example.com/$resizeWidth/$resizeHeight/photo.jpg",
83
- };
84
- const result = expectNonNull(captureUsePixelImageData({ data: absoluteData, defaultRenderWidth: 200, config }));
85
- expect(result.imageUrl.startsWith("https://cdn.example.com/")).toBe(true);
86
- expect(result.imageUrl.includes(baseUrl)).toBe(false);
87
- });
88
- });
89
- describe("usePixelImageBlockData — aspect ratio", () => {
90
- it("uses the natural image aspect ratio for SMART crop areas", () => {
91
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, config }));
92
- expect(result.desktopImageHeight).toBe(100);
93
- expect(result.imageUrl).toContain(":640:320/");
94
- });
95
- it("derives aspect ratio from crop dimensions for non-SMART crop areas", () => {
96
- const nonSmartData = {
97
- damFile: {
98
- id: "id-2",
99
- name: "photo2.jpg",
100
- size: 1000,
101
- mimetype: "image/jpeg",
102
- contentHash: "hash",
103
- archived: false,
104
- fileUrl: "/dam/files/id-2.jpg",
105
- image: {
106
- width: 4000,
107
- height: 2000,
108
- cropArea: { focalPoint: "CENTER", width: 50, height: 100, x: 25, y: 0 },
109
- },
110
- },
111
- urlTemplate: smartUrlTemplate,
112
- };
113
- const result = expectNonNull(captureUsePixelImageData({ data: nonSmartData, defaultRenderWidth: 200, config }));
114
- expect(result.desktopImageHeight).toBe(200);
115
- expect(result.imageUrl).toContain(":640:640/");
116
- });
117
- it("throws when crop dimensions are missing on a non-SMART crop area", () => {
118
- const malformedData = {
119
- damFile: {
120
- id: "id-3",
121
- name: "photo3.jpg",
122
- size: 1000,
123
- mimetype: "image/jpeg",
124
- contentHash: "hash",
125
- archived: false,
126
- fileUrl: "/dam/files/id-3.jpg",
127
- image: {
128
- width: 4000,
129
- height: 2000,
130
- cropArea: { focalPoint: "CENTER" },
131
- },
132
- },
133
- urlTemplate: smartUrlTemplate,
134
- };
135
- expect(() => captureUsePixelImageData({ data: malformedData, defaultRenderWidth: 200, config })).toThrow(/Missing crop dimensions/);
136
- });
137
- });
138
- describe("usePixelImageBlockData — aspectRatio override", () => {
139
- it("uses a numeric aspectRatio in place of the cropArea-derived ratio", () => {
140
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, aspectRatio: 16 / 9, config }));
141
- expect(result.imageUrl).toContain(":640:360/");
142
- expect(result.desktopImageHeight).toBe(113);
143
- });
144
- it.each([
145
- ["WxH", "16x9"],
146
- ["W:H", "16:9"],
147
- ["W/H", "16/9"],
148
- ])("parses string aspectRatio in %s form", (_label, value) => {
149
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, aspectRatio: value, config }));
150
- expect(result.imageUrl).toContain(":640:360/");
151
- expect(result.desktopImageHeight).toBe(113);
152
- });
153
- it("treats a single-token string as `width / 1`", () => {
154
- const result = expectNonNull(captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, aspectRatio: "2", config }));
155
- expect(result.imageUrl).toContain(":640:320/");
156
- expect(result.desktopImageHeight).toBe(100);
157
- });
158
- it("overrides a non-SMART cropArea ratio", () => {
159
- const nonSmartData = {
160
- damFile: {
161
- id: "id-override",
162
- name: "photo.jpg",
163
- size: 1000,
164
- mimetype: "image/jpeg",
165
- contentHash: "hash",
166
- archived: false,
167
- fileUrl: "/dam/files/id-override.jpg",
168
- image: {
169
- width: 4000,
170
- height: 2000,
171
- cropArea: { focalPoint: "CENTER", width: 50, height: 100, x: 25, y: 0 },
172
- },
173
- },
174
- urlTemplate: smartUrlTemplate,
175
- };
176
- const result = expectNonNull(captureUsePixelImageData({ data: nonSmartData, defaultRenderWidth: 200, aspectRatio: "16x9", config }));
177
- expect(result.imageUrl).toContain(":640:360/");
178
- expect(result.desktopImageHeight).toBe(113);
179
- });
180
- it("throws when the aspectRatio string is malformed", () => {
181
- expect(() => captureUsePixelImageData({ data: smartImageData, defaultRenderWidth: 200, aspectRatio: "not-a-ratio", config })).toThrow(/An error occurred while parsing the aspect ratio: not-a-ratio/);
182
- });
183
- });
184
- describe("usePixelImageBlockData — incomplete data", () => {
185
- it("returns null when damFile is absent", () => {
186
- const result = captureUsePixelImageData({ data: { urlTemplate: smartUrlTemplate }, defaultRenderWidth: 200, config });
187
- expect(result).toBeNull();
188
- });
189
- it("returns null when damFile.image is absent", () => {
190
- const noImageData = {
191
- damFile: {
192
- id: "id-4",
193
- name: "no-image.jpg",
194
- size: 0,
195
- mimetype: "image/jpeg",
196
- contentHash: "hash",
197
- archived: false,
198
- fileUrl: "/dam/files/id-4.jpg",
199
- },
200
- urlTemplate: smartUrlTemplate,
201
- };
202
- const result = captureUsePixelImageData({ data: noImageData, defaultRenderWidth: 200, config });
203
- expect(result).toBeNull();
204
- });
205
- });
@@ -1,10 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { HtmlDivider } from "../HtmlDivider.js";
3
- type Story = StoryObj<typeof HtmlDivider>;
4
- declare const config: Meta<typeof HtmlDivider>;
5
- export default config;
6
- export declare const Default: Story;
7
- export declare const CustomHeightAndColor: Story;
8
- export declare const WithTheme: Story;
9
- export declare const WithVariants: Story;
10
- export declare const GradientWithFallback: Story;
@@ -1,60 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlRaw, MjmlSpacer } from "@faire/mjml-react";
3
- import { createTheme } from "../../../theme/createTheme.js";
4
- import { MjmlSection } from "../../section/MjmlSection.js";
5
- import { HtmlDivider } from "../HtmlDivider.js";
6
- const config = {
7
- title: "Components/HtmlDivider",
8
- component: HtmlDivider,
9
- tags: ["autodocs"],
10
- argTypes: {
11
- variant: { control: "text" },
12
- height: { control: "number" },
13
- backgroundColor: { control: "text" },
14
- backgroundImage: { control: "text" },
15
- className: { control: "text" },
16
- style: { control: false },
17
- },
18
- render: (args) => (_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: "16px" }), _jsx(MjmlRaw, { children: _jsx(HtmlDivider, { ...args }) }), _jsx(MjmlSpacer, { height: "16px" })] }) })),
19
- };
20
- export default config;
21
- export const Default = {};
22
- export const CustomHeightAndColor = {
23
- args: {
24
- height: 8,
25
- backgroundColor: "#FF6B6B",
26
- },
27
- };
28
- export const WithTheme = {
29
- parameters: {
30
- theme: createTheme({
31
- divider: {
32
- height: 2,
33
- backgroundColor: "#5B4FC7",
34
- },
35
- }),
36
- },
37
- };
38
- export const WithVariants = {
39
- parameters: {
40
- theme: createTheme({
41
- divider: {
42
- defaultVariant: "thin",
43
- variants: {
44
- thin: { height: 1, backgroundColor: "#999999" },
45
- thick: { height: { default: 12, mobile: 8 }, backgroundColor: "#222222" },
46
- },
47
- },
48
- }),
49
- },
50
- render: (args) => (_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: "16px" }), _jsx(MjmlRaw, { children: _jsx(HtmlDivider, { ...args }) }), _jsx(MjmlSpacer, { height: "16px" }), _jsx(MjmlRaw, { children: _jsx(HtmlDivider, { ...args, variant: "thick" }) }), _jsx(MjmlSpacer, { height: "16px" })] }) })),
51
- };
52
- export const GradientWithFallback = {
53
- // `backgroundColor` is the solid fallback for clients that
54
- // don't render `background-image` (notably older Outlook).
55
- args: {
56
- height: 6,
57
- backgroundColor: "#5B4FC7",
58
- backgroundImage: "linear-gradient(to right, #5B4FC7, #FF6B6B, #FFD166)",
59
- },
60
- };
@@ -1,10 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- import { MjmlDivider } from "../MjmlDivider.js";
3
- type Story = StoryObj<typeof MjmlDivider>;
4
- declare const config: Meta<typeof MjmlDivider>;
5
- export default config;
6
- export declare const Default: Story;
7
- export declare const CustomHeightAndColor: Story;
8
- export declare const WithTheme: Story;
9
- export declare const WithVariants: Story;
10
- export declare const GradientWithFallback: Story;