Skip to content

Commit 9111816

Browse files
committed
feat(button): respect the user's order of padding properties (#2234)
1 parent 32a47f6 commit 9111816

File tree

9 files changed

+190
-124
lines changed

9 files changed

+190
-124
lines changed

apps/web/components/customer-reviews/tailwind.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ export const component = (
8787
</Text>
8888
<Button
8989
href="#"
90-
className="mt-[26px] mb-[24px] inline-block w-full rounded-[8px] bg-indigo-600 p-3 text-center box-border font-semibold text-white"
90+
className="mt-[26px] mb-[24px] inline-block w-full rounded-[8px] bg-indigo-600 p-[12px] text-center box-border font-semibold text-white"
9191
>
9292
Write a review
9393
</Button>
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
22

3-
exports[`<Button> component > renders correctly with padding values from style prop 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a href="https://example.com" style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>&#8202;&#8202;</i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px"></span><span><!--[if mso]><i style="mso-font-width:500%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span></a><!--/$-->"`;
3+
exports[`<Button> component > renders correctly with padding values from style prop 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a href="https://example.com" style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px;padding-top:12px;padding-right:20px;padding-bottom:12px;padding-left:20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>&#8202;&#8202;</i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px"></span><span><!--[if mso]><i style="mso-font-width:500%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span></a><!--/$-->"`;
44
5-
exports[`<Button> component > renders the <Button> component with no padding value 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a href="https://example.com" style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:0px 0px 0px 0px" target="_blank"><span><!--[if mso]><i style="mso-font-width:0%;mso-text-raise:0" hidden></i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:0"></span><span><!--[if mso]><i style="mso-font-width:0%" hidden>&#8203;</i><![endif]--></span></a><!--/$-->"`;
5+
exports[`<Button> component > renders the <Button> component with no padding value 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a href="https://example.com" style="line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px" target="_blank"><span><!--[if mso]><i style="mso-font-width:0%;mso-text-raise:0" hidden></i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px"></span><span><!--[if mso]><i style="mso-font-width:0%" hidden>&#8203;</i><![endif]--></span></a><!--/$-->"`;
66
7-
exports[`<Button> component > should allow users to overwrite style props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a style="line-height:150%;text-decoration:underline red;display:block;max-width:50%;mso-padding-alt:0px;padding:0px 0px 0px 0px" target="_blank"><span><!--[if mso]><i style="mso-font-width:0%;mso-text-raise:0" hidden></i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:0"></span><span><!--[if mso]><i style="mso-font-width:0%" hidden>&#8203;</i><![endif]--></span></a><!--/$-->"`;
7+
exports[`<Button> component > should allow users to overwrite style props 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a style="line-height:150%;text-decoration:underline red;display:block;max-width:50%;mso-padding-alt:0px" target="_blank"><span><!--[if mso]><i style="mso-font-width:0%;mso-text-raise:0" hidden></i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px"></span><span><!--[if mso]><i style="mso-font-width:0%" hidden>&#8203;</i><![endif]--></span></a><!--/$-->"`;

packages/button/src/button.tsx

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const maxFontWidth = 5;
1111
* when applied, end up being as close to `expectedWidth` as possible.
1212
*/
1313
function computeFontWidthAndSpaceCount(expectedWidth: number) {
14-
if (expectedWidth === 0) return [0, 0];
14+
if (expectedWidth === 0) return [0, 0] as const;
1515

1616
let smallestSpaceCount = 0;
1717

@@ -27,30 +27,47 @@ function computeFontWidthAndSpaceCount(expectedWidth: number) {
2727
smallestSpaceCount++;
2828
}
2929

30-
return [computeRequiredFontWidth(), smallestSpaceCount];
30+
return [computeRequiredFontWidth(), smallestSpaceCount] as const;
31+
}
32+
33+
declare module 'react' {
34+
interface CSSProperties {
35+
msoPaddingAlt?: string | number | undefined;
36+
msoTextRaise?: string | number | undefined;
37+
}
3138
}
3239

3340
export const Button = React.forwardRef<HTMLAnchorElement, ButtonProps>(
3441
({ children, style, target = '_blank', ...props }, ref) => {
35-
const { pt, pr, pb, pl } = parsePadding({
36-
padding: style?.padding,
37-
paddingLeft: style?.paddingLeft ?? style?.paddingInline,
38-
paddingRight: style?.paddingRight ?? style?.paddingInline,
39-
paddingTop: style?.paddingTop ?? style?.paddingBlock,
40-
paddingBottom: style?.paddingBottom ?? style?.paddingBlock,
41-
});
42+
const { paddingTop, paddingRight, paddingBottom, paddingLeft } =
43+
parsePadding(style ?? {});
4244

43-
const y = pt + pb;
45+
const y = (paddingTop ?? 0) + (paddingBottom ?? 0);
4446
const textRaise = pxToPt(y);
4547

46-
const [plFontWidth, plSpaceCount] = computeFontWidthAndSpaceCount(pl);
47-
const [prFontWidth, prSpaceCount] = computeFontWidthAndSpaceCount(pr);
48+
const [plFontWidth, plSpaceCount] = computeFontWidthAndSpaceCount(
49+
paddingLeft ?? 0,
50+
);
51+
const [prFontWidth, prSpaceCount] = computeFontWidthAndSpaceCount(
52+
paddingRight ?? 0,
53+
);
4854

4955
return (
5056
<a
5157
{...props}
5258
ref={ref}
53-
style={buttonStyle({ ...style, pt, pr, pb, pl })}
59+
style={{
60+
lineHeight: '100%',
61+
textDecoration: 'none',
62+
display: 'inline-block',
63+
maxWidth: '100%',
64+
msoPaddingAlt: '0px',
65+
...style,
66+
paddingTop,
67+
paddingRight,
68+
paddingBottom,
69+
paddingLeft,
70+
}}
5471
target={target}
5572
>
5673
<span
@@ -67,7 +84,17 @@ export const Button = React.forwardRef<HTMLAnchorElement, ButtonProps>(
6784
)}</i><![endif]-->`,
6885
}}
6986
/>
70-
<span style={buttonTextStyle(pb)}>{children}</span>
87+
<span
88+
style={{
89+
maxWidth: '100%',
90+
display: 'inline-block',
91+
lineHeight: '120%',
92+
msoPaddingAlt: '0px',
93+
msoTextRaise: pxToPt(paddingBottom),
94+
}}
95+
>
96+
{children}
97+
</span>
7198
<span
7299
dangerouslySetInnerHTML={{
73100
__html: `<!--[if mso]><i style="mso-font-width:${
@@ -83,34 +110,3 @@ export const Button = React.forwardRef<HTMLAnchorElement, ButtonProps>(
83110
);
84111

85112
Button.displayName = 'Button';
86-
87-
const buttonStyle = (
88-
style?: React.CSSProperties & {
89-
pt: number;
90-
pr: number;
91-
pb: number;
92-
pl: number;
93-
},
94-
) => {
95-
const { pt, pr, pb, pl, ...rest } = style || {};
96-
97-
return {
98-
lineHeight: '100%',
99-
textDecoration: 'none',
100-
display: 'inline-block',
101-
maxWidth: '100%',
102-
msoPaddingAlt: '0px',
103-
...rest,
104-
padding: `${pt}px ${pr}px ${pb}px ${pl}px`,
105-
};
106-
};
107-
108-
const buttonTextStyle = (pb?: number) => {
109-
return {
110-
maxWidth: '100%',
111-
display: 'inline-block',
112-
lineHeight: '120%',
113-
msoPaddingAlt: '0px',
114-
msoTextRaise: pxToPt(pb || 0),
115-
};
116-
};
Lines changed: 80 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
type PaddingType = string | number | undefined;
22

33
interface PaddingProperties {
4-
padding: PaddingType;
4+
padding?: PaddingType;
55
paddingTop?: PaddingType;
66
paddingRight?: PaddingType;
77
paddingBottom?: PaddingType;
@@ -46,64 +46,92 @@ export function convertToPx(value: PaddingType) {
4646
return 0;
4747
}
4848

49+
function parsePaddingValue(value: PaddingType) {
50+
if (typeof value === 'number')
51+
return {
52+
paddingTop: value,
53+
paddingBottom: value,
54+
paddingLeft: value,
55+
paddingRight: value,
56+
};
57+
58+
if (typeof value === 'string') {
59+
const values = value.toString().trim().split(/\s+/);
60+
61+
if (values.length === 1) {
62+
return {
63+
paddingTop: values[0],
64+
paddingBottom: values[0],
65+
paddingLeft: values[0],
66+
paddingRight: values[0],
67+
};
68+
}
69+
70+
if (values.length === 2) {
71+
return {
72+
paddingTop: values[0],
73+
paddingRight: values[1],
74+
paddingBottom: values[0],
75+
paddingLeft: values[1],
76+
};
77+
}
78+
79+
if (values.length === 3) {
80+
return {
81+
paddingTop: values[0],
82+
paddingRight: values[1],
83+
paddingBottom: values[2],
84+
paddingLeft: values[1],
85+
};
86+
}
87+
88+
if (values.length === 4) {
89+
return {
90+
paddingTop: values[0],
91+
paddingRight: values[1],
92+
paddingBottom: values[2],
93+
paddingLeft: values[3],
94+
};
95+
}
96+
}
97+
98+
return {
99+
paddingTop: undefined,
100+
paddingBottom: undefined,
101+
paddingLeft: undefined,
102+
paddingRight: undefined,
103+
};
104+
}
105+
49106
/**
50107
* Parses all the values out of a padding string to get the value for all padding props in `px`
51108
* @example e.g. "10px" =\> pt: 10, pr: 10, pb: 10, pl: 10
52109
*/
53-
export function parsePadding({
54-
padding = '',
55-
paddingTop,
56-
paddingRight,
57-
paddingBottom,
58-
paddingLeft,
59-
}: PaddingProperties) {
60-
let pt = 0;
61-
let pr = 0;
62-
let pb = 0;
63-
let pl = 0;
64-
65-
if (typeof padding === 'number') {
66-
pt = padding;
67-
pr = padding;
68-
pb = padding;
69-
pl = padding;
70-
} else {
71-
const values = padding.split(/\s+/);
72-
73-
switch (values.length) {
74-
case 1:
75-
pt = convertToPx(values[0]);
76-
pr = convertToPx(values[0]);
77-
pb = convertToPx(values[0]);
78-
pl = convertToPx(values[0]);
79-
break;
80-
case 2:
81-
pt = convertToPx(values[0]);
82-
pb = convertToPx(values[0]);
83-
pr = convertToPx(values[1]);
84-
pl = convertToPx(values[1]);
85-
break;
86-
case 3:
87-
pt = convertToPx(values[0]);
88-
pr = convertToPx(values[1]);
89-
pl = convertToPx(values[1]);
90-
pb = convertToPx(values[2]);
91-
break;
92-
case 4:
93-
pt = convertToPx(values[0]);
94-
pr = convertToPx(values[1]);
95-
pb = convertToPx(values[2]);
96-
pl = convertToPx(values[3]);
97-
break;
98-
default:
99-
break;
110+
export function parsePadding(properties: PaddingProperties) {
111+
let paddingTop: string | number | undefined;
112+
let paddingRight: string | number | undefined;
113+
let paddingBottom: string | number | undefined;
114+
let paddingLeft: string | number | undefined;
115+
116+
for (const [key, value] of Object.entries(properties)) {
117+
if (key === 'padding') {
118+
({ paddingTop, paddingBottom, paddingLeft, paddingRight } =
119+
parsePaddingValue(value));
120+
} else if (key === 'paddingTop') {
121+
paddingTop = value;
122+
} else if (key === 'paddingRight') {
123+
paddingRight = value;
124+
} else if (key === 'paddingBottom') {
125+
paddingBottom = value;
126+
} else if (key === 'paddingLeft') {
127+
paddingLeft = value;
100128
}
101129
}
102130

103131
return {
104-
pt: paddingTop ? convertToPx(paddingTop) : pt,
105-
pr: paddingRight ? convertToPx(paddingRight) : pr,
106-
pb: paddingBottom ? convertToPx(paddingBottom) : pb,
107-
pl: paddingLeft ? convertToPx(paddingLeft) : pl,
132+
paddingTop: paddingTop ? convertToPx(paddingTop) : undefined,
133+
paddingRight: paddingRight ? convertToPx(paddingRight) : undefined,
134+
paddingBottom: paddingBottom ? convertToPx(paddingBottom) : undefined,
135+
paddingLeft: paddingLeft ? convertToPx(paddingLeft) : undefined,
108136
};
109137
}

packages/button/src/utils/px-to-pt.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1-
export const pxToPt = (px: number): number | null =>
2-
typeof px === 'number' && !Number.isNaN(Number(px)) ? (px * 3) / 4 : null;
1+
export const pxToPt = (px: number | undefined): number | undefined =>
2+
typeof px === 'number' && !Number.isNaN(Number(px))
3+
? (px * 3) / 4
4+
: undefined;

0 commit comments

Comments
 (0)