@dextinity/mail-react 2.0.0-canary-20260806092023 → 10.0.0-beta.0
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.
- package/lib/blocks/pixelImage/HtmlPixelImageBlock.d.ts +1 -0
- package/lib/blocks/pixelImage/HtmlPixelImageBlock.js +1 -0
- package/lib/blocks/richText/RichTextList.d.ts +19 -0
- package/lib/blocks/richText/RichTextList.js +82 -0
- package/lib/blocks/richText/createRichTextBlock.d.ts +4 -2
- package/lib/blocks/richText/createRichTextBlock.js +3 -1
- package/lib/blocks/richText/createRichTextRenderers.js +4 -3
- package/lib/components/button/HtmlButton.d.ts +2 -0
- package/lib/components/button/HtmlButton.js +2 -2
- package/lib/components/button/MjmlButton.js +0 -2
- package/lib/components/button/generateResponsiveButtonCss.js +1 -1
- package/lib/components/divider/HtmlDivider.d.ts +2 -0
- package/lib/components/divider/HtmlDivider.js +2 -0
- package/lib/components/divider/MjmlDivider.d.ts +4 -1
- package/lib/components/divider/MjmlDivider.js +5 -2
- package/lib/components/divider/generateResponsiveDividerCss.js +1 -1
- package/lib/components/image/HtmlImage.d.ts +1 -0
- package/lib/components/image/HtmlImage.js +1 -0
- package/lib/components/section/MjmlSection.js +10 -24
- package/lib/components/text/HtmlText.d.ts +2 -0
- package/lib/components/text/HtmlText.js +1 -1
- package/lib/components/text/MjmlText.d.ts +1 -1
- package/lib/components/text/MjmlText.js +3 -3
- package/lib/components/text/{textStyles.js → generateResponsiveTextCss.js} +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/styles/{generateResponsiveVariantCss.d.ts → responsiveCss.d.ts} +14 -0
- package/lib/styles/{generateResponsiveVariantCss.js → responsiveCss.js} +15 -0
- package/lib/theme/createTheme.d.ts +2 -1
- package/lib/theme/createTheme.js +1 -0
- package/lib/theme/defaultTheme.js +8 -0
- package/lib/theme/themeTypes.d.ts +27 -0
- package/package.json +5 -5
- /package/lib/components/text/{textStyles.d.ts → generateResponsiveTextCss.d.ts} +0 -0
|
@@ -6,6 +6,7 @@ export type HtmlPixelImageBlockProps = Omit<ComponentProps<"img">, "src" | "widt
|
|
|
6
6
|
*
|
|
7
7
|
* Use within raw HTML context — HTML-only emails or
|
|
8
8
|
* [MJML ending tags](https://documentation.mjml.io/#ending-tags) like `MjmlRaw`.
|
|
9
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlPixelImageBlock` in a `<tr>` and `<td>` of its own.
|
|
9
10
|
* For MJML context, use `MjmlPixelImageBlock`.
|
|
10
11
|
*/
|
|
11
12
|
export declare function HtmlPixelImageBlock({ data, width, largestPossibleRenderWidth, aspectRatio, className, ...imgProps }: HtmlPixelImageBlockProps): ReactNode;
|
|
@@ -7,6 +7,7 @@ import { usePixelImageBlockData } from "./usePixelImageBlockData.js";
|
|
|
7
7
|
*
|
|
8
8
|
* Use within raw HTML context — HTML-only emails or
|
|
9
9
|
* [MJML ending tags](https://documentation.mjml.io/#ending-tags) like `MjmlRaw`.
|
|
10
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlPixelImageBlock` in a `<tr>` and `<td>` of its own.
|
|
10
11
|
* For MJML context, use `MjmlPixelImageBlock`.
|
|
11
12
|
*/
|
|
12
13
|
export function HtmlPixelImageBlock({ data, width, largestPossibleRenderWidth, aspectRatio, className, ...imgProps }) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { Theme, VariantName } from "../../theme/themeTypes.js";
|
|
3
|
+
interface RichTextListItem {
|
|
4
|
+
key: string;
|
|
5
|
+
content: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
interface RichTextListProps {
|
|
8
|
+
ordered?: boolean;
|
|
9
|
+
variant?: VariantName;
|
|
10
|
+
/** When true, the variant's spacing below the block applies below the last item. */
|
|
11
|
+
bottomSpacing?: boolean;
|
|
12
|
+
/** The nesting level the theme's marker receives. Zero for a list that is not nested. */
|
|
13
|
+
depth: number;
|
|
14
|
+
items: RichTextListItem[];
|
|
15
|
+
}
|
|
16
|
+
/** Renders one draft-js list as a table, because cell padding is the only list indent Outlook applies reliably. */
|
|
17
|
+
export declare function RichTextList({ ordered, variant, bottomSpacing, depth, items }: RichTextListProps): ReactNode;
|
|
18
|
+
export declare function generateRichTextListStyles(theme: Theme): string;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { generateResponsiveTextCss } from "../../components/text/generateResponsiveTextCss.js";
|
|
4
|
+
import { useOutlookTextStyle } from "../../components/text/OutlookTextStyleContext.js";
|
|
5
|
+
import { registerStyles } from "../../styles/registerStyles.js";
|
|
6
|
+
import { generateResponsiveTokenCss } from "../../styles/responsiveCss.js";
|
|
7
|
+
import { defaultTheme } from "../../theme/defaultTheme.js";
|
|
8
|
+
import { getDefaultFromResponsiveValue, getDefaultOrUndefined } from "../../theme/responsiveValue.js";
|
|
9
|
+
import { useOptionalTheme } from "../../theme/ThemeProvider.js";
|
|
10
|
+
function resolveMarker(marker, options) {
|
|
11
|
+
return typeof marker === "function" ? marker(options) : marker;
|
|
12
|
+
}
|
|
13
|
+
// Variant names come from the consumer, so the `variant` prefix keeps them apart from the package's own modifiers.
|
|
14
|
+
function variantModifier(variantName) {
|
|
15
|
+
return `richTextBlock__list--variant${variantName.charAt(0).toUpperCase()}${variantName.slice(1)}`;
|
|
16
|
+
}
|
|
17
|
+
/** Renders one draft-js list as a table, because cell padding is the only list indent Outlook applies reliably. */
|
|
18
|
+
export function RichTextList({ ordered, variant, bottomSpacing, depth, items }) {
|
|
19
|
+
const theme = useOptionalTheme();
|
|
20
|
+
const outlookTextStyle = useOutlookTextStyle();
|
|
21
|
+
const list = theme?.list ?? defaultTheme.list;
|
|
22
|
+
const themeText = theme?.text ?? {};
|
|
23
|
+
const { defaultVariant, variants, ...baseTextStyles } = themeText;
|
|
24
|
+
const activeVariant = variant ?? defaultVariant;
|
|
25
|
+
const variantStyles = activeVariant ? variants?.[activeVariant] : undefined;
|
|
26
|
+
const mergedStyles = { ...baseTextStyles, ...variantStyles };
|
|
27
|
+
// The HTML parser can move a raw-HTML block's text element out of the table, so spacing set
|
|
28
|
+
// there would not apply to the list. The last row carries it instead.
|
|
29
|
+
const blockSpacing = bottomSpacing ? getDefaultOrUndefined(mergedStyles.bottomSpacing) : undefined;
|
|
30
|
+
// The cells cannot inherit the text styles: once the parser has moved the text element away,
|
|
31
|
+
// the nearest ancestor with a font size is MJML's column, whose font size is zero.
|
|
32
|
+
const fontStyle = {
|
|
33
|
+
...outlookTextStyle,
|
|
34
|
+
...(outlookTextStyle?.lineHeight !== undefined && { msoLineHeightRule: "exactly" }),
|
|
35
|
+
};
|
|
36
|
+
const itemSpacing = getDefaultFromResponsiveValue(list.itemSpacing);
|
|
37
|
+
const markerCellPadding = {
|
|
38
|
+
paddingLeft: getDefaultFromResponsiveValue(list.indent),
|
|
39
|
+
paddingRight: getDefaultFromResponsiveValue(list.markerGap),
|
|
40
|
+
};
|
|
41
|
+
return (_jsx("table", { role: "presentation", cellPadding: 0, cellSpacing: 0, border: 0, width: "100%", className: clsx("richTextBlock__list", ordered ? "richTextBlock__list--ordered" : "richTextBlock__list--unordered", activeVariant && variantModifier(activeVariant)), style: { borderCollapse: "collapse" }, children: _jsx("tbody", { children: items.map((item, index) => {
|
|
42
|
+
const isLastItem = index === items.length - 1;
|
|
43
|
+
const spacingBelow = isLastItem ? blockSpacing : itemSpacing;
|
|
44
|
+
const cellStyle = { ...fontStyle, ...(spacingBelow !== undefined && { paddingBottom: spacingBelow }) };
|
|
45
|
+
return (_jsxs("tr", { className: clsx("richTextBlock__listItem", !isLastItem && "richTextBlock__listItem--itemSpacing", isLastItem && blockSpacing !== undefined && "richTextBlock__listItem--blockSpacing"), children: [_jsx("td", { className: "richTextBlock__listItemMarker", align: ordered ? "right" : "left", valign: "top", style: {
|
|
46
|
+
...cellStyle,
|
|
47
|
+
...markerCellPadding,
|
|
48
|
+
whiteSpace: "nowrap", // The full-width text cell squeezes this column, so markers such as `10.` must stay on one line.
|
|
49
|
+
}, children: resolveMarker(ordered ? list.orderedMarker : list.unorderedMarker, { index, depth }) }), _jsx("td", { className: "richTextBlock__listItemText", width: "100%", valign: "top", style: cellStyle, children: item.content })] }, item.key));
|
|
50
|
+
}) }) }));
|
|
51
|
+
}
|
|
52
|
+
export function generateRichTextListStyles(theme) {
|
|
53
|
+
return [
|
|
54
|
+
generateResponsiveListSpacingCss(theme),
|
|
55
|
+
generateResponsiveTextCss(theme, {
|
|
56
|
+
styleSelector: (variantName) => `.${variantModifier(variantName)} .richTextBlock__listItemMarker, .${variantModifier(variantName)} .richTextBlock__listItemText`,
|
|
57
|
+
spacingSelector: (variantName) => `.${variantModifier(variantName)} .richTextBlock__listItem--blockSpacing > td`,
|
|
58
|
+
}),
|
|
59
|
+
]
|
|
60
|
+
.filter(Boolean)
|
|
61
|
+
.join("\n");
|
|
62
|
+
}
|
|
63
|
+
function generateResponsiveListSpacingCss(theme) {
|
|
64
|
+
return [
|
|
65
|
+
generateResponsiveTokenCss({
|
|
66
|
+
breakpoints: theme.breakpoints,
|
|
67
|
+
selector: ".richTextBlock__listItemMarker",
|
|
68
|
+
tokens: [
|
|
69
|
+
{ value: theme.list.indent, cssProperty: "padding-left", unit: "px" },
|
|
70
|
+
{ value: theme.list.markerGap, cssProperty: "padding-right", unit: "px" },
|
|
71
|
+
],
|
|
72
|
+
}),
|
|
73
|
+
generateResponsiveTokenCss({
|
|
74
|
+
breakpoints: theme.breakpoints,
|
|
75
|
+
selector: ".richTextBlock__listItem--itemSpacing > td",
|
|
76
|
+
tokens: [{ value: theme.list.itemSpacing, cssProperty: "padding-bottom", unit: "px" }],
|
|
77
|
+
}),
|
|
78
|
+
]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join("\n");
|
|
81
|
+
}
|
|
82
|
+
registerStyles(generateRichTextListStyles);
|
|
@@ -11,7 +11,9 @@ import type { CreateRichTextBlockOptions, RichTextBlockProps } from "./common.js
|
|
|
11
11
|
*
|
|
12
12
|
* `MjmlRichTextBlock` renders each draft block as `MjmlText` and must be
|
|
13
13
|
* placed within an `MjmlColumn`. `HtmlRichTextBlock` renders each draft block
|
|
14
|
-
* as `HtmlText` for raw-HTML contexts (e.g. inside `MjmlRaw`)
|
|
14
|
+
* as `HtmlText` for raw-HTML contexts (e.g. inside `MjmlRaw`); inside
|
|
15
|
+
* `MjmlRaw` in an `MjmlColumn`, place `HtmlRichTextBlock` in a `<tr>` and
|
|
16
|
+
* `<td>` of its own.
|
|
15
17
|
*
|
|
16
18
|
* ```ts
|
|
17
19
|
* export const { MjmlRichTextBlock, HtmlRichTextBlock } = createRichTextBlock({
|
|
@@ -25,6 +27,6 @@ import type { CreateRichTextBlockOptions, RichTextBlockProps } from "./common.js
|
|
|
25
27
|
export declare function createRichTextBlock<TLinkTypes extends Record<string, unknown> = Record<string, unknown>>(options?: CreateRichTextBlockOptions<TLinkTypes>): {
|
|
26
28
|
/** Renders CMS RichText block data (draft-js raw content) as one `MjmlText` per draft block. Must be placed within an `MjmlColumn`. */
|
|
27
29
|
MjmlRichTextBlock: (props: RichTextBlockProps) => ReactNode;
|
|
28
|
-
/** Renders CMS RichText block data (draft-js raw content) as one `HtmlText` div per draft block, for raw-HTML contexts such as `MjmlRaw`. */
|
|
30
|
+
/** Renders CMS RichText block data (draft-js raw content) as one `HtmlText` div per draft block, for raw-HTML contexts such as `MjmlRaw`. Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlRichTextBlock` in a `<tr>` and `<td>` of its own. */
|
|
29
31
|
HtmlRichTextBlock: (props: RichTextBlockProps) => ReactNode;
|
|
30
32
|
};
|
|
@@ -50,7 +50,9 @@ function renderRichTextContent({ draftContent, blockTypes, linkTypes, inline, bl
|
|
|
50
50
|
*
|
|
51
51
|
* `MjmlRichTextBlock` renders each draft block as `MjmlText` and must be
|
|
52
52
|
* placed within an `MjmlColumn`. `HtmlRichTextBlock` renders each draft block
|
|
53
|
-
* as `HtmlText` for raw-HTML contexts (e.g. inside `MjmlRaw`)
|
|
53
|
+
* as `HtmlText` for raw-HTML contexts (e.g. inside `MjmlRaw`); inside
|
|
54
|
+
* `MjmlRaw` in an `MjmlColumn`, place `HtmlRichTextBlock` in a `<tr>` and
|
|
55
|
+
* `<td>` of its own.
|
|
54
56
|
*
|
|
55
57
|
* ```ts
|
|
56
58
|
* export const { MjmlRichTextBlock, HtmlRichTextBlock } = createRichTextBlock({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { HtmlInlineLink } from "../../components/inlineLink/HtmlInlineLink.js";
|
|
3
|
+
import { RichTextList } from "./RichTextList.js";
|
|
3
4
|
const inlineStyleRenderers = {
|
|
4
5
|
// The explicit styles back up the semantic tags in rendering engines that don't apply their default styling.
|
|
5
6
|
BOLD: (children, { key }) => (_jsx("strong", { style: { fontWeight: "bold" }, children: children }, key)),
|
|
@@ -42,8 +43,8 @@ function renderWithLineBreaks(node) {
|
|
|
42
43
|
function createTextBlockRenderFn({ blockTextComponent: BlockText, blockTypeProps, lastBlockKey }) {
|
|
43
44
|
return (children, { keys }) => children.map((child, index) => (_jsx(BlockText, { bottomSpacing: keys[index] !== lastBlockKey, ...blockTypeProps, children: renderWithLineBreaks(child) }, keys[index])));
|
|
44
45
|
}
|
|
45
|
-
function createListBlockRenderFn({
|
|
46
|
-
return (children, { keys }) => (_jsx(BlockText, { bottomSpacing:
|
|
46
|
+
function createListBlockRenderFn({ ordered, blockTextComponent: BlockText, blockTypeProps, lastBlockKey, }) {
|
|
47
|
+
return (children, { keys, depth }) => (_jsx(BlockText, { bottomSpacing: false, ...blockTypeProps, children: _jsx(RichTextList, { ordered: ordered, variant: blockTypeProps.variant, bottomSpacing: !keys.includes(lastBlockKey), depth: depth, items: children.map((child, index) => ({ key: keys[index], content: renderWithLineBreaks(child) })) }) }, keys.join("-")));
|
|
47
48
|
}
|
|
48
49
|
const listBlockTypes = ["unordered-list-item", "ordered-list-item"];
|
|
49
50
|
export function createRichTextRenderers({ blockTypes, linkTypes, inline, blockTextComponent, lastBlockKey, }) {
|
|
@@ -57,7 +58,7 @@ export function createRichTextRenderers({ blockTypes, linkTypes, inline, blockTe
|
|
|
57
58
|
}
|
|
58
59
|
for (const listBlockType of listBlockTypes) {
|
|
59
60
|
blocks[listBlockType] = createListBlockRenderFn({
|
|
60
|
-
|
|
61
|
+
ordered: listBlockType === "ordered-list-item",
|
|
61
62
|
blockTextComponent,
|
|
62
63
|
blockTypeProps: blockTypes[listBlockType] ?? {},
|
|
63
64
|
lastBlockKey,
|
|
@@ -4,6 +4,8 @@ import type { ButtonProps } from "./buttonProps.js";
|
|
|
4
4
|
export type HtmlButtonProps = ButtonProps & Omit<AnchorHTMLAttributes<HTMLAnchorElement>, keyof ButtonProps>;
|
|
5
5
|
/**
|
|
6
6
|
* Themed button for use inside MJML ending tags or outside of the MJML context.
|
|
7
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlButton` in a `<tr>` and `<td>` of its own.
|
|
8
|
+
* For MJML context, use `MjmlButton`.
|
|
7
9
|
*/
|
|
8
10
|
export declare function HtmlButton({ variant: variantProp, fullWidth, target, href, className, style, children, ...restProps }: HtmlButtonProps): ReactNode;
|
|
9
11
|
export declare function generateHtmlButtonStyles(theme: Theme): string;
|
|
@@ -8,6 +8,8 @@ import { defaultButtonStyles } from "./defaultButtonStyles.js";
|
|
|
8
8
|
import { generateResponsiveButtonCss } from "./generateResponsiveButtonCss.js";
|
|
9
9
|
/**
|
|
10
10
|
* Themed button for use inside MJML ending tags or outside of the MJML context.
|
|
11
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlButton` in a `<tr>` and `<td>` of its own.
|
|
12
|
+
* For MJML context, use `MjmlButton`.
|
|
11
13
|
*/
|
|
12
14
|
export function HtmlButton({ variant: variantProp, fullWidth, target = "_blank", href = "#", className, style, children, ...restProps }) {
|
|
13
15
|
const theme = useOptionalTheme();
|
|
@@ -31,8 +33,6 @@ export function HtmlButton({ variant: variantProp, fullWidth, target = "_blank",
|
|
|
31
33
|
};
|
|
32
34
|
const anchorStyle = {
|
|
33
35
|
display: fullWidth ? "block" : "inline-block",
|
|
34
|
-
boxSizing: fullWidth ? "border-box" : undefined,
|
|
35
|
-
width: fullWidth ? "100%" : undefined,
|
|
36
36
|
backgroundColor,
|
|
37
37
|
backgroundImage: getDefaultOrUndefined(mergedStyles.backgroundImage),
|
|
38
38
|
color: getDefaultOrUndefined(mergedStyles.color),
|
|
@@ -4,6 +4,8 @@ import type { DividerProps } from "./dividerProps.js";
|
|
|
4
4
|
export type HtmlDividerProps = DividerProps;
|
|
5
5
|
/**
|
|
6
6
|
* Themed divider for use inside MJML ending tags or outside of the MJML context.
|
|
7
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlDivider` in a `<tr>` and `<td>` of its own.
|
|
8
|
+
* For MJML context, use `MjmlDivider`.
|
|
7
9
|
*/
|
|
8
10
|
export declare function HtmlDivider({ variant: variantProp, height: heightProp, backgroundColor: backgroundColorProp, backgroundImage: backgroundImageProp, className, style, }: HtmlDividerProps): ReactNode;
|
|
9
11
|
export declare function generateHtmlDividerStyles(theme: Theme): string;
|
|
@@ -10,6 +10,8 @@ import { generateResponsiveDividerCss } from "./generateResponsiveDividerCss.js"
|
|
|
10
10
|
const zeroWidthSpace = String.fromCharCode(0x200b);
|
|
11
11
|
/**
|
|
12
12
|
* Themed divider for use inside MJML ending tags or outside of the MJML context.
|
|
13
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlDivider` in a `<tr>` and `<td>` of its own.
|
|
14
|
+
* For MJML context, use `MjmlDivider`.
|
|
13
15
|
*/
|
|
14
16
|
export function HtmlDivider({ variant: variantProp, height: heightProp, backgroundColor: backgroundColorProp, backgroundImage: backgroundImageProp, className, style, }) {
|
|
15
17
|
const theme = useOptionalTheme();
|
|
@@ -2,6 +2,9 @@ import type { ReactNode } from "react";
|
|
|
2
2
|
import type { DividerProps } from "./dividerProps.js";
|
|
3
3
|
export type MjmlDividerProps = DividerProps;
|
|
4
4
|
/**
|
|
5
|
-
* Themed divider
|
|
5
|
+
* Themed divider.
|
|
6
|
+
*
|
|
7
|
+
* Must be placed within an `MjmlColumn`. For raw HTML context (e.g. inside `MjmlRaw`),
|
|
8
|
+
* use `HtmlDivider` instead.
|
|
6
9
|
*/
|
|
7
10
|
export declare function MjmlDivider({ variant, height, backgroundColor, backgroundImage, className, style }: MjmlDividerProps): ReactNode;
|
|
@@ -4,10 +4,13 @@ import clsx from "clsx";
|
|
|
4
4
|
import { useOptionalTheme } from "../../theme/ThemeProvider.js";
|
|
5
5
|
import { HtmlDivider } from "./HtmlDivider.js";
|
|
6
6
|
/**
|
|
7
|
-
* Themed divider
|
|
7
|
+
* Themed divider.
|
|
8
|
+
*
|
|
9
|
+
* Must be placed within an `MjmlColumn`. For raw HTML context (e.g. inside `MjmlRaw`),
|
|
10
|
+
* use `HtmlDivider` instead.
|
|
8
11
|
*/
|
|
9
12
|
export function MjmlDivider({ variant, height, backgroundColor, backgroundImage, className, style }) {
|
|
10
13
|
const theme = useOptionalTheme();
|
|
11
14
|
const activeVariant = variant ?? theme?.divider.defaultVariant;
|
|
12
|
-
return (_jsx(MjmlRaw, { children: _jsx(HtmlDivider, { variant: variant, height: height, backgroundColor: backgroundColor, backgroundImage: backgroundImage, className: clsx("mjmlDivider", activeVariant && `mjmlDivider--${activeVariant}`, className), style: style }) }));
|
|
15
|
+
return (_jsx(MjmlRaw, { children: _jsx("tr", { children: _jsx("td", { style: { padding: 0, fontSize: 0, lineHeight: 0, msoLineHeightRule: "exactly" }, children: _jsx(HtmlDivider, { variant: variant, height: height, backgroundColor: backgroundColor, backgroundImage: backgroundImage, className: clsx("mjmlDivider", activeVariant && `mjmlDivider--${activeVariant}`, className), style: style }) }) }) }));
|
|
13
16
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateResponsiveVariantCss } from "../../styles/
|
|
1
|
+
import { generateResponsiveVariantCss } from "../../styles/responsiveCss.js";
|
|
2
2
|
const dividerProperties = [
|
|
3
3
|
// line-height matches height so Outlook honors the declared cell height.
|
|
4
4
|
{ themeKey: "height", cssProperties: ["height", "line-height"], unit: "px" },
|
|
@@ -6,6 +6,7 @@ export type HtmlImageProps = ComponentProps<"img">;
|
|
|
6
6
|
*
|
|
7
7
|
* Use within raw HTML context — HTML-only emails or
|
|
8
8
|
* [MJML ending tags](https://documentation.mjml.io/#ending-tags) like `MjmlRaw`.
|
|
9
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlImage` in a `<tr>` and `<td>` of its own.
|
|
9
10
|
* For MJML context, use `MjmlImage`.
|
|
10
11
|
*/
|
|
11
12
|
export declare function HtmlImage({ className, ...restProps }: HtmlImageProps): ReactNode;
|
|
@@ -8,6 +8,7 @@ import { css } from "../../utils/css.js";
|
|
|
8
8
|
*
|
|
9
9
|
* Use within raw HTML context — HTML-only emails or
|
|
10
10
|
* [MJML ending tags](https://documentation.mjml.io/#ending-tags) like `MjmlRaw`.
|
|
11
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlImage` in a `<tr>` and `<td>` of its own.
|
|
11
12
|
* For MJML context, use `MjmlImage`.
|
|
12
13
|
*/
|
|
13
14
|
export function HtmlImage({ className, ...restProps }) {
|
|
@@ -2,9 +2,9 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
2
2
|
import { MjmlGroup, MjmlSection as BaseMjmlSection } from "@faire/mjml-react";
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
import { registerStyles } from "../../styles/registerStyles.js";
|
|
5
|
-
import {
|
|
5
|
+
import { generateResponsiveTokenCss } from "../../styles/responsiveCss.js";
|
|
6
|
+
import { getDefaultFromResponsiveValue } from "../../theme/responsiveValue.js";
|
|
6
7
|
import { useOptionalTheme } from "../../theme/ThemeProvider.js";
|
|
7
|
-
import { css } from "../../utils/css.js";
|
|
8
8
|
import { useIsInsideMjmlWrapper } from "../wrapper/InsideMjmlWrapperContext.js";
|
|
9
9
|
/** A section wrapper for email layouts. Must be a direct child of `MjmlBody` or `MjmlWrapper`. */
|
|
10
10
|
export function MjmlSection({ children, indent, disableResponsiveBehavior, slotProps, className, ...restProps }) {
|
|
@@ -24,25 +24,11 @@ function getIndentProps(theme) {
|
|
|
24
24
|
paddingRight: getDefaultFromResponsiveValue(theme.sizes.contentIndentation),
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
registerStyles((theme) => {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (!breakpoint) {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
return css `
|
|
39
|
-
${breakpoint.belowMediaQuery} {
|
|
40
|
-
.mjmlSection--indented > table > tbody > tr > td {
|
|
41
|
-
padding-left: ${override.value}px !important;
|
|
42
|
-
padding-right: ${override.value}px !important;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
`;
|
|
46
|
-
})
|
|
47
|
-
.join("\n");
|
|
48
|
-
});
|
|
27
|
+
registerStyles((theme) => generateResponsiveTokenCss({
|
|
28
|
+
breakpoints: theme.breakpoints,
|
|
29
|
+
selector: ".mjmlSection--indented > table > tbody > tr > td",
|
|
30
|
+
tokens: [
|
|
31
|
+
{ value: theme.sizes.contentIndentation, cssProperty: "padding-left", unit: "px" },
|
|
32
|
+
{ value: theme.sizes.contentIndentation, cssProperty: "padding-right", unit: "px" },
|
|
33
|
+
],
|
|
34
|
+
}));
|
|
@@ -43,6 +43,8 @@ export type HtmlTextProps<E extends keyof JSX.IntrinsicElements = "td"> = HtmlTe
|
|
|
43
43
|
} & Omit<ComponentPropsWithoutRef<E>, keyof HtmlTextOwnProps | "element">;
|
|
44
44
|
/**
|
|
45
45
|
* Themed text component for use inside MJML ending tags or outside of the MJML context.
|
|
46
|
+
* Inside `MjmlRaw` in an `MjmlColumn`, place `HtmlText` in a `<tr>` of its own,
|
|
47
|
+
* and in a `<td>` too when `element` is not the default `td`.
|
|
46
48
|
*/
|
|
47
49
|
export declare function HtmlText<E extends keyof JSX.IntrinsicElements>(props: HtmlTextOwnProps & {
|
|
48
50
|
element: E;
|
|
@@ -3,8 +3,8 @@ import clsx from "clsx";
|
|
|
3
3
|
import { registerStyles } from "../../styles/registerStyles.js";
|
|
4
4
|
import { getDefaultOrUndefined } from "../../theme/responsiveValue.js";
|
|
5
5
|
import { useTheme } from "../../theme/ThemeProvider.js";
|
|
6
|
+
import { generateResponsiveTextCss } from "./generateResponsiveTextCss.js";
|
|
6
7
|
import { OutlookTextStyleProvider } from "./OutlookTextStyleContext.js";
|
|
7
|
-
import { generateResponsiveTextCss } from "./textStyles.js";
|
|
8
8
|
export function HtmlText({ element: Element = "td", variant: variantProp, bottomSpacing, className, style, children, ...restProps }) {
|
|
9
9
|
const theme = useTheme();
|
|
10
10
|
const { defaultVariant, variants, ...baseStyles } = theme.text;
|
|
@@ -37,4 +37,4 @@ export type MjmlTextProps = IMjmlTextProps & {
|
|
|
37
37
|
* The `variant` and `bottomSpacing` props require a `ThemeProvider` (or `MjmlMailRoot`).
|
|
38
38
|
*/
|
|
39
39
|
export declare function MjmlText({ variant: variantProp, bottomSpacing, className, children, ...restProps }: MjmlTextProps): ReactNode;
|
|
40
|
-
export declare function
|
|
40
|
+
export declare function generateMjmlTextStyles(theme: Theme): string;
|
|
@@ -4,8 +4,8 @@ import clsx from "clsx";
|
|
|
4
4
|
import { registerStyles } from "../../styles/registerStyles.js";
|
|
5
5
|
import { getDefaultOrUndefined } from "../../theme/responsiveValue.js";
|
|
6
6
|
import { useOptionalTheme } from "../../theme/ThemeProvider.js";
|
|
7
|
+
import { generateResponsiveTextCss } from "./generateResponsiveTextCss.js";
|
|
7
8
|
import { OutlookTextStyleProvider } from "./OutlookTextStyleContext.js";
|
|
8
|
-
import { generateResponsiveTextCss } from "./textStyles.js";
|
|
9
9
|
/**
|
|
10
10
|
* Text component that can be styled using the theme, optionally using a variant.
|
|
11
11
|
*
|
|
@@ -56,10 +56,10 @@ function getThemedProps(theme, variantProp, bottomSpacing, explicitProps) {
|
|
|
56
56
|
},
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
|
-
export function
|
|
59
|
+
export function generateMjmlTextStyles(theme) {
|
|
60
60
|
return generateResponsiveTextCss(theme, {
|
|
61
61
|
styleSelector: (variantName) => `.mjmlText--${variantName} > div`,
|
|
62
62
|
spacingSelector: (variantName) => `.mjmlText--bottomSpacing.mjmlText--${variantName}`,
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
-
registerStyles(
|
|
65
|
+
registerStyles(generateMjmlTextStyles);
|
package/lib/index.d.ts
CHANGED
|
@@ -40,7 +40,7 @@ export { createTheme } from "./theme/createTheme.js";
|
|
|
40
40
|
export type { ResponsiveValue } from "./theme/responsiveValue.js";
|
|
41
41
|
export { getDefaultFromResponsiveValue, getResponsiveOverrides } from "./theme/responsiveValue.js";
|
|
42
42
|
export { ThemeProvider, useTheme } from "./theme/ThemeProvider.js";
|
|
43
|
-
export type { ButtonStyles, ButtonVariants, ButtonVariantStyles, DividerStyles, DividerVariants, DividerVariantStyles, TextStyles, TextVariants, TextVariantStyles, Theme, ThemeBackgroundColors, ThemeBreakpoint, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeSizes, ThemeText, } from "./theme/themeTypes.js";
|
|
43
|
+
export type { ButtonStyles, ButtonVariants, ButtonVariantStyles, DividerStyles, DividerVariants, DividerVariantStyles, ListMarker, ListMarkerOptions, TextStyles, TextVariants, TextVariantStyles, Theme, ThemeBackgroundColors, ThemeBreakpoint, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeList, ThemeSizes, ThemeText, } from "./theme/themeTypes.js";
|
|
44
44
|
export { css } from "./utils/css.js";
|
|
45
45
|
export { Mjml, MjmlAccordion, MjmlAccordionElement, type IMjmlAccordionElementProps as MjmlAccordionElementProps, type IMjmlAccordionProps as MjmlAccordionProps, MjmlAccordionText, type IMjmlAccordionTextProps as MjmlAccordionTextProps, MjmlAccordionTitle, type IMjmlAccordionTitleProps as MjmlAccordionTitleProps, MjmlAll, type IMjmlAllProps as MjmlAllProps, MjmlAttributes, type IMjmlAttributesProps as MjmlAttributesProps, MjmlBody, type IMjmlBodyProps as MjmlBodyProps, MjmlBreakpoint, type IMjmlBreakpointProps as MjmlBreakpointProps, MjmlCarousel, MjmlCarouselImage, type IMjmlCarouselImageProps as MjmlCarouselImageProps, type IMjmlCarouselProps as MjmlCarouselProps, MjmlClass, type IMjmlClassProps as MjmlClassProps, MjmlColumn, type IMjmlColumnProps as MjmlColumnProps, MjmlFont, type IMjmlFontProps as MjmlFontProps, MjmlGroup, type IMjmlGroupProps as MjmlGroupProps, MjmlHead, type IMjmlHeadProps as MjmlHeadProps, MjmlHero, type IMjmlHeroProps as MjmlHeroProps, MjmlHtmlAttribute, type IMjmlHtmlAttributeProps as MjmlHtmlAttributeProps, MjmlHtmlAttributes, type IMjmlHtmlAttributesProps as MjmlHtmlAttributesProps, MjmlInclude, type IMjmlIncludeProps as MjmlIncludeProps, MjmlNavbar, MjmlNavbarLink, type IMjmlNavbarLinkProps as MjmlNavbarLinkProps, type IMjmlNavbarProps as MjmlNavbarProps, MjmlPreview, type IMjmlPreviewProps as MjmlPreviewProps, type IMjmlProps as MjmlProps, MjmlRaw, type IMjmlRawProps as MjmlRawProps, MjmlSelector, type IMjmlSelectorProps as MjmlSelectorProps, MjmlSocial, MjmlSocialElement, type IMjmlSocialElementProps as MjmlSocialElementProps, type IMjmlSocialProps as MjmlSocialProps, MjmlSpacer, type IMjmlSpacerProps as MjmlSpacerProps, MjmlStyle, type IMjmlStyleProps as MjmlStyleProps, MjmlTable, type IMjmlTableProps as MjmlTableProps, MjmlTitle, type IMjmlTitleProps as MjmlTitleProps, } from "@faire/mjml-react";
|
|
46
46
|
export { MjmlComment, MjmlConditionalComment, MjmlHtml, MjmlTrackingPixel, MjmlYahooStyle } from "@faire/mjml-react/extensions/index.js";
|
|
@@ -23,4 +23,18 @@ interface GenerateResponsiveVariantCssOptions<TVariantStyles> {
|
|
|
23
23
|
* Returns an empty string when no variant has breakpoint overrides.
|
|
24
24
|
*/
|
|
25
25
|
export declare function generateResponsiveVariantCss<TVariantStyles extends VariantStylesConstraint>({ breakpoints, variants, groups, }: GenerateResponsiveVariantCssOptions<TVariantStyles>): string;
|
|
26
|
+
interface ResponsiveToken {
|
|
27
|
+
value: ResponsiveValue<string | number>;
|
|
28
|
+
cssProperty: string;
|
|
29
|
+
unit?: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generates `max-width` media-query CSS for theme tokens that are not keyed by variant name.
|
|
33
|
+
* Returns an empty string when no token has breakpoint overrides.
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateResponsiveTokenCss({ breakpoints, selector, tokens, }: {
|
|
36
|
+
breakpoints: ThemeBreakpoints;
|
|
37
|
+
selector: string;
|
|
38
|
+
tokens: readonly ResponsiveToken[];
|
|
39
|
+
}): string;
|
|
26
40
|
export {};
|
|
@@ -21,6 +21,21 @@ export function generateResponsiveVariantCss({ breakpoints, variants, groups, })
|
|
|
21
21
|
}
|
|
22
22
|
return cssChunks.filter(Boolean).join("\n");
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Generates `max-width` media-query CSS for theme tokens that are not keyed by variant name.
|
|
26
|
+
* Returns an empty string when no token has breakpoint overrides.
|
|
27
|
+
*/
|
|
28
|
+
export function generateResponsiveTokenCss({ breakpoints, selector, tokens, }) {
|
|
29
|
+
const overridesByBreakpoint = new Map();
|
|
30
|
+
for (const token of tokens) {
|
|
31
|
+
for (const { breakpointKey, value } of getResponsiveOverrides(token.value)) {
|
|
32
|
+
const declarations = overridesByBreakpoint.get(breakpointKey) ?? [];
|
|
33
|
+
declarations.push(formatDeclaration({ cssProperty: token.cssProperty, value, unit: token.unit }));
|
|
34
|
+
overridesByBreakpoint.set(breakpointKey, declarations);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return renderMediaQueries({ breakpoints, selector, overridesByBreakpoint });
|
|
38
|
+
}
|
|
24
39
|
function collectOverridesByBreakpoint(variantStyles, properties) {
|
|
25
40
|
const overridesByBreakpoint = new Map();
|
|
26
41
|
for (const entry of properties) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { Theme, ThemeBackgroundColors, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeSizes, ThemeText } from "./themeTypes.js";
|
|
1
|
+
import type { Theme, ThemeBackgroundColors, ThemeBreakpoints, ThemeButton, ThemeColors, ThemeDivider, ThemeList, 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
|
+
list?: Partial<ThemeList>;
|
|
6
7
|
divider?: Partial<ThemeDivider>;
|
|
7
8
|
button?: Partial<ThemeButton>;
|
|
8
9
|
colors?: {
|
package/lib/theme/createTheme.js
CHANGED
|
@@ -21,6 +21,7 @@ export function createTheme(overrides) {
|
|
|
21
21
|
...overrides?.breakpoints,
|
|
22
22
|
},
|
|
23
23
|
text: { ...defaultTheme.text, ...overrides?.text },
|
|
24
|
+
list: { ...defaultTheme.list, ...overrides?.list },
|
|
24
25
|
divider: { ...defaultTheme.divider, ...overrides?.divider },
|
|
25
26
|
button: { ...defaultTheme.button, ...overrides?.button },
|
|
26
27
|
colors: {
|
|
@@ -15,6 +15,14 @@ export const defaultTheme = {
|
|
|
15
15
|
fontSize: "16px",
|
|
16
16
|
lineHeight: "20px",
|
|
17
17
|
bottomSpacing: "16px",
|
|
18
|
+
color: "#000000",
|
|
19
|
+
},
|
|
20
|
+
list: {
|
|
21
|
+
indent: 8,
|
|
22
|
+
markerGap: 12,
|
|
23
|
+
itemSpacing: 8,
|
|
24
|
+
unorderedMarker: "•",
|
|
25
|
+
orderedMarker: ({ index }) => `${index + 1}.`,
|
|
18
26
|
},
|
|
19
27
|
divider: defaultDividerStyles,
|
|
20
28
|
button: defaultButtonStyles,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
import type { ResponsiveValue } from "./responsiveValue.js";
|
|
2
3
|
/**
|
|
3
4
|
* Single source of truth for text style property names and their value types.
|
|
@@ -46,6 +47,31 @@ export interface ThemeText extends TextStyles {
|
|
|
46
47
|
defaultVariant?: VariantName;
|
|
47
48
|
variants?: VariantsRecord;
|
|
48
49
|
}
|
|
50
|
+
/** The position of the item a marker belongs to. */
|
|
51
|
+
export interface ListMarkerOptions {
|
|
52
|
+
/** The item's position in its own list, counting from zero. */
|
|
53
|
+
index: number;
|
|
54
|
+
/** The nesting level of the item's list, zero when it is not nested. */
|
|
55
|
+
depth: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* A list item's marker content: a fixed node, or a function of the item's position.
|
|
59
|
+
* Use plain HTML elements, not MJML components.
|
|
60
|
+
*/
|
|
61
|
+
export type ListMarker = ReactNode | ((options: ListMarkerOptions) => ReactNode);
|
|
62
|
+
/** Spacing tokens in pixels and marker content, applying to lists the RichText block renders. */
|
|
63
|
+
export interface ThemeList {
|
|
64
|
+
/** The space before the marker, measured from the list's left edge. */
|
|
65
|
+
indent: ResponsiveValue;
|
|
66
|
+
/** The space between the marker and the item's text. */
|
|
67
|
+
markerGap: ResponsiveValue;
|
|
68
|
+
/** The space between items. The space below the last one comes from the text variant's `bottomSpacing`. */
|
|
69
|
+
itemSpacing: ResponsiveValue;
|
|
70
|
+
/** The marker of every item of an unordered list. */
|
|
71
|
+
unorderedMarker: ListMarker;
|
|
72
|
+
/** The marker of every item of an ordered list. */
|
|
73
|
+
orderedMarker: ListMarker;
|
|
74
|
+
}
|
|
49
75
|
/**
|
|
50
76
|
* Single source of truth for divider style property names and their value types.
|
|
51
77
|
* Both `DividerStyles` and `DividerVariantStyles` are derived from this interface.
|
|
@@ -197,6 +223,7 @@ export interface Theme {
|
|
|
197
223
|
sizes: ThemeSizes;
|
|
198
224
|
breakpoints: ThemeBreakpoints;
|
|
199
225
|
text: ThemeText;
|
|
226
|
+
list: ThemeList;
|
|
200
227
|
divider: ThemeDivider;
|
|
201
228
|
button: ThemeButton;
|
|
202
229
|
colors: ThemeColors;
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dextinity/mail-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0-beta.0",
|
|
4
4
|
"description": "Utilities for building HTML emails with React",
|
|
5
5
|
"license": "BSD-2-Clause",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
8
8
|
"directory": "packages/mail-react",
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/vivid-planet/
|
|
10
|
+
"url": "https://github.com/vivid-planet/dextinity"
|
|
11
11
|
},
|
|
12
12
|
"exports": {
|
|
13
13
|
".": "./lib/index.js",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"chokidar-cli": "^3.0.0",
|
|
40
40
|
"eslint": "^9.39.4",
|
|
41
41
|
"npm-run-all2": "^8.0.4",
|
|
42
|
-
"playwright": "^1.
|
|
42
|
+
"playwright": "^1.62.0",
|
|
43
43
|
"prettier": "^3.6.2",
|
|
44
44
|
"react": "^19.2.6",
|
|
45
45
|
"react-dom": "^19.2.6",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"typescript": "^5.9.3",
|
|
49
49
|
"vite": "^7.3.1",
|
|
50
50
|
"vitest": "^4.1.8",
|
|
51
|
-
"@dextinity/cli": "
|
|
52
|
-
"@dextinity/eslint-config": "
|
|
51
|
+
"@dextinity/cli": "10.0.0-beta.0",
|
|
52
|
+
"@dextinity/eslint-config": "10.0.0-beta.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
File without changes
|