Skip to content

Commit e8befa5

Browse files
authored
fix(components): prefer mjml enum type over CSSProperties (#85)
In the previous version enum was being pre-empted by CSSProperties. This resulted in some values not passing type checking when using validationLevel: "strict". Fixes #81
1 parent d422621 commit e8befa5

15 files changed

Lines changed: 191 additions & 69 deletions
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
export const ATTRIBUTES_TO_USE_CSSProperties_WITH = new Set([
2+
"color",
3+
"textDecoration",
4+
"textTransform",
5+
6+
"border",
7+
"borderRadius",
8+
"borderColor",
9+
"borderStyle",
10+
11+
"backgroundColor",
12+
"backgroundPosition",
13+
"backgroundSize",
14+
]);
15+
16+
/**
17+
* Converts an mjml type definition into a typescript type definition
18+
* Handles boolean, integer, enum, and
19+
* This is used to generate the types on the React <> mjml binding component.
20+
*/
21+
export function getPropTypeFromMjmlAttributeType(
22+
attribute: string,
23+
mjmlAttributeType: string
24+
): string {
25+
if (mjmlAttributeType === "boolean") {
26+
return "boolean";
27+
}
28+
if (mjmlAttributeType === "integer") {
29+
return "number";
30+
}
31+
// e.g. "vertical-align": "enum(top,bottom,middle)"
32+
if (mjmlAttributeType.startsWith("enum(")) {
33+
return transformEnumType(mjmlAttributeType);
34+
}
35+
if (ATTRIBUTES_TO_USE_CSSProperties_WITH.has(attribute)) {
36+
// When possible prefer using the CSSProperties definitions over the
37+
// less helpful "string" or "string | number" type definitions.
38+
return `React.CSSProperties["${attribute}"]`;
39+
}
40+
if (
41+
mjmlAttributeType.startsWith("unit") &&
42+
mjmlAttributeType.includes("px")
43+
) {
44+
return "string | number";
45+
}
46+
return "string";
47+
}
48+
49+
/**
50+
* Converts an mjml enum type definition into a typescript string literal type.
51+
* Strings like `"enum(a,b,c)"` become `"a" | "b" | "c"`.
52+
* This is used to generate the types on the React <> mjml binding component.
53+
*/
54+
function transformEnumType(mjmlAttributeType: string): string {
55+
return (
56+
mjmlAttributeType
57+
.match(/\(.*\)/)?.[0]
58+
?.slice(1, -1)
59+
.split(",")
60+
.map((str) => '"' + str + '"')
61+
.join(" | ") ?? "unknown"
62+
);
63+
}

scripts/generate-mjml-react.ts

Lines changed: 5 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import camelCase from "lodash.camelcase";
1111
import upperFirst from "lodash.upperfirst";
1212
import * as path from "path";
1313

14+
import { getPropTypeFromMjmlAttributeType } from "./generate-mjml-react-utils/getPropTypeFromMjmlAttributeType";
15+
1416
const MJML_REACT_DIR = "src";
1517

1618
const UTILS_FILE = "utils";
@@ -23,7 +25,7 @@ const GENERATED_HEADER_TSX = `
2325
*/
2426
`;
2527

26-
interface IMjmlComponent {
28+
export interface IMjmlComponent {
2729
componentName: string;
2830
allowedAttributes?: Record<string, string>;
2931
defaultAttributes?: Record<string, string>;
@@ -52,30 +54,14 @@ const MJML_COMPONENT_NAMES = MJML_COMPONENTS_TO_GENERATE.map(
5254
(component) => component.componentName
5355
);
5456

55-
const ATTRIBUTES_TO_USE_CSSProperties_WITH = new Set([
56-
"color",
57-
"textAlign",
58-
"verticalAlign",
59-
"textDecoration",
60-
"textTransform",
61-
62-
"border",
63-
"borderRadius",
64-
"borderColor",
65-
"borderStyle",
66-
67-
"backgroundColor",
68-
"backgroundPosition",
69-
"backgroundRepeat",
70-
"backgroundSize",
71-
]);
72-
7357
const TYPE_OVERRIDE: { [componentName: string]: { [prop: string]: string } } = {
7458
mjml: { owa: "string", lang: "string" },
7559
"mj-style": { inline: "boolean" },
7660
"mj-class": { name: "string" },
7761
"mj-table": { cellspacing: "string", cellpadding: "string" },
7862
"mj-selector": { path: "string" },
63+
"mj-section": { fullWidth: "boolean" },
64+
"mj-wrapper": { fullWidth: "boolean" },
7965
"mj-html-attribute": { name: "string" },
8066
"mj-include": { path: "string" },
8167
"mj-breakpoint": { width: "string" },
@@ -117,42 +103,6 @@ const ALLOW_ANY_PROPERTY = new Set(
117103
)
118104
);
119105

120-
function getPropTypeFromMjmlAttributeType(
121-
attribute: string,
122-
mjmlAttributeType: string
123-
): string {
124-
if (attribute === "fullWidth") {
125-
return "boolean";
126-
}
127-
if (ATTRIBUTES_TO_USE_CSSProperties_WITH.has(attribute)) {
128-
return `React.CSSProperties["${attribute}"]`;
129-
}
130-
if (
131-
mjmlAttributeType.startsWith("unit") &&
132-
mjmlAttributeType.includes("px")
133-
) {
134-
return "string | number";
135-
}
136-
if (mjmlAttributeType === "boolean") {
137-
return "boolean";
138-
}
139-
if (mjmlAttributeType === "integer") {
140-
return "number";
141-
}
142-
// e.g. "vertical-align": "enum(top,bottom,middle)"
143-
if (mjmlAttributeType.startsWith("enum")) {
144-
return (
145-
mjmlAttributeType
146-
.match(/\(.*\)/)?.[0]
147-
?.slice(1, -1)
148-
.split(",")
149-
.map((str) => "'" + str + "'")
150-
.join(" | ") ?? "unknown"
151-
);
152-
}
153-
return "string";
154-
}
155-
156106
function buildTypesForComponent(mjmlComponent: IMjmlComponent): string {
157107
const {
158108
componentName,

src/mjml/MjmlButton.tsx

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlColumn.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlGroup.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlHero.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlSection.tsx

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlSocial.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlSocialElement.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mjml/MjmlTable.tsx

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)