- Lorem ipsum
+ ,
);
- expect(actualOutput).toContain('');
+
+ // Should contain the wrapper table with background-color and padding
+ expect(actualOutput).toContain('background-color:black');
+ expect(actualOutput).toContain('padding:1');
+ expect(actualOutput).toContain('border-radius:8px');
+ expect(actualOutput).toContain(' Test content ');
+ expect(actualOutput).not.toContain('border:1px solid black');
});
- it('renders with | wrapper if is provided', async () => {
+ it('should not use BorderWrapper when only border is present', async () => {
const actualOutput = await render(
-
- | Lorem ipsum |
+ ,
);
- expect(actualOutput).toContain('');
+
+ // Should render normally without wrapper
+ expect(actualOutput).toContain('border:1px solid black');
+ expect(actualOutput).not.toContain('background-color:black');
+ expect(actualOutput).not.toContain('padding:1');
});
- it('renders wrapping any child provided in a | tag', async () => {
+ it('should not use BorderWrapper when only borderRadius is present', async () => {
const actualOutput = await render(
-
- Lorem ipsum
- Lorem ipsum
-
+ ,
);
- const tdChildrenArr = actualOutput.match(/.*?<\/td>/g);
- expect(tdChildrenArr).toHaveLength(1);
+
+ // Should render normally without wrapper
+ expect(actualOutput).toContain('border-radius:8px');
+ expect(actualOutput).not.toContain('background-color:');
+ expect(actualOutput).not.toContain('padding:');
+ });
+
+ it('should handle individual border properties with borderRadius', async () => {
+ const actualOutput = await render(
+ ,
+ );
+
+ expect(actualOutput).toContain('background-color:red');
+ expect(actualOutput).toContain('padding:2');
+ expect(actualOutput).toContain('border-radius:4px');
+ expect(actualOutput).toContain('color:blue');
});
});
diff --git a/packages/section/src/section.tsx b/packages/section/src/section.tsx
index 20c494e2a8..2742c40dc6 100644
--- a/packages/section/src/section.tsx
+++ b/packages/section/src/section.tsx
@@ -1,9 +1,23 @@
import * as React from 'react';
+import {
+ BorderWrapper,
+ hasBorderAndBorderRadius,
+} from './utils/border-wrapper.js';
export type SectionProps = Readonly>;
export const Section = React.forwardRef(
({ children, style, ...props }, ref) => {
+ // Check if we need to use the border wrapper for compatibility
+ if (hasBorderAndBorderRadius(style)) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ // Default rendering without border wrapper
return (
{
+ describe('hasBorderAndBorderRadius', () => {
+ it('should return false when no style is provided', () => {
+ expect(hasBorderAndBorderRadius()).toBe(false);
+ });
+
+ it('should return false when only border is provided', () => {
+ expect(hasBorderAndBorderRadius({ border: '1px solid black' })).toBe(
+ false,
+ );
+ });
+
+ it('should return false when only borderRadius is provided', () => {
+ expect(hasBorderAndBorderRadius({ borderRadius: '8px' })).toBe(false);
+ });
+
+ it('should return true when both border and borderRadius are provided', () => {
+ expect(
+ hasBorderAndBorderRadius({
+ border: '1px solid black',
+ borderRadius: '8px',
+ }),
+ ).toBe(true);
+ });
+
+ it('should detect individual border properties', () => {
+ expect(
+ hasBorderAndBorderRadius({
+ borderWidth: '2px',
+ borderStyle: 'solid',
+ borderColor: 'red',
+ borderRadius: '4px',
+ }),
+ ).toBe(true);
+ });
+
+ it('should detect individual border radius properties', () => {
+ expect(
+ hasBorderAndBorderRadius({
+ border: '1px solid blue',
+ borderTopLeftRadius: '8px',
+ borderTopRightRadius: '8px',
+ }),
+ ).toBe(true);
+ });
+ });
+
+ describe('extractBorderProperties', () => {
+ it('should return null when no style is provided', () => {
+ expect(extractBorderProperties()).toBe(null);
+ });
+
+ it('should return null when no border properties are present', () => {
+ expect(extractBorderProperties({ color: 'red', fontSize: '16px' })).toBe(
+ null,
+ );
+ });
+
+ it('should extract border properties when present', () => {
+ const style = {
+ border: '2px solid red',
+ borderRadius: '8px',
+ color: 'blue',
+ };
+
+ const result = extractBorderProperties(style);
+ expect(result).toEqual({
+ border: '2px solid red',
+ borderTop: undefined,
+ borderRight: undefined,
+ borderBottom: undefined,
+ borderLeft: undefined,
+ borderWidth: undefined,
+ borderStyle: undefined,
+ borderColor: undefined,
+ borderRadius: '8px',
+ borderTopLeftRadius: undefined,
+ borderTopRightRadius: undefined,
+ borderBottomLeftRadius: undefined,
+ borderBottomRightRadius: undefined,
+ });
+ });
+ });
+
+ describe('BorderWrapper component', () => {
+ it('should render children directly when no border properties are present', async () => {
+ const result = await render(
+
+ Test content
+ ,
+ );
+
+ expect(result).toContain('Test content ');
+ expect(result).not.toContain(' {
+ const result = await render(
+
+ Test content
+ ,
+ );
+
+ expect(result).toContain('Test content');
+ });
+
+ it('should handle individual border properties', async () => {
+ const result = await render(
+
+ Test content
+ ,
+ );
+
+ expect(result).toContain('background-color:red');
+ expect(result).toContain('padding:2');
+ expect(result).toContain('border-radius:4px');
+ });
+
+ it('should preserve non-border styles on inner element', async () => {
+ const result = await render(
+
+ Test content
+ ,
+ );
+
+ expect(result).toContain('color:red');
+ expect(result).toContain('font-size:16px');
+ expect(result).toContain('background-color:white');
+ expect(result).not.toContain('border:1px solid black');
+ });
+ });
+});
diff --git a/packages/section/src/utils/border-wrapper.tsx b/packages/section/src/utils/border-wrapper.tsx
new file mode 100644
index 0000000000..1123816be2
--- /dev/null
+++ b/packages/section/src/utils/border-wrapper.tsx
@@ -0,0 +1,149 @@
+import type * as React from 'react';
+
+interface BorderWrapperProps {
+ children: React.ReactNode;
+ style?: React.CSSProperties;
+ [key: string]: any;
+}
+
+interface BorderProperties {
+ border?: React.CSSProperties['border'];
+ borderTop?: React.CSSProperties['borderTop'];
+ borderRight?: React.CSSProperties['borderRight'];
+ borderBottom?: React.CSSProperties['borderBottom'];
+ borderLeft?: React.CSSProperties['borderLeft'];
+ borderWidth?: React.CSSProperties['borderWidth'];
+ borderStyle?: React.CSSProperties['borderStyle'];
+ borderColor?: React.CSSProperties['borderColor'];
+ borderRadius?: React.CSSProperties['borderRadius'];
+ borderTopLeftRadius?: React.CSSProperties['borderTopLeftRadius'];
+ borderTopRightRadius?: React.CSSProperties['borderTopRightRadius'];
+ borderBottomLeftRadius?: React.CSSProperties['borderBottomLeftRadius'];
+ borderBottomRightRadius?: React.CSSProperties['borderBottomRightRadius'];
+}
+
+/**
+ * Detects if both border and borderRadius are present in the style object
+ */
+export const hasBorderAndBorderRadius = (
+ style?: React.CSSProperties,
+): boolean => {
+ if (!style) return false;
+
+ const hasBorder =
+ style.border ||
+ style.borderTop ||
+ style.borderRight ||
+ style.borderBottom ||
+ style.borderLeft ||
+ style.borderWidth ||
+ style.borderStyle ||
+ style.borderColor;
+
+ const hasBorderRadius =
+ style.borderRadius ||
+ style.borderTopLeftRadius ||
+ style.borderTopRightRadius ||
+ style.borderBottomLeftRadius ||
+ style.borderBottomRightRadius;
+
+ return Boolean(hasBorder && hasBorderRadius);
+};
+
+/**
+ * Extracts border properties from style object
+ */
+export const extractBorderProperties = (
+ style?: React.CSSProperties,
+): BorderProperties | null => {
+ if (!style) return null;
+
+ const borderProps: BorderProperties = {
+ border: style.border,
+ borderTop: style.borderTop,
+ borderRight: style.borderRight,
+ borderBottom: style.borderBottom,
+ borderLeft: style.borderLeft,
+ borderWidth: style.borderWidth,
+ borderStyle: style.borderStyle,
+ borderColor: style.borderColor,
+ borderRadius: style.borderRadius,
+ borderTopLeftRadius: style.borderTopLeftRadius,
+ borderTopRightRadius: style.borderTopRightRadius,
+ borderBottomLeftRadius: style.borderBottomLeftRadius,
+ borderBottomRightRadius: style.borderBottomRightRadius,
+ };
+
+ // Check if any border properties exist
+ const hasBorderProps = Object.values(borderProps).some(Boolean);
+ return hasBorderProps ? borderProps : null;
+};
+
+/**
+ * Creates a wrapper table that simulates border with background color and padding
+ * This approach provides full border-radius support across all email clients
+ */
+export const BorderWrapper: React.FC = ({
+ children,
+ style,
+ ...props
+}) => {
+ const borderProps = extractBorderProperties(style);
+
+ if (!borderProps) {
+ // No border properties, render children directly
+ return <>{children}>;
+ }
+
+ // Extract border color and width for the wrapper
+ const borderColor =
+ borderProps.borderColor ||
+ (typeof borderProps.border === 'string' &&
+ borderProps.border.includes('solid')
+ ? borderProps.border.split('solid')[1]?.trim()
+ : undefined);
+
+ const borderWidth =
+ borderProps.borderWidth ||
+ (typeof borderProps.border === 'string'
+ ? Number.parseInt(borderProps.border.split('px')[0]) || 1
+ : 1);
+
+ // Create style without border properties for the inner element
+ const innerStyle = { ...style };
+ delete innerStyle.border;
+ delete innerStyle.borderTop;
+ delete innerStyle.borderRight;
+ delete innerStyle.borderBottom;
+ delete innerStyle.borderLeft;
+ delete innerStyle.borderWidth;
+ delete innerStyle.borderStyle;
+ delete innerStyle.borderColor;
+
+ return (
+
+ );
+};
| | |