diff --git a/src/typography/__tests__/__snapshots__/paragraph.test.tsx.snap b/src/typography/__tests__/__snapshots__/paragraph.test.tsx.snap
index eccbc420a3..9757d36103 100644
--- a/src/typography/__tests__/__snapshots__/paragraph.test.tsx.snap
+++ b/src/typography/__tests__/__snapshots__/paragraph.test.tsx.snap
@@ -1,5 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Paragraph does not render dropCap when first children is not plain text 1`] = `
+
+ .emotion-0 {
+ margin: 0;
+ color: #2E2E2E;
+ font-family: "DM Sans",sans-serif;
+ font-size: 16px;
+ line-height: 1.5;
+ font-weight: 400;
+ letter-spacing: 0;
+}
+
+.emotion-0 svg {
+ fill: #2E2E2E;
+}
+
+
+
+ paragraph
+
+ component
+
+
+`;
+
exports[`Paragraph renders Paragraph 1`] = `
.emotion-0 {
@@ -101,14 +128,85 @@ exports[`Paragraph renders empty Paragraph with dropCap without crashing 1`] =
exports[`Paragraph renders with drop cap 1`] = `
.emotion-0 {
- display: -webkit-inline-box;
- display: -webkit-inline-flex;
- display: -ms-inline-flexbox;
- display: inline-flex;
- max-width: 100%;
+ margin: 0;
+ color: #2E2E2E;
+ font-family: "DM Sans",sans-serif;
+ font-size: 16px;
+ line-height: 1.5;
+ font-weight: 400;
+ letter-spacing: 0;
+}
+
+.emotion-0 svg {
+ fill: #2E2E2E;
}
.emotion-1 {
+ margin: 0 0.15em 0 0;
+ float: left;
+ margin-top: 4px;
+ color: #0A0A0A;
+}
+
+@media screen and (max-width: 767px) {
+ .emotion-1 {
+ font-size: 5.625em;
+ line-height: 0.85;
+ }
+}
+
+@media screen and (min-width: 768px) and (max-width: 1023px) {
+ .emotion-1 {
+ font-size: 5.875em;
+ line-height: 0.85;
+ }
+}
+
+@media screen and (min-width: 1024px) {
+ .emotion-1 {
+ font-size: 6em;
+ line-height: 0.85;
+ }
+}
+
+.emotion-1 svg {
+ fill: #0A0A0A;
+}
+
+
+
+ p
+
+ aragraph component
+
+ .emotion-0 {
+ display: block;
+ clip: rect(0 0 0 0);
+ -webkit-clip-path: inset(100%);
+ clip-path: inset(100%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+}
+
+
+ paragraph component
+
+
+`;
+
+exports[`Paragraph renders with drop cap and complex children 1`] = `
+
+ .emotion-0 {
margin: 0;
color: #2E2E2E;
font-family: "DM Sans",sans-serif;
@@ -118,44 +216,63 @@ exports[`Paragraph renders with drop cap 1`] = `
letter-spacing: 0;
}
-.emotion-1 svg {
+.emotion-0 svg {
fill: #2E2E2E;
}
-.emotion-2 {
- margin: 0;
+.emotion-1 {
+ margin: 0 0.15em 0 0;
float: left;
- margin-right: 0.15em;
margin-top: 4px;
color: #0A0A0A;
}
@media screen and (max-width: 767px) {
- .emotion-2 {
+ .emotion-1 {
font-size: 5.625em;
line-height: 0.85;
}
}
@media screen and (min-width: 768px) and (max-width: 1023px) {
- .emotion-2 {
+ .emotion-1 {
font-size: 5.875em;
line-height: 0.85;
}
}
@media screen and (min-width: 1024px) {
- .emotion-2 {
+ .emotion-1 {
font-size: 6em;
line-height: 0.85;
}
}
-.emotion-2 svg {
+.emotion-1 svg {
fill: #0A0A0A;
}
-.emotion-3 {
+
+
+ p
+
+ aragraph
+
+ component
+
+ with
+
+ link
+
+
+ .emotion-0 {
display: block;
clip: rect(0 0 0 0);
-webkit-clip-path: inset(100%);
@@ -167,26 +284,20 @@ exports[`Paragraph renders with drop cap 1`] = `
width: 1px;
}
-
+ link
+
+
`;
diff --git a/src/typography/__tests__/paragraph.stories.tsx b/src/typography/__tests__/paragraph.stories.tsx
index 64c380d821..e9a5fb42a8 100644
--- a/src/typography/__tests__/paragraph.stories.tsx
+++ b/src/typography/__tests__/paragraph.stories.tsx
@@ -48,6 +48,18 @@ export const StoryParagraph = () => (
Week.
+
+ with drop cap and multiply children
+
+
+ This being Black History Month, last week was Politicians
+ In Search Of An Eye-Catching Race-Related Policy Week. Both Theresa May
+ and Jeremy Corbyn had their own announcement, each
+ seemingly benign and right-on, each actually destructive and wrong-headed.
+ This being Black History Month, last week was
+ Politicians In Search Of An Eye-Catching Race-Related Policy Week.
+
+
with Sup and Sub elements
Paragraph component containing a subscript element and a{' '}
diff --git a/src/typography/__tests__/paragraph.test.tsx b/src/typography/__tests__/paragraph.test.tsx
index 6e4ebfa932..3d15d03c54 100644
--- a/src/typography/__tests__/paragraph.test.tsx
+++ b/src/typography/__tests__/paragraph.test.tsx
@@ -1,3 +1,4 @@
+import React from 'react';
import {
Paragraph,
P,
@@ -56,6 +57,30 @@ describe('Paragraph', () => {
expect(wrapper).toMatchSnapshot();
});
+ test('renders with drop cap and complex children', () => {
+ const wrapper = renderToFragmentWithTheme(Paragraph, {
+ children: (
+ <>
+ paragraph component with link
+ >
+ ),
+ dropCap: true,
+ } as ParagraphProps);
+ expect(wrapper).toMatchSnapshot();
+ });
+
+ test('does not render dropCap when first children is not plain text', () => {
+ const wrapper = renderToFragmentWithTheme(Paragraph, {
+ children: (
+ <>
+ paragraph component
+ >
+ ),
+ dropCap: true,
+ } as ParagraphProps);
+ expect(wrapper).toMatchSnapshot();
+ });
+
test('P alias for Paragraph', () => {
expect(P).toEqual(Paragraph);
});
diff --git a/src/typography/paragraph/paragraph.tsx b/src/typography/paragraph/paragraph.tsx
index f0583486d5..f57b92b373 100644
--- a/src/typography/paragraph/paragraph.tsx
+++ b/src/typography/paragraph/paragraph.tsx
@@ -1,10 +1,11 @@
import React from 'react';
+import {isFragment} from 'react-is';
import {
styled,
getTypographyPreset,
MQ,
- getSpace,
getStylePreset,
+ getResponsiveSpace,
} from '../../utils/style';
import defaults from './defaults';
import {withOwnTheme} from '../../utils/with-own-theme';
@@ -12,8 +13,7 @@ import {ScreenReaderOnly} from '../../screen-reader-only';
import {logicalProps, LogicalProps} from '../../utils/logical-properties';
export interface ParagraphProps {
- // eslint-disable-next-line
- children: any;
+ children: React.ReactNode;
dropCap?: boolean;
overrides?: {
stylePreset?: MQ;
@@ -36,12 +36,14 @@ export const ParagraphText = withOwnTheme(ThemelessParagraphText)({
defaults,
});
+/*
+We use this solution instead of css :first-letter since there is Firefox inconsistences,
+which causes the first letter to has less margin than desired.
+*/
const ThemelessParagraphDropCap = styled.span`
- margin: 0;
+ margin: 0 0.15em 0 0;
float: left;
- margin-right: 0.15em;
-
- margin-top: ${getSpace('paragraph.dropCap', 'dropCap')};
+ ${getResponsiveSpace('marginTop', 'paragraph.dropCap', 'dropCap', 'space')};
${getTypographyPreset('paragraph.dropCap', 'dropCap')};
${getStylePreset('paragraph.dropCap', 'dropCap')};
`;
@@ -49,28 +51,58 @@ export const ParagraphDropCap = withOwnTheme(ThemelessParagraphDropCap)({
defaults,
});
-const ParagraphContainer = styled.div`
- display: inline-flex;
- max-width: 100%;
-`;
+const getFirstLetter = (
+ children: (React.ReactChild | React.ReactFragment | React.ReactPortal)[],
+): string => {
+ const [firstChild] = children;
+ if (typeof firstChild === 'string') {
+ return firstChild.charAt(0);
+ }
+ if (isFragment(firstChild)) {
+ return getFirstLetter(React.Children.toArray(firstChild.props.children));
+ }
+ return '';
+};
+
+const removeFirstLetter = (
+ children: (React.ReactChild | React.ReactFragment | React.ReactPortal)[],
+): (React.ReactChild | React.ReactFragment | React.ReactPortal)[] => {
+ const [firstChild, ...rest] = children;
+ if (typeof firstChild === 'string') {
+ return [firstChild.substring(1), ...rest];
+ }
+ if (isFragment(firstChild)) {
+ return [
+ removeFirstLetter(React.Children.toArray(firstChild.props.children)),
+ ...rest,
+ ];
+ }
+
+ /* istanbul ignore next */
+ return children;
+};
export const Paragraph: React.FC = ({
children,
overrides = {},
dropCap = false,
-}) =>
- dropCap && children ? (
-
+}) => {
+ const childrenAsArray = React.Children.toArray(children);
+ const firstLetter = getFirstLetter(childrenAsArray);
+ const useDropCap = dropCap && firstLetter;
+
+ return useDropCap && children ? (
+ <>
- {children[0]}
- {children.slice(1)}
+ {firstLetter}
+ {removeFirstLetter(childrenAsArray)}
-
{children}
-
+ >
) : (
{children}
);
+};
Paragraph.displayName = 'Paragraph';
export const P = Paragraph;