@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/lib/index.js CHANGED
@@ -4,6 +4,8 @@ export { OneOfBlock } from "./blocks/factories/OneOfBlock.js";
4
4
  export { OptionalBlock } from "./blocks/factories/OptionalBlock.js";
5
5
  export { HtmlPixelImageBlock } from "./blocks/pixelImage/HtmlPixelImageBlock.js";
6
6
  export { MjmlPixelImageBlock } from "./blocks/pixelImage/MjmlPixelImageBlock.js";
7
+ export { HtmlButton } from "./components/button/HtmlButton.js";
8
+ export { MjmlButton } from "./components/button/MjmlButton.js";
7
9
  export { HtmlDivider } from "./components/divider/HtmlDivider.js";
8
10
  export { MjmlDivider } from "./components/divider/MjmlDivider.js";
9
11
  export { HtmlImage } from "./components/image/HtmlImage.js";
@@ -21,6 +23,6 @@ export { createTheme } from "./theme/createTheme.js";
21
23
  export { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./theme/responsiveValue.js";
22
24
  export { ThemeProvider, useTheme } from "./theme/ThemeProvider.js";
23
25
  export { css } from "./utils/css.js";
24
- export { Mjml, MjmlAccordion, MjmlAccordionElement, MjmlAccordionText, MjmlAccordionTitle, MjmlAll, MjmlAttributes, MjmlBody, MjmlBreakpoint, MjmlButton, MjmlCarousel, MjmlCarouselImage, MjmlClass, MjmlColumn, MjmlFont, MjmlGroup, MjmlHead, MjmlHero, MjmlHtmlAttribute, MjmlHtmlAttributes, MjmlInclude, MjmlNavbar, MjmlNavbarLink, MjmlPreview, MjmlRaw, MjmlSelector, MjmlSocial, MjmlSocialElement, MjmlSpacer, MjmlStyle, MjmlTable, MjmlTitle, } from "@faire/mjml-react";
26
+ export { Mjml, MjmlAccordion, MjmlAccordionElement, MjmlAccordionText, MjmlAccordionTitle, MjmlAll, MjmlAttributes, MjmlBody, MjmlBreakpoint, MjmlCarousel, MjmlCarouselImage, MjmlClass, MjmlColumn, MjmlFont, MjmlGroup, MjmlHead, MjmlHero, MjmlHtmlAttribute, MjmlHtmlAttributes, MjmlInclude, MjmlNavbar, MjmlNavbarLink, MjmlPreview, MjmlRaw, MjmlSelector, MjmlSocial, MjmlSocialElement, MjmlSpacer, MjmlStyle, MjmlTable, MjmlTitle, } from "@faire/mjml-react";
25
27
  export { MjmlComment, MjmlConditionalComment, MjmlHtml, MjmlTrackingPixel, MjmlYahooStyle } from "@faire/mjml-react/extensions/index.js";
26
28
  export { renderToMjml } from "@faire/mjml-react/utils/renderToMjml.js";
@@ -0,0 +1,26 @@
1
+ import { type ResponsiveValue } from "../theme/responsiveValue.js";
2
+ import type { ThemeBreakpoints } from "../theme/themeTypes.js";
3
+ type VariantStylesConstraint = Record<string, ResponsiveValue<string | number> | undefined>;
4
+ export interface ResponsiveVariantProperty<TVariantStyles> {
5
+ themeKey: keyof TVariantStyles;
6
+ /** CSS property name(s); defaults to the kebab-case form of `themeKey`. */
7
+ cssProperties?: string | readonly string[];
8
+ unit?: string;
9
+ }
10
+ /** A variant property is either a bare theme key (CSS name derived) or a descriptor for the exceptions. */
11
+ export type ResponsiveVariantProperties<TVariantStyles> = ReadonlyArray<keyof TVariantStyles | ResponsiveVariantProperty<TVariantStyles>>;
12
+ interface ResponsiveVariantGroup<TVariantStyles> {
13
+ selector: (variantName: string) => string;
14
+ properties: ResponsiveVariantProperties<TVariantStyles>;
15
+ }
16
+ interface GenerateResponsiveVariantCssOptions<TVariantStyles> {
17
+ breakpoints: ThemeBreakpoints;
18
+ variants: Record<string, TVariantStyles | undefined> | undefined;
19
+ groups: ReadonlyArray<ResponsiveVariantGroup<TVariantStyles>>;
20
+ }
21
+ /**
22
+ * Generates `max-width` media-query CSS for a themed component's variant overrides.
23
+ * Returns an empty string when no variant has breakpoint overrides.
24
+ */
25
+ export declare function generateResponsiveVariantCss<TVariantStyles extends VariantStylesConstraint>({ breakpoints, variants, groups, }: GenerateResponsiveVariantCssOptions<TVariantStyles>): string;
26
+ export {};
@@ -0,0 +1,66 @@
1
+ import { getResponsiveOverrides } from "../theme/responsiveValue.js";
2
+ import { css } from "../utils/css.js";
3
+ const toKebabCase = (value) => value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
4
+ /**
5
+ * Generates `max-width` media-query CSS for a themed component's variant overrides.
6
+ * Returns an empty string when no variant has breakpoint overrides.
7
+ */
8
+ export function generateResponsiveVariantCss({ breakpoints, variants, groups, }) {
9
+ if (!variants) {
10
+ return "";
11
+ }
12
+ const cssChunks = [];
13
+ for (const [variantName, variantStyles] of Object.entries(variants)) {
14
+ if (!variantStyles) {
15
+ continue;
16
+ }
17
+ for (const group of groups) {
18
+ const overridesByBreakpoint = collectOverridesByBreakpoint(variantStyles, group.properties);
19
+ cssChunks.push(renderMediaQueries({ breakpoints, selector: group.selector(variantName), overridesByBreakpoint }));
20
+ }
21
+ }
22
+ return cssChunks.filter(Boolean).join("\n");
23
+ }
24
+ function collectOverridesByBreakpoint(variantStyles, properties) {
25
+ const overridesByBreakpoint = new Map();
26
+ for (const entry of properties) {
27
+ const property = typeof entry === "object" ? entry : { themeKey: entry };
28
+ const value = variantStyles[property.themeKey];
29
+ if (value === undefined) {
30
+ continue;
31
+ }
32
+ const cssProperties = resolveCssProperties(property);
33
+ for (const { breakpointKey, value: breakpointValue } of getResponsiveOverrides(value)) {
34
+ const declarations = overridesByBreakpoint.get(breakpointKey) ?? [];
35
+ for (const cssProperty of cssProperties) {
36
+ declarations.push(formatDeclaration({ cssProperty, value: breakpointValue, unit: property.unit }));
37
+ }
38
+ overridesByBreakpoint.set(breakpointKey, declarations);
39
+ }
40
+ }
41
+ return overridesByBreakpoint;
42
+ }
43
+ function resolveCssProperties(property) {
44
+ const cssProperties = property.cssProperties ?? toKebabCase(String(property.themeKey));
45
+ return typeof cssProperties === "string" ? [cssProperties] : cssProperties;
46
+ }
47
+ function formatDeclaration({ cssProperty, value, unit }) {
48
+ return `${cssProperty}: ${String(value)}${unit ?? ""} !important`;
49
+ }
50
+ function renderMediaQueries({ breakpoints, selector, overridesByBreakpoint, }) {
51
+ const cssChunks = [];
52
+ for (const [breakpointKey, declarations] of overridesByBreakpoint) {
53
+ const breakpoint = breakpoints[breakpointKey];
54
+ if (!breakpoint) {
55
+ continue;
56
+ }
57
+ cssChunks.push(css `
58
+ ${breakpoint.belowMediaQuery} {
59
+ ${selector} {
60
+ ${declarations.join(";\n")}
61
+ }
62
+ }
63
+ `);
64
+ }
65
+ return cssChunks.join("\n");
66
+ }
@@ -1,9 +1,10 @@
1
- import type { Theme, ThemeBackgroundColors, ThemeBreakpoints, ThemeColors, ThemeDivider, ThemeSizes, ThemeText } from "./themeTypes.js";
1
+ import type { Theme, ThemeBackgroundColors, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeSizes, ThemeText } from "./themeTypes.js";
2
2
  type CreateThemeOverrides = {
3
3
  sizes?: Partial<ThemeSizes>;
4
4
  breakpoints?: Partial<ThemeBreakpoints>;
5
5
  text?: Partial<ThemeText>;
6
6
  divider?: Partial<ThemeDivider>;
7
+ button?: Partial<ThemeButton>;
7
8
  colors?: {
8
9
  background?: Partial<ThemeBackgroundColors>;
9
10
  } & Partial<Omit<ThemeColors, "background">>;
@@ -22,6 +22,7 @@ export function createTheme(overrides) {
22
22
  },
23
23
  text: { ...defaultTheme.text, ...overrides?.text },
24
24
  divider: { ...defaultTheme.divider, ...overrides?.divider },
25
+ button: { ...defaultTheme.button, ...overrides?.button },
25
26
  colors: {
26
27
  ...defaultTheme.colors,
27
28
  ...overrides?.colors,
@@ -1,3 +1,4 @@
1
+ import { defaultButtonStyles } from "../components/button/defaultButtonStyles.js";
1
2
  import { defaultDividerStyles } from "../components/divider/defaultDividerStyles.js";
2
3
  import { createBreakpoint } from "./createBreakpoint.js";
3
4
  export const defaultTheme = {
@@ -16,6 +17,7 @@ export const defaultTheme = {
16
17
  bottomSpacing: "16px",
17
18
  },
18
19
  divider: defaultDividerStyles,
20
+ button: defaultButtonStyles,
19
21
  colors: {
20
22
  background: {
21
23
  body: "#F2F2F2",
@@ -86,6 +86,62 @@ export interface ThemeDivider extends DividerStyles {
86
86
  defaultVariant?: DividerVariantName;
87
87
  variants?: DividerVariantsRecord;
88
88
  }
89
+ /**
90
+ * Single source of truth for button style property names and their value types.
91
+ * Both `ButtonStyles` and `ButtonVariantStyles` are derived from this interface.
92
+ *
93
+ * `padding` is the button's inner padding (the space around the label). It is
94
+ * deliberately distinct from `MjmlButton`'s `padding` prop, which is the outer
95
+ * spacing around the button.
96
+ */
97
+ interface ButtonStyleMap {
98
+ color: string;
99
+ backgroundColor: string;
100
+ /**
101
+ * A background image — typically a gradient. Rendered as progressive
102
+ * enhancement on top of `backgroundColor`; clients that don't render it
103
+ * (notably Outlook) fall back to the solid color.
104
+ */
105
+ backgroundImage: string;
106
+ border: string;
107
+ borderRadius: string;
108
+ fontFamily: string;
109
+ fontSize: string;
110
+ fontWeight: string | number;
111
+ lineHeight: string | number;
112
+ padding: string;
113
+ }
114
+ /** Base button styles where each property holds a plain value. */
115
+ export type ButtonStyles = {
116
+ [K in keyof ButtonStyleMap]?: ButtonStyleMap[K];
117
+ };
118
+ /** Variant button styles where each property supports responsive values. */
119
+ export type ButtonVariantStyles = {
120
+ [K in keyof ButtonStyleMap]?: ResponsiveValue<ButtonStyleMap[K]>;
121
+ };
122
+ /**
123
+ * Defines the variants available on the `MjmlButton` and `HtmlButton` components.
124
+ *
125
+ * ```ts
126
+ * declare module "@comet/mail-react" {
127
+ * interface ButtonVariants {
128
+ * primary: true;
129
+ * secondary: true;
130
+ * }
131
+ * }
132
+ * ```
133
+ */
134
+ export interface ButtonVariants {
135
+ }
136
+ type ButtonVariantsRecord = keyof ButtonVariants extends never ? Record<string, ButtonVariantStyles> : {
137
+ [K in keyof ButtonVariants]?: ButtonVariantStyles;
138
+ };
139
+ export type ButtonVariantName = keyof ButtonVariants extends never ? string : keyof ButtonVariants;
140
+ /** Theme configuration for button styles, variants, and default variant. */
141
+ export interface ThemeButton extends ButtonStyles {
142
+ defaultVariant?: ButtonVariantName;
143
+ variants?: ButtonVariantsRecord;
144
+ }
89
145
  /**
90
146
  * A resolved breakpoint with its numeric value and a ready-to-use media query
91
147
  * string that targets viewports narrower than this breakpoint.
@@ -142,6 +198,7 @@ export interface Theme {
142
198
  breakpoints: ThemeBreakpoints;
143
199
  text: ThemeText;
144
200
  divider: ThemeDivider;
201
+ button: ThemeButton;
145
202
  colors: ThemeColors;
146
203
  }
147
204
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@comet/mail-react",
3
- "version": "9.0.0-beta.5",
3
+ "version": "9.0.0-beta.6",
4
4
  "description": "Utilities for building HTML emails with React",
5
5
  "license": "BSD-2-Clause",
6
6
  "type": "module",
@@ -27,23 +27,28 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "@storybook/addon-docs": "^10.3.5",
30
+ "@storybook/addon-vitest": "10.3.5",
30
31
  "@storybook/react-vite": "^10.3.5",
31
32
  "@types/mjml": "^4.7.4",
32
33
  "@types/mjml-browser": "^4.15.0",
33
34
  "@types/react": "^19.2.15",
34
35
  "@types/react-dom": "^19.2.3",
36
+ "@vitest/browser": "4.1.8",
37
+ "@vitest/browser-playwright": "4.1.8",
35
38
  "chokidar-cli": "^3.0.0",
36
39
  "eslint": "^9.39.4",
37
40
  "npm-run-all2": "^8.0.4",
41
+ "playwright": "^1.49.1",
38
42
  "prettier": "^3.6.2",
39
43
  "react": "^19.2.6",
40
44
  "react-dom": "^19.2.6",
41
45
  "rimraf": "^6.1.2",
42
46
  "storybook": "^10.3.5",
43
47
  "typescript": "^5.9.3",
44
- "vitest": "^4.0.16",
45
- "@comet/eslint-config": "9.0.0-beta.5",
46
- "@comet/cli": "9.0.0-beta.5"
48
+ "vite": "^7.3.1",
49
+ "vitest": "^4.1.8",
50
+ "@comet/cli": "9.0.0-beta.6",
51
+ "@comet/eslint-config": "9.0.0-beta.6"
47
52
  },
48
53
  "peerDependencies": {
49
54
  "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
@@ -62,7 +67,7 @@
62
67
  "registry": "https://registry.npmjs.org"
63
68
  },
64
69
  "scripts": {
65
- "build": "pnpm run clean && pnpm run generate-block-types && tsc",
70
+ "build": "pnpm run clean && pnpm run generate-block-types && tsc -p tsconfig.build.json",
66
71
  "build-storybook": "storybook build",
67
72
  "clean": "rimraf lib 'src/**/*.generated.ts'",
68
73
  "dev": "pnpm run clean && pnpm generate-block-types && tsc --watch",
@@ -77,7 +82,9 @@
77
82
  "lint:prettier": "pnpm exec prettier --check '*.{ts,js,json,md,yml,yaml}'",
78
83
  "lint:tsc": "tsc",
79
84
  "storybook": "storybook dev -p 6066 --no-open",
80
- "test": "vitest --run",
85
+ "test": "pnpm run test:unit && pnpm run test:storybook",
86
+ "test:storybook": "vitest --run --project storybook --outputFile.junit=./junit-storybook.xml",
87
+ "test:unit": "vitest --run --project unit --outputFile.junit=./junit-unit.xml",
81
88
  "test:watch": "vitest --watch"
82
89
  }
83
90
  }
@@ -1,5 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- declare const config: Meta;
3
- export default config;
4
- type Story = StoryObj;
5
- export declare const Default: Story;
@@ -1,32 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer, MjmlTable } from "@faire/mjml-react";
3
- import { HtmlInlineLink } from "../../components/inlineLink/HtmlInlineLink.js";
4
- import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
5
- import { MjmlSection } from "../../components/section/MjmlSection.js";
6
- import { HtmlText } from "../../components/text/HtmlText.js";
7
- import { MjmlText } from "../../components/text/MjmlText.js";
8
- import { MjmlWrapper } from "../../components/wrapper/MjmlWrapper.js";
9
- import { createTheme } from "../../theme/createTheme.js";
10
- const config = {
11
- title: "Examples/CustomBackgroundForFooter",
12
- parameters: { mailRoot: false },
13
- };
14
- export default config;
15
- export const Default = {
16
- render: () => {
17
- const theme = createTheme({
18
- text: {
19
- defaultVariant: "body",
20
- variants: {
21
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
22
- body: { fontSize: "14px", lineHeight: "20px" },
23
- legal: { fontSize: "12px", lineHeight: "18px" },
24
- },
25
- },
26
- });
27
- const footerTheme = structuredClone(theme);
28
- footerTheme.colors.background.content = "#2d4a6e";
29
- footerTheme.text.color = "#c8d8e9";
30
- return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "This is a notification email" }), _jsx(MjmlText, { bottomSpacing: true, children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia. Occaecati vel possimus similique reiciendis possimus iure rerum sit architecto." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsxs(MjmlWrapper, { backgroundColor: "#dddddd", children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { align: "center", bottomSpacing: true, children: "\u00A9 2026 Company Name \u2013 All rights reserved" }), _jsx(MjmlText, { variant: "legal", align: "center", bottomSpacing: true, children: "Legal text, corporis eos et quia. Assumenda eum maiores esse. Voluptas laudantium cupiditate aut repudiandae iste fugiat nam. Quas in debitis. Sed laudantium illum aut occaecati excepturi veniam harum reprehenderit." })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlTable, { width: "auto", align: "center", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/privacy", children: "Privacy Policy" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/imprint", children: "Imprint" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com", children: "Website" }) })] }) }) }), _jsx(MjmlSpacer, { height: 20 })] }) })] })] }));
31
- },
32
- };
@@ -1,5 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- declare const config: Meta;
3
- export default config;
4
- type Story = StoryObj;
5
- export declare const Default: Story;
@@ -1,32 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer, MjmlTable } from "@faire/mjml-react";
3
- import { HtmlInlineLink } from "../../components/inlineLink/HtmlInlineLink.js";
4
- import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
5
- import { MjmlSection } from "../../components/section/MjmlSection.js";
6
- import { HtmlText } from "../../components/text/HtmlText.js";
7
- import { MjmlText } from "../../components/text/MjmlText.js";
8
- import { createTheme } from "../../theme/createTheme.js";
9
- import { ThemeProvider } from "../../theme/ThemeProvider.js";
10
- const config = {
11
- title: "Examples/CustomNestedFooterTheme",
12
- parameters: { mailRoot: false },
13
- };
14
- export default config;
15
- export const Default = {
16
- render: () => {
17
- const theme = createTheme({
18
- text: {
19
- defaultVariant: "body",
20
- variants: {
21
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
22
- body: { fontSize: "14px", lineHeight: "20px" },
23
- legal: { fontSize: "12px", lineHeight: "18px" },
24
- },
25
- },
26
- });
27
- const footerTheme = structuredClone(theme);
28
- footerTheme.colors.background.content = "#2d4a6e";
29
- footerTheme.text.color = "#c8d8e9";
30
- return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "This is a notification email" }), _jsx(MjmlText, { bottomSpacing: true, children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia. Occaecati vel possimus similique reiciendis possimus iure rerum sit architecto." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsxs(ThemeProvider, { theme: footerTheme, children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 20 }), _jsx(MjmlText, { align: "center", bottomSpacing: true, children: "\u00A9 2026 Company Name \u2013 All rights reserved" }), _jsx(MjmlText, { variant: "legal", align: "center", bottomSpacing: true, children: "Legal text, corporis eos et quia. Assumenda eum maiores esse. Voluptas laudantium cupiditate aut repudiandae iste fugiat nam. Quas in debitis. Sed laudantium illum aut occaecati excepturi veniam harum reprehenderit." })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlTable, { width: "auto", align: "center", children: _jsx("tbody", { children: _jsxs("tr", { children: [_jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/privacy", children: "Privacy Policy" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com/imprint", children: "Imprint" }) }), _jsx("td", { width: "20px" }), _jsx(HtmlText, { align: "center", children: _jsx(HtmlInlineLink, { href: "https://example.com", children: "Website" }) })] }) }) }), _jsx(MjmlSpacer, { height: 20 })] }) })] })] }));
31
- },
32
- };
@@ -1,5 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- declare const config: Meta;
3
- export default config;
4
- type Story = StoryObj;
5
- export declare const Default: Story;
@@ -1,25 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer } from "@faire/mjml-react";
3
- import { MjmlMailRoot } from "../../components/mailRoot/MjmlMailRoot.js";
4
- import { MjmlSection } from "../../components/section/MjmlSection.js";
5
- import { MjmlText } from "../../components/text/MjmlText.js";
6
- import { createTheme } from "../../theme/createTheme.js";
7
- const config = {
8
- title: "Examples/NotificationEmail",
9
- parameters: { mailRoot: false },
10
- };
11
- export default config;
12
- export const Default = {
13
- render: () => {
14
- const theme = createTheme({
15
- text: {
16
- defaultVariant: "body",
17
- variants: {
18
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
19
- body: { fontSize: "16px", lineHeight: "24px" },
20
- },
21
- },
22
- });
23
- return (_jsxs(MjmlMailRoot, { theme: theme, children: [_jsx(MjmlSection, { backgroundColor: "#1a1a1a", indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 15 }), _jsx(MjmlText, { color: "#ffffff", fontWeight: "bold", align: "center", children: "Company Name" }), _jsx(MjmlSpacer, { height: 15 })] }) }), _jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "This is a notification email" }), _jsx(MjmlText, { bottomSpacing: true, children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur. Ratione perferendis distinctio sapiente est. Dolor consequatur qui excepturi natus." }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus quis soluta. Est recusandae delectus sed sed deserunt velit quia. Occaecati vel possimus similique reiciendis possimus iure rerum sit architecto." }), _jsx(MjmlSpacer, { height: 30 })] }) })] }));
24
- },
25
- };
@@ -1,6 +0,0 @@
1
- import type { Meta, StoryObj } from "@storybook/react-vite";
2
- declare const config: Meta;
3
- export default config;
4
- export declare const SmallColumnLeft: StoryObj;
5
- export declare const SmallColumnRight: StoryObj;
6
- export declare const ReversedMobileStackOrder: StoryObj;
@@ -1,108 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { MjmlColumn, MjmlSpacer, MjmlWrapper } from "@faire/mjml-react";
3
- import { MjmlImage } from "../../components/image/MjmlImage.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: "Layout Patterns/Asymmetric Two-Column Layout",
12
- };
13
- export default config;
14
- const theme = createTheme({
15
- text: {
16
- defaultVariant: "body",
17
- variants: {
18
- heading: { fontSize: "22px", lineHeight: "28px", fontWeight: "bold" },
19
- body: { fontSize: "16px", lineHeight: "24px" },
20
- },
21
- },
22
- });
23
- const SMALL_COLUMN_WIDTH = 120;
24
- const COLUMN_GAP = 20;
25
- const sectionIndent = getDefaultFromResponsiveValue(theme.sizes.contentIndentation);
26
- const sectionInnerWidth = theme.sizes.bodyWidth - 2 * sectionIndent;
27
- const fluidColumnWidth = sectionInnerWidth - SMALL_COLUMN_WIDTH;
28
- registerStyles((theme) => css `
29
- ${theme.breakpoints.default.belowMediaQuery} {
30
- .asymmetricLayoutLeft__fluidColumn {
31
- width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
32
- max-width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
33
- }
34
- }
35
-
36
- ${theme.breakpoints.mobile.belowMediaQuery} {
37
- .asymmetricLayoutLeft__fluidColumn {
38
- width: 100% !important;
39
- max-width: 100% !important;
40
- }
41
-
42
- .asymmetricLayoutLeft__smallColumn {
43
- margin-bottom: 10px;
44
- }
45
-
46
- .asymmetricLayoutLeft__fluidColumn > table > tbody > tr > td {
47
- padding-left: 0 !important;
48
- }
49
- }
50
- `);
51
- export const SmallColumnLeft = {
52
- parameters: { theme },
53
- render: () => (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Small column on the left" }), _jsx(MjmlText, { children: "A fixed-width column on the left with a fluid column taking the remaining space. On mobile, columns stack vertically with the small column on top." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsxs(MjmlSection, { indent: true, children: [_jsx(MjmlColumn, { className: "asymmetricLayoutLeft__smallColumn", width: `${SMALL_COLUMN_WIDTH}px`, verticalAlign: "middle", children: _jsx(MjmlImage, { src: `https://picsum.photos/seed/1/${SMALL_COLUMN_WIDTH}/150`, alt: "Placeholder", align: "center", width: SMALL_COLUMN_WIDTH }) }), _jsxs(MjmlColumn, { className: "asymmetricLayoutLeft__fluidColumn", width: `${fluidColumnWidth}px`, paddingLeft: `${COLUMN_GAP}px`, verticalAlign: "middle", children: [_jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Fluid column" }), _jsx(MjmlText, { children: "This column takes the remaining width after the fixed-width column. The gap between columns is created via padding on the fluid column's inner edge." })] })] }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] })),
54
- };
55
- registerStyles((theme) => css `
56
- ${theme.breakpoints.default.belowMediaQuery} {
57
- .asymmetricLayoutRight__fluidColumn {
58
- width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
59
- max-width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
60
- }
61
- }
62
-
63
- ${theme.breakpoints.mobile.belowMediaQuery} {
64
- .asymmetricLayoutRight__fluidColumn {
65
- width: 100% !important;
66
- max-width: 100% !important;
67
- }
68
-
69
- .asymmetricLayoutRight__smallColumn {
70
- margin-top: 10px;
71
- }
72
-
73
- .asymmetricLayoutRight__fluidColumn > table > tbody > tr > td {
74
- padding-right: 0 !important;
75
- }
76
- }
77
- `);
78
- export const SmallColumnRight = {
79
- parameters: { theme },
80
- render: () => (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Small column on the right" }), _jsx(MjmlText, { children: "A fluid column on the left with a fixed-width column on the right. On mobile, columns stack vertically with the fluid column on top." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsxs(MjmlSection, { indent: true, children: [_jsxs(MjmlColumn, { className: "asymmetricLayoutRight__fluidColumn", width: `${fluidColumnWidth}px`, paddingRight: `${COLUMN_GAP}px`, verticalAlign: "middle", children: [_jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Fluid column" }), _jsx(MjmlText, { children: "This column takes the remaining width after the fixed-width column. The gap between columns is created via padding on the fluid column's inner edge." })] }), _jsx(MjmlColumn, { className: "asymmetricLayoutRight__smallColumn", width: `${SMALL_COLUMN_WIDTH}px`, verticalAlign: "middle", children: _jsx(MjmlImage, { src: `https://picsum.photos/seed/2/${SMALL_COLUMN_WIDTH}/150`, alt: "Placeholder", align: "center", width: SMALL_COLUMN_WIDTH }) })] }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] })),
81
- };
82
- registerStyles((theme) => css `
83
- ${theme.breakpoints.default.belowMediaQuery} {
84
- .asymmetricLayoutRtl__fluidColumn {
85
- width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
86
- max-width: calc(100% - ${SMALL_COLUMN_WIDTH}px) !important;
87
- }
88
- }
89
-
90
- ${theme.breakpoints.mobile.belowMediaQuery} {
91
- .asymmetricLayoutRtl__fluidColumn {
92
- width: 100% !important;
93
- max-width: 100% !important;
94
- }
95
-
96
- .asymmetricLayoutRtl__smallColumn {
97
- margin-bottom: 10px;
98
- }
99
-
100
- .asymmetricLayoutRtl__fluidColumn > table > tbody > tr > td {
101
- padding-right: 0 !important;
102
- }
103
- }
104
- `);
105
- export const ReversedMobileStackOrder = {
106
- parameters: { theme },
107
- render: () => (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Small column on the right, stacks on top on mobile" }), _jsxs(MjmlText, { children: ["Uses ", _jsx("code", { children: "direction=\"rtl\"" }), " on the section to visually place the small column on the right on desktop, while keeping it first in source order so it stacks on top on mobile. A wrapper handles the indentation separately to avoid a 1px line artifact in Outlook."] }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsx(MjmlWrapper, { padding: `0 ${sectionIndent}px`, backgroundColor: theme.colors.background.content, children: _jsxs(MjmlSection, { direction: "rtl", children: [_jsx(MjmlColumn, { className: "asymmetricLayoutRtl__smallColumn", width: `${SMALL_COLUMN_WIDTH}px`, verticalAlign: "middle", children: _jsx(MjmlImage, { src: `https://picsum.photos/seed/3/${SMALL_COLUMN_WIDTH}/150`, alt: "Placeholder", align: "center", width: SMALL_COLUMN_WIDTH }) }), _jsxs(MjmlColumn, { className: "asymmetricLayoutRtl__fluidColumn", width: `${fluidColumnWidth}px`, paddingRight: `${COLUMN_GAP}px`, verticalAlign: "middle", children: [_jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Fluid column" }), _jsxs(MjmlText, { children: ["Source order: small column first, fluid column second. On desktop, ", _jsx("code", { children: "direction=\"rtl\"" }), " flips the visual order so the fluid column appears on the left."] })] })] }) }), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] })),
108
- };
@@ -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,58 +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 Four-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
- .symmetricFourColumnsSection > table > tbody > tr > td {
26
- display: flex !important;
27
- flex-direction: column !important;
28
- gap: 20px !important;
29
- }
30
-
31
- .symmetricFourColumnsSection__column {
32
- flex: none !important;
33
- width: 100% !important;
34
- max-width: 100% !important;
35
- display: block !important;
36
- }
37
-
38
- .symmetricFourColumnsSection__column > table > tbody > tr > td {
39
- padding-left: 0 !important;
40
- padding-right: 0 !important;
41
- }
42
- }
43
- `);
44
- export const Default = {
45
- parameters: { theme },
46
- render: () => {
47
- const SymmetricFourColumnSection = () => {
48
- const columnGap = 20;
49
- const halfColumnGap = columnGap / 2;
50
- const availableContentWidth = theme.sizes.bodyWidth - 2 * getDefaultFromResponsiveValue(theme.sizes.contentIndentation);
51
- const contentWidthPerColumn = (availableContentWidth - 3 * columnGap) / 4;
52
- const outerColumnWidth = `${((contentWidthPerColumn + halfColumnGap) / availableContentWidth) * 100}%`;
53
- const innerColumnWidth = `${((contentWidthPerColumn + columnGap) / availableContentWidth) * 100}%`;
54
- return (_jsxs(MjmlSection, { indent: true, className: "symmetricFourColumnsSection", children: [_jsxs(MjmlColumn, { width: outerColumnWidth, paddingRight: halfColumnGap, className: "symmetricFourColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "First" }), _jsx(MjmlText, { children: "Minima ea distinctio quisquam. Illo reiciendis non officiis consectetur." })] }), _jsxs(MjmlColumn, { width: innerColumnWidth, paddingLeft: halfColumnGap, paddingRight: halfColumnGap, className: "symmetricFourColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Second" }), _jsx(MjmlText, { children: "Numquam aut voluptas numquam aspernatur. Consequatur quidem omnis dolorem natus." })] }), _jsxs(MjmlColumn, { width: innerColumnWidth, paddingLeft: halfColumnGap, paddingRight: halfColumnGap, className: "symmetricFourColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Third" }), _jsx(MjmlText, { children: "Occaecati vel possimus similique reiciendis iure rerum sit architecto." })] }), _jsxs(MjmlColumn, { width: outerColumnWidth, paddingLeft: halfColumnGap, className: "symmetricFourColumnsSection__column", children: [_jsx(MjmlText, { variant: "subheading", bottomSpacing: true, children: "Fourth" }), _jsx(MjmlText, { children: "Voluptas laudantium cupiditate aut repudiandae iste fugiat nam quas debitis." })] })] }));
55
- };
56
- return (_jsxs(_Fragment, { children: [_jsx(MjmlSection, { indent: true, children: _jsxs(MjmlColumn, { children: [_jsx(MjmlSpacer, { height: 30 }), _jsx(MjmlText, { variant: "heading", bottomSpacing: true, children: "Four column layout" }), _jsx(MjmlText, { children: "Four columns follow the same compensation pattern as three columns. The two inner columns each get a wider width to absorb their double-sided padding. On mobile, a flex reset neutralizes the desktop widths so columns can stack equally." }), _jsx(MjmlSpacer, { height: 30 })] }) }), _jsx(SymmetricFourColumnSection, {}), _jsx(MjmlSection, { indent: true, children: _jsx(MjmlColumn, { children: _jsx(MjmlSpacer, { height: 30 }) }) })] }));
57
- },
58
- };
@@ -1,5 +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;
5
- export declare const NeverStacks: StoryObj;