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; +} + + + .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 { + + .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} - + ) : ( {children} ); +}; Paragraph.displayName = 'Paragraph'; export const P = Paragraph;