@financial-times/cp-content-pipeline-ui 6.15.6 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/lib/components/BigNumber/index.d.ts +4 -1
  3. package/lib/components/BigNumber/index.js +1 -1
  4. package/lib/components/BigNumber/index.js.map +1 -1
  5. package/lib/components/Body/index.test.js +12 -1
  6. package/lib/components/Body/index.test.js.map +1 -1
  7. package/lib/components/Byline/index.js +2 -2
  8. package/lib/components/Byline/index.js.map +1 -1
  9. package/lib/components/Clip/components/ClipTag.d.ts +3 -3
  10. package/lib/components/Clip/components/ClipTag.js.map +1 -1
  11. package/lib/components/Clip/components/ClosedCaptions.d.ts +2 -2
  12. package/lib/components/Clip/components/VideoDescription.d.ts +2 -2
  13. package/lib/components/Clip/components/VideoDescription.js.map +1 -1
  14. package/lib/components/Clip/template/component.d.ts +9 -4
  15. package/lib/components/Clip/template/component.js +19 -14
  16. package/lib/components/Clip/template/component.js.map +1 -1
  17. package/lib/components/Clip/test/fixtures.js +114 -77
  18. package/lib/components/Clip/test/fixtures.js.map +1 -1
  19. package/lib/components/Clip/test/snapshot.spec.js +110 -61
  20. package/lib/components/Clip/test/snapshot.spec.js.map +1 -1
  21. package/lib/components/Flourish/index.d.ts +4 -12
  22. package/lib/components/Flourish/index.js +1 -1
  23. package/lib/components/Flourish/index.js.map +1 -1
  24. package/lib/components/Flourish/test/snapshot.spec.js +48 -6
  25. package/lib/components/Flourish/test/snapshot.spec.js.map +1 -1
  26. package/lib/components/Heading/index.d.ts +2 -1
  27. package/lib/components/Heading/index.js +2 -2
  28. package/lib/components/Heading/index.js.map +1 -1
  29. package/lib/components/ImageOverrideWrappers/index.d.ts +4 -0
  30. package/lib/components/ImageOverrideWrappers/index.js +44 -0
  31. package/lib/components/ImageOverrideWrappers/index.js.map +1 -0
  32. package/lib/components/ImageSet/index.d.ts +12 -8
  33. package/lib/components/ImageSet/index.js +13 -8
  34. package/lib/components/ImageSet/index.js.map +1 -1
  35. package/lib/components/Layout/index.d.ts +3 -13
  36. package/lib/components/Layout/index.js +1 -1
  37. package/lib/components/Layout/index.js.map +1 -1
  38. package/lib/components/MainImage/index.js +2 -2
  39. package/lib/components/MainImage/index.js.map +1 -1
  40. package/lib/components/Paragraph/index.d.ts +4 -1
  41. package/lib/components/Paragraph/index.js.map +1 -1
  42. package/lib/components/Pullquote/index.d.ts +4 -1
  43. package/lib/components/Pullquote/index.js +7 -6
  44. package/lib/components/Pullquote/index.js.map +1 -1
  45. package/lib/components/Recommended/index.d.ts +5 -8
  46. package/lib/components/Recommended/index.js +1 -6
  47. package/lib/components/Recommended/index.js.map +1 -1
  48. package/lib/components/RichText/BasicComponents.d.ts +12 -11
  49. package/lib/components/RichText/BasicComponents.js +2 -2
  50. package/lib/components/RichText/BasicComponents.js.map +1 -1
  51. package/lib/components/RichText/index.d.ts +10 -3
  52. package/lib/components/RichText/index.js +8 -2
  53. package/lib/components/RichText/index.js.map +1 -1
  54. package/lib/components/Scrollytelling/ScrollyImage.d.ts +6 -3
  55. package/lib/components/Scrollytelling/ScrollyImage.js +2 -2
  56. package/lib/components/Scrollytelling/ScrollyImage.js.map +1 -1
  57. package/lib/components/Scrollytelling/index.d.ts +14 -6
  58. package/lib/components/Scrollytelling/index.js +3 -3
  59. package/lib/components/Scrollytelling/index.js.map +1 -1
  60. package/lib/components/Table/TableBody.d.ts +4 -1
  61. package/lib/components/Table/TableBody.js +1 -1
  62. package/lib/components/Table/TableBody.js.map +1 -1
  63. package/lib/components/Table/TableCell.d.ts +4 -4
  64. package/lib/components/Table/TableCell.js +2 -2
  65. package/lib/components/Table/TableCell.js.map +1 -1
  66. package/lib/components/Table/index.d.ts +4 -1
  67. package/lib/components/Table/index.js +1 -1
  68. package/lib/components/Table/index.js.map +1 -1
  69. package/lib/components/Topper/Picture.d.ts +1 -1
  70. package/lib/components/Topper/Picture.js +6 -3
  71. package/lib/components/Topper/Picture.js.map +1 -1
  72. package/lib/components/Topper/index.js +7 -1
  73. package/lib/components/Topper/index.js.map +1 -1
  74. package/lib/components/Tweet/index.d.ts +4 -2
  75. package/lib/components/Tweet/index.js +1 -1
  76. package/lib/components/Tweet/index.js.map +1 -1
  77. package/lib/components/Video/index.d.ts +4 -2
  78. package/lib/components/Video/index.js +1 -1
  79. package/lib/components/Video/index.js.map +1 -1
  80. package/lib/components/Workarounds.d.ts +88 -0
  81. package/lib/components/Workarounds.js +3 -0
  82. package/lib/components/Workarounds.js.map +1 -0
  83. package/lib/components/YoutubeVideo/index.d.ts +5 -2
  84. package/lib/components/YoutubeVideo/index.js +1 -1
  85. package/lib/components/YoutubeVideo/index.js.map +1 -1
  86. package/lib/components/types.d.ts +5 -0
  87. package/lib/components/types.js +3 -0
  88. package/lib/components/types.js.map +1 -0
  89. package/lib/context.d.ts +14 -0
  90. package/lib/context.js +2 -1
  91. package/lib/context.js.map +1 -1
  92. package/lib/index.d.ts +1 -1
  93. package/lib/index.js +2 -1
  94. package/lib/index.js.map +1 -1
  95. package/lib/stories/Clip.stories.d.ts +39 -243
  96. package/lib/stories/Clip.stories.js +320 -307
  97. package/lib/stories/Clip.stories.js.map +1 -1
  98. package/package.json +2 -1
  99. package/src/components/BigNumber/index.tsx +6 -3
  100. package/src/components/Body/__snapshots__/index.test.tsx.snap +2 -1
  101. package/src/components/Body/index.test.tsx +12 -1
  102. package/src/components/Byline/index.tsx +9 -4
  103. package/src/components/Clip/components/ClipTag.tsx +3 -6
  104. package/src/components/Clip/components/ClosedCaptions.tsx +2 -2
  105. package/src/components/Clip/components/VideoDescription.tsx +2 -2
  106. package/src/components/Clip/template/component.tsx +39 -41
  107. package/src/components/Clip/test/__snapshots__/snapshot.spec.tsx.snap +4 -8
  108. package/src/components/Clip/test/fixtures.ts +127 -90
  109. package/src/components/Clip/test/snapshot.spec.tsx +123 -102
  110. package/src/components/Flourish/index.tsx +10 -21
  111. package/src/components/Flourish/test/snapshot.spec.tsx +48 -36
  112. package/src/components/Heading/index.tsx +6 -5
  113. package/src/components/ImageOverrideWrappers/index.tsx +34 -0
  114. package/src/components/ImageSet/index.tsx +75 -55
  115. package/src/components/Layout/index.tsx +6 -18
  116. package/src/components/MainImage/index.tsx +10 -8
  117. package/src/components/Paragraph/index.tsx +4 -1
  118. package/src/components/Pullquote/index.tsx +15 -8
  119. package/src/components/Recommended/index.tsx +11 -11
  120. package/src/components/RichText/BasicComponents.tsx +22 -20
  121. package/src/components/RichText/index.tsx +35 -12
  122. package/src/components/Scrollytelling/ScrollyImage.tsx +10 -7
  123. package/src/components/Scrollytelling/index.tsx +19 -9
  124. package/src/components/Table/TableBody.tsx +7 -3
  125. package/src/components/Table/TableCell.tsx +9 -7
  126. package/src/components/Table/index.tsx +12 -9
  127. package/src/components/Topper/Picture.tsx +17 -12
  128. package/src/components/Topper/index.tsx +7 -4
  129. package/src/components/Tweet/index.tsx +4 -2
  130. package/src/components/Video/index.tsx +4 -6
  131. package/src/components/Workarounds.ts +188 -0
  132. package/src/components/YoutubeVideo/index.tsx +5 -4
  133. package/src/components/types.ts +6 -0
  134. package/src/context.ts +22 -1
  135. package/src/index.ts +4 -1
  136. package/src/stories/Clip.stories.tsx +357 -341
  137. package/tsconfig.tsbuildinfo +1 -1
@@ -24,12 +24,14 @@ describe('Flourish component', () => {
24
24
  it('renders with a full-grid layout and fallback image', async () => {
25
25
  const { container } = render(
26
26
  <Flourish
27
- type="flourish"
28
- id="123"
29
- flourishType="bar"
30
- description="foo"
31
- layoutWidth="full-grid"
32
- fallbackImage={anImage}
27
+ content={{
28
+ type: 'flourish',
29
+ id: '123',
30
+ flourishType: 'bar',
31
+ description: 'foo',
32
+ layoutWidth: 'full-grid',
33
+ fallbackImage: anImage,
34
+ }}
33
35
  />
34
36
  )
35
37
  expect(container).toMatchSnapshot()
@@ -38,12 +40,14 @@ describe('Flourish component', () => {
38
40
  it('renders ignoring layout different from full-grid', async () => {
39
41
  const { container } = render(
40
42
  <Flourish
41
- type="flourish"
42
- id="123"
43
- flourishType="bar"
44
- description="foo"
45
- layoutWidth="whatever"
46
- fallbackImage={anImage}
43
+ content={{
44
+ type: 'flourish',
45
+ id: '123',
46
+ flourishType: 'bar',
47
+ description: 'foo',
48
+ layoutWidth: 'whatever',
49
+ fallbackImage: anImage,
50
+ }}
47
51
  />
48
52
  )
49
53
  expect(container).toMatchSnapshot()
@@ -53,12 +57,14 @@ describe('Flourish component', () => {
53
57
  const iFrame = true
54
58
  const { container } = render(
55
59
  <Flourish
56
- type="flourish"
57
- id="123"
58
- flourishType="bar"
59
- description="foo"
60
- layoutWidth="full-grid"
61
- fallbackImage={anImage}
60
+ content={{
61
+ type: 'flourish',
62
+ id: '123',
63
+ flourishType: 'bar',
64
+ description: 'foo',
65
+ layoutWidth: 'full-grid',
66
+ fallbackImage: anImage,
67
+ }}
62
68
  iFrame={iFrame}
63
69
  />
64
70
  )
@@ -69,12 +75,14 @@ describe('Flourish component', () => {
69
75
  const inArticleBody = false
70
76
  const { container } = render(
71
77
  <Flourish
72
- type="flourish"
73
- id="123"
74
- flourishType="bar"
75
- description="foo"
76
- layoutWidth="full-grid"
77
- fallbackImage={anImage}
78
+ content={{
79
+ type: 'flourish',
80
+ id: '123',
81
+ flourishType: 'bar',
82
+ description: 'foo',
83
+ layoutWidth: 'full-grid',
84
+ fallbackImage: anImage,
85
+ }}
78
86
  inArticleBody={inArticleBody}
79
87
  />
80
88
  )
@@ -87,12 +95,14 @@ it('sets the hideTitle param in the iframe url to true when not in article body'
87
95
  const iFrame = true
88
96
  const { container } = render(
89
97
  <Flourish
90
- type="flourish"
91
- id="123"
92
- flourishType="bar"
93
- description="foo"
94
- layoutWidth="full-grid"
95
- fallbackImage={anImage}
98
+ content={{
99
+ type: 'flourish',
100
+ id: '123',
101
+ flourishType: 'bar',
102
+ description: 'foo',
103
+ layoutWidth: 'full-grid',
104
+ fallbackImage: anImage,
105
+ }}
96
106
  iFrame={iFrame}
97
107
  inArticleBody={inArticleBody}
98
108
  />
@@ -103,12 +113,14 @@ it('sets the hideTitle param in the iframe url to true when not in article body'
103
113
  it('does not set aspect ratios when in article body', async () => {
104
114
  const { container } = render(
105
115
  <Flourish
106
- type="flourish"
107
- id="123"
108
- flourishType="bar"
109
- description="foo"
110
- layoutWidth="full-grid"
111
- fallbackImage={anImage}
116
+ content={{
117
+ type: 'flourish',
118
+ id: '123',
119
+ flourishType: 'bar',
120
+ description: 'foo',
121
+ layoutWidth: 'full-grid',
122
+ fallbackImage: anImage,
123
+ }}
112
124
  />
113
125
  )
114
126
  expect(container).toMatchSnapshot()
@@ -1,12 +1,13 @@
1
1
  import React from 'react'
2
2
  import type { ContentTree } from '@financial-times/content-tree'
3
+ import { ContentProps } from '../types'
3
4
 
4
- const Heading: React.FC<React.PropsWithChildren<ContentTree.Heading>> = (
5
- props
6
- ) => {
7
- if (!props.children.length) return null
5
+ const Heading: React.FC<
6
+ React.PropsWithChildren<ContentProps<ContentTree.Heading>>
7
+ > = (props) => {
8
+ if (!React.Children.count(props.children)) return null
8
9
 
9
- switch (props.level) {
10
+ switch (props.content.level) {
10
11
  case 'chapter':
11
12
  return <h2 className="n-content-heading-2">{props.children}</h2>
12
13
  case 'subheading':
@@ -0,0 +1,34 @@
1
+ import React, { useContext } from 'react'
2
+
3
+ import { PresentationOverridesContext } from '../../context'
4
+
5
+ import type { PresentationOverrideTypes } from '../../context'
6
+
7
+ export const FallbackImageOverrideWrapper: PresentationOverrideTypes['FallbackImage'] =
8
+ ({ children, ...props }) => {
9
+ const { FallbackImage } = useContext(PresentationOverridesContext)
10
+
11
+ return FallbackImage ? (
12
+ <FallbackImage {...props}>{children}</FallbackImage>
13
+ ) : (
14
+ children
15
+ )
16
+ }
17
+
18
+ export const ImageSetOverrideWrapper: PresentationOverrideTypes['ImageSet'] = ({
19
+ children,
20
+ ...props
21
+ }) => {
22
+ const { ImageSet } = useContext(PresentationOverridesContext)
23
+
24
+ return ImageSet ? <ImageSet {...props}>{children}</ImageSet> : children
25
+ }
26
+
27
+ export const PictureOverrideWrapper: PresentationOverrideTypes['Picture'] = ({
28
+ children,
29
+ ...props
30
+ }) => {
31
+ const { Picture } = useContext(PresentationOverridesContext)
32
+
33
+ return Picture ? <Picture {...props}>{children}</Picture> : children
34
+ }
@@ -1,24 +1,24 @@
1
1
  import React, { PropsWithChildren, useContext } from 'react'
2
+ import {
3
+ FallbackImageOverrideWrapper,
4
+ ImageSetOverrideWrapper,
5
+ } from '../ImageOverrideWrappers'
2
6
 
3
- import type {
4
- ImageFragment,
5
- ImageSetFragment,
6
- ImageSourceFragment,
7
- PictureFragment,
8
- } from '@financial-times/cp-content-pipeline-client'
9
7
  import type { ContentTree } from '@financial-times/content-tree'
10
8
 
11
9
  import { PresentationFlagsContext } from '../../context'
10
+ import { ContentProps } from '../types'
11
+ import { ContentTreeWorkarounds } from '@financial-times/cp-content-pipeline-schema'
12
+ import { SetRequired } from 'type-fest'
13
+ import type * as ComponentWorkarounds from '../Workarounds'
12
14
 
13
- type ImageWithSourceSet = ImageFragment & {
14
- sourceSet: ImageSourceFragment[]
15
- }
15
+ type ImageWithSourceSet = SetRequired<ContentTree.Image, 'sourceSet'>
16
16
 
17
- type ImageWithAliases = ImageFragment & {
17
+ type ImageWithAliases = ContentTree.Image & {
18
18
  [alias: string]: ImageWithSourceSet['sourceSet']
19
19
  }
20
20
 
21
- const formatSourceSet = (sourceSet: ImageSourceFragment[]) =>
21
+ const formatSourceSet = (sourceSet: ContentTree.ImageSource[]) =>
22
22
  sourceSet.map((src) => `${src.url} ${src.dpr}x`).join(',')
23
23
 
24
24
  const figureClassNameMap: Record<
@@ -42,7 +42,7 @@ export type AliasedBreakpoints = {
42
42
  }
43
43
 
44
44
  const defaultGetBreakpoints = (
45
- image: ImageFragment
45
+ image: ComponentWorkarounds.Image
46
46
  ): Breakpoint | Array<Breakpoint> | AliasedBreakpoints | undefined => {
47
47
  switch (image.format) {
48
48
  case 'mobile':
@@ -66,7 +66,9 @@ const breakpointToMedia = (breakpoint: Breakpoint): string =>
66
66
  .filter(Boolean)
67
67
  .join(' and ')
68
68
 
69
- function hasSourceSet(image: ImageFragment): image is ImageWithSourceSet {
69
+ function hasSourceSet(
70
+ image: ComponentWorkarounds.Image | ContentTreeWorkarounds.RawImage
71
+ ): image is ImageWithSourceSet {
70
72
  return image && 'sourceSet' in image
71
73
  }
72
74
 
@@ -80,7 +82,7 @@ function isAliasedBreakpoints(
80
82
  )
81
83
  }
82
84
 
83
- function figureWidth(picture: PictureFragment) {
85
+ function figureWidth(picture: ComponentWorkarounds.ImageSetPicture) {
84
86
  if (picture.layoutWidth === 'inset-left') {
85
87
  const DEFAULT_WIDTH = 700
86
88
  const width = picture.images[0]?.width ?? DEFAULT_WIDTH
@@ -97,7 +99,7 @@ function figureWidth(picture: PictureFragment) {
97
99
  }
98
100
 
99
101
  type SourceProps = {
100
- image: ImageFragment
102
+ image: ComponentWorkarounds.Image
101
103
  getBreakpoints?: BreakpointGetter
102
104
  }
103
105
 
@@ -141,7 +143,7 @@ const Source: React.FC<SourceProps> = ({
141
143
  }
142
144
 
143
145
  type SourcesProps = {
144
- images: readonly ImageFragment[]
146
+ images: readonly ComponentWorkarounds.Image[]
145
147
  getBreakpoints?: BreakpointGetter
146
148
  }
147
149
 
@@ -155,24 +157,34 @@ export const Sources: React.FC<SourcesProps> = ({ images, getBreakpoints }) => {
155
157
  )
156
158
  }
157
159
 
158
- type FallbackImageProps = {
159
- image: ImageFragment
160
+ // HACK:20240809:IM This props type is quite a mess as the FallbackImage
161
+ // component can be used both directly in a RichTextChild with a Content Tree
162
+ // node, but also from the Image component above which the Image type. Image is
163
+ // defined in Content Tree but is _not_ a node so this is a bit icky :(
164
+ export type FallbackImageProps = (
165
+ | ContentProps<ComponentWorkarounds.RawImage>
166
+ | {
167
+ content: { image: ComponentWorkarounds.Image }
168
+ }
169
+ ) & {
160
170
  imageType?: string
161
- picture?: PictureFragment
171
+ picture?: ComponentWorkarounds.ImageSetPicture
162
172
  className?: string
163
173
  inSourceSet?: boolean
164
174
  isMainImage?: boolean
165
175
  }
166
176
 
167
177
  // TODO should `fallback` be added to content-tree imageset?
168
- export const FallbackImage: React.FC<FallbackImageProps> = ({
169
- image,
170
- imageType = 'image',
171
- picture,
172
- className,
173
- inSourceSet = false,
174
- isMainImage = false,
175
- }) => {
178
+ export const FallbackImage: React.FC<FallbackImageProps> = (props) => {
179
+ const {
180
+ content: { image },
181
+ imageType = 'image',
182
+ picture,
183
+ className,
184
+ inSourceSet = false,
185
+ isMainImage = false,
186
+ } = props
187
+
176
188
  const Wrapper = inSourceSet
177
189
  ? React.Fragment
178
190
  : ({ children }: PropsWithChildren) => (
@@ -181,29 +193,35 @@ export const FallbackImage: React.FC<FallbackImageProps> = ({
181
193
 
182
194
  return (
183
195
  <Wrapper>
184
- <img
185
- src={
186
- hasSourceSet(image)
187
- ? image.sourceSet[0]?.url
188
- : image?.url ?? undefined
189
- }
190
- srcSet={
191
- hasSourceSet(image) ? formatSourceSet(image.sourceSet) : undefined
192
- }
193
- alt={picture?.alt ?? ''}
194
- data-image-type={imageType}
195
- width={image.width ?? undefined}
196
- height={image.height ?? undefined}
197
- className={className}
198
- loading={isMainImage ? 'eager' : 'lazy'}
199
- />
196
+ <FallbackImageOverrideWrapper {...props}>
197
+ <img
198
+ src={
199
+ hasSourceSet(image)
200
+ ? image.sourceSet[0]?.url
201
+ : image?.url ?? undefined
202
+ }
203
+ srcSet={
204
+ hasSourceSet(image) ? formatSourceSet(image.sourceSet) : undefined
205
+ }
206
+ alt={picture?.alt ?? ''}
207
+ data-image-type={imageType}
208
+ width={image.width ?? undefined}
209
+ height={image.height ?? undefined}
210
+ className={className}
211
+ loading={isMainImage ? 'eager' : 'lazy'}
212
+ />
213
+ </FallbackImageOverrideWrapper>
200
214
  </Wrapper>
201
215
  )
202
216
  }
203
217
 
204
- type ImageSetProps = ImageSetFragment & { isMainImage?: boolean }
218
+ export interface ImageSetProps extends ContentProps<ComponentWorkarounds.ImageSet> {
219
+ isMainImage?: boolean
220
+ }
221
+
222
+ const ImageSet: React.FC<ImageSetProps> = (props) => {
223
+ const { content: imageSet, isMainImage } = props
205
224
 
206
- const ImageSet: React.FC<ImageSetProps> = (imageSet) => {
207
225
  if (!imageSet.picture?.images) {
208
226
  return null
209
227
  }
@@ -232,16 +250,18 @@ const ImageSet: React.FC<ImageSetProps> = (imageSet) => {
232
250
  style={figureWidth(imageSet.picture)}
233
251
  data-component="image-set"
234
252
  >
235
- <picture>
236
- <Sources images={imageSet.picture.images} />
237
- <FallbackImage
238
- picture={imageSet.picture}
239
- image={imageSet.picture.fallbackImage}
240
- imageType={imageSet.picture.imageType || 'image'}
241
- inSourceSet={true}
242
- isMainImage={imageSet.isMainImage}
243
- />
244
- </picture>
253
+ <ImageSetOverrideWrapper {...props}>
254
+ <picture>
255
+ <Sources images={imageSet.picture.images} />
256
+ <FallbackImage
257
+ picture={imageSet.picture}
258
+ content={{ image: imageSet.picture.fallbackImage }}
259
+ imageType={imageSet.picture.imageType || 'image'}
260
+ inSourceSet={true}
261
+ isMainImage={isMainImage}
262
+ />
263
+ </picture>
264
+ </ImageSetOverrideWrapper>
245
265
  {(imageSet.picture.caption || imageSet.picture.credit) && (
246
266
  <figcaption className="n-content-picture__caption">
247
267
  <span>{imageSet.picture.caption}</span>
@@ -1,21 +1,8 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
2
  import React from 'react'
3
+ import { ContentProps } from '../types'
3
4
 
4
- interface ContentTreeLayoutSlot extends ContentTree.Node {
5
- type: 'slot'
6
- children: [ContentTree.ImageSet?, ...ContentTree.Paragraph[]]
7
- }
8
-
9
- interface BaseLayoutProps {
10
- layoutWidth: 'inset-left' | 'full-width' | 'full-grid' | 'in-line'
11
- layoutName?: string
12
- }
13
-
14
- interface ContentTreeCardLayout extends BaseLayoutProps, ContentTree.Node {
15
- type: 'layout'
16
- layoutName: 'auto' | 'card' | 'timeline'
17
- children: ContentTreeLayoutSlot[]
18
- }
5
+ interface LayoutProps extends ContentProps<ContentTree.transit.Layout> {}
19
6
 
20
7
  /*
21
8
  * @description Layout component is used to define the layout for content. It can be used with ContentTree Layout Nodes or as a static layout.
@@ -23,9 +10,10 @@ interface ContentTreeCardLayout extends BaseLayoutProps, ContentTree.Node {
23
10
  * @param layoutName - The name of the layout.
24
11
  * @param children - The content to be displayed in the layout.
25
12
  */
26
- export const Layout: React.FC<
27
- React.PropsWithChildren<ContentTreeCardLayout | BaseLayoutProps>
28
- > = ({ children, layoutWidth, layoutName = 'auto' }) => {
13
+ export const Layout: React.FC<React.PropsWithChildren<LayoutProps>> = ({
14
+ content: { layoutWidth, layoutName = 'auto' },
15
+ children,
16
+ }) => {
29
17
  return (
30
18
  <div
31
19
  className="n-content-layout"
@@ -1,11 +1,11 @@
1
1
  import React from 'react'
2
2
  import type {
3
3
  ArticleQuery,
4
- ImageFragment,
5
- ImageSetFragment,
4
+ MainImageFragment,
6
5
  } from '@financial-times/cp-content-pipeline-client'
7
6
  import ImageSet, { FallbackImage } from '../ImageSet/index'
8
7
  import { ContentTree } from '@financial-times/content-tree'
8
+ import type * as ComponentWorkarounds from '../Workarounds'
9
9
 
10
10
  type MainImageProps = {
11
11
  content: ArticleQuery['content']
@@ -20,19 +20,21 @@ const MainImage: React.FC<MainImageProps> = ({ content }) => {
20
20
  */
21
21
 
22
22
  const mainImage = references?.find(
23
- (ref) => ref.type === 'main-image'
24
- ) as ContentTree.ImageSet & ImageSetFragment
23
+ (ref): ref is ContentTree.ImageSet & MainImageFragment =>
24
+ ref.type === 'main-image'
25
+ )
25
26
 
26
27
  if (mainImage) {
27
- return <ImageSet isMainImage {...mainImage} />
28
+ return <ImageSet isMainImage content={mainImage} />
28
29
  }
29
30
 
30
31
  const mainImageRaw = references?.find(
31
- (ref) => ref.type === 'main-image-raw'
32
- ) as { image: ImageFragment }
32
+ (ref): ref is ComponentWorkarounds.MainImageRaw =>
33
+ ref.type === 'main-image-raw'
34
+ )
33
35
 
34
36
  if (mainImageRaw) {
35
- return <FallbackImage isMainImage {...mainImageRaw} />
37
+ return <FallbackImage isMainImage content={mainImageRaw} />
36
38
  }
37
39
 
38
40
  return null
@@ -1,11 +1,14 @@
1
1
  import { ContentTree } from '@financial-times/content-tree'
2
2
  import React, { createContext, useContext } from 'react'
3
+ import { ContentProps } from '../types'
3
4
 
4
5
  export const ParagraphContext = createContext<{ className: string } | null>(
5
6
  null
6
7
  )
7
8
 
8
- const Paragraph: React.FC<React.PropsWithChildren<ContentTree.Paragraph>> = (
9
+ interface ParagraphProps extends ContentProps<ContentTree.Paragraph> {}
10
+
11
+ const Paragraph: React.FC<React.PropsWithChildren<ParagraphProps>> = (
9
12
  props
10
13
  ) => {
11
14
  const { className } = useContext(ParagraphContext) ?? { className: undefined }
@@ -1,24 +1,31 @@
1
1
  import React from 'react'
2
2
  import { ContentTreeWorkarounds } from '@financial-times/cp-content-pipeline-schema'
3
3
  import classnames from 'classnames'
4
+ import { ContentProps } from '../types'
5
+
6
+ interface PullquoteProps
7
+ extends ContentProps<ContentTreeWorkarounds.Pullquote> {}
8
+
9
+ const Pullquote: React.FC<React.PropsWithChildren<PullquoteProps>> = ({
10
+ content: { text, source },
11
+ children,
12
+ }) => {
13
+ const hasChildren = React.Children.count(children) > 0
4
14
 
5
- const Pullquote: React.FC<
6
- React.PropsWithChildren<ContentTreeWorkarounds.ContentTreePullquote>
7
- > = (props) => {
8
15
  return (
9
16
  <blockquote
10
17
  className={classnames('n-content-pullquote', {
11
- 'n-content-pullquote--with-image': props.children.length > 0,
12
- 'n-content-pullquote--no-image': !props.children.length,
18
+ 'n-content-pullquote--with-image': hasChildren,
19
+ 'n-content-pullquote--no-image': !hasChildren,
13
20
  })}
14
21
  aria-hidden="true"
15
22
  >
16
23
  <div className="n-content-pullquote__content">
17
- <p>{props.text}</p>
18
- <footer className="n-content-pullquote__footer">{props.source}</footer>
24
+ <p>{text}</p>
25
+ <footer className="n-content-pullquote__footer">{source}</footer>
19
26
  </div>
20
27
 
21
- {props.children}
28
+ {children}
22
29
  </blockquote>
23
30
  )
24
31
  }
@@ -2,19 +2,19 @@ import React from 'react'
2
2
 
3
3
  //HACK: worked around missing Teaser type by declaring a module x-teaser.d.ts
4
4
  import { presets, Teaser } from '@financial-times/x-teaser/dist/Teaser.cjs'
5
- import { ContentTree } from '@financial-times/content-tree'
6
- import type { RecommendedFragment } from '@financial-times/cp-content-pipeline-client'
7
5
  import classnames from 'classnames'
8
- /**
9
- * Renders a Recommended teaser component
10
- * `<Teaser>` is imported from x-dash
11
- * https://github.com/Financial-Times/x-dash/tree/main/components/x-teaser)
12
- */
6
+ import { ContentProps } from '../types'
7
+ import type * as ComponentWorkarounds from '../Workarounds'
13
8
 
14
- const Recommended: React.FC<ContentTree.Recommended & RecommendedFragment> = ({
15
- heading,
16
- teaser,
17
- isInLiveBlog,
9
+ // Renders a Recommended teaser component
10
+ // `<Teaser>` is imported from x-dash
11
+ // https://github.com/Financial-Times/x-dash/tree/main/components/x-teaser)
12
+
13
+ interface RecommendedProps
14
+ extends ContentProps<ComponentWorkarounds.Recommended> {}
15
+
16
+ const Recommended: React.FC<RecommendedProps> = ({
17
+ content: { heading, teaser, isInLiveBlog },
18
18
  }) => {
19
19
  if (!teaser) {
20
20
  return null
@@ -1,34 +1,36 @@
1
1
  import React from 'react'
2
2
  import type { ContentTree } from '@financial-times/content-tree'
3
3
  import type { ContentTreeWorkarounds } from '@financial-times/cp-content-pipeline-schema'
4
+ import { ContentProps } from '../types'
4
5
 
5
- export const List: React.FC<React.PropsWithChildren<ContentTree.List>> = (
6
- props
7
- ) => (props.ordered ? <ol>{props.children}</ol> : <ul>{props.children}</ul>)
6
+ export const List: React.FC<
7
+ React.PropsWithChildren<ContentProps<ContentTree.List>>
8
+ > = (props) =>
9
+ props.content.ordered ? <ol>{props.children}</ol> : <ul>{props.children}</ul>
8
10
 
9
11
  export const ListItem: React.FC<
10
- React.PropsWithChildren<ContentTree.ListItem>
12
+ React.PropsWithChildren<ContentProps<ContentTree.ListItem>>
11
13
  > = (props) => <li>{props.children}</li>
12
14
 
13
- export const Link: React.FC<React.PropsWithChildren<ContentTree.Link>> = (
14
- props
15
- ) => (
16
- <a href={props.url} title={props.title}>
15
+ export const Link: React.FC<
16
+ React.PropsWithChildren<ContentProps<ContentTree.Link>>
17
+ > = (props) => (
18
+ <a href={props.content.url} title={props.content.title}>
17
19
  {props.children}
18
20
  </a>
19
21
  )
20
22
 
21
23
  export const Blockquote: React.FC<
22
- React.PropsWithChildren<ContentTree.Blockquote>
24
+ React.PropsWithChildren<ContentProps<ContentTree.Blockquote>>
23
25
  > = (props) => {
24
26
  return (
25
27
  <blockquote className="n-content-blockquote">{props.children}</blockquote>
26
28
  )
27
29
  }
28
30
 
29
- export const Cite: React.FC<React.PropsWithChildren<ContentTree.Phrasing>> = (
30
- props
31
- ) => {
31
+ export const Cite: React.FC<
32
+ React.PropsWithChildren<ContentProps<ContentTreeWorkarounds.Cite>>
33
+ > = (props) => {
32
34
  return <cite>{props.children}</cite>
33
35
  }
34
36
 
@@ -37,23 +39,23 @@ export const LineBreak: React.FC = () => <br />
37
39
  export const HorizontalRule: React.FC = () => <hr />
38
40
 
39
41
  export const Emphasis: React.FC<
40
- React.PropsWithChildren<ContentTree.Emphasis>
42
+ React.PropsWithChildren<ContentProps<ContentTree.Emphasis>>
41
43
  > = (props) => <em>{props.children}</em>
42
44
 
43
- export const Strong: React.FC<React.PropsWithChildren<ContentTree.Strong>> = (
44
- props
45
- ) => <strong>{props.children}</strong>
45
+ export const Strong: React.FC<
46
+ React.PropsWithChildren<ContentProps<ContentTree.Strong>>
47
+ > = (props) => <strong>{props.children}</strong>
46
48
 
47
49
  export const Strikethrough: React.FC<
48
- React.PropsWithChildren<ContentTree.Strikethrough>
50
+ React.PropsWithChildren<ContentProps<ContentTree.Strikethrough>>
49
51
  > = (props) => <s>{props.children}</s>
50
52
 
51
53
  export const TableRow: React.FC<
52
- React.PropsWithChildren<ContentTreeWorkarounds.TableRow>
54
+ React.PropsWithChildren<ContentProps<ContentTreeWorkarounds.TableRow>>
53
55
  > = (props) => <tr>{props.children}</tr>
54
56
 
55
57
  export const TableFooter: React.FC<
56
- React.PropsWithChildren<ContentTreeWorkarounds.TableFooter>
58
+ React.PropsWithChildren<ContentProps<ContentTreeWorkarounds.TableFooter>>
57
59
  > = (props) => (
58
60
  <tfoot>
59
61
  <tr>
@@ -65,7 +67,7 @@ export const TableFooter: React.FC<
65
67
  )
66
68
 
67
69
  export const TableCaption: React.FC<
68
- React.PropsWithChildren<ContentTreeWorkarounds.TableCaption>
70
+ React.PropsWithChildren<ContentProps<ContentTreeWorkarounds.TableCaption>>
69
71
  > = (props) => (
70
72
  <caption className="n-content-body__caption n-content-body__caption--auto">
71
73
  {props.children}