@financial-times/x-teaser 17.0.5 → 18.1.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.
- package/Props.d.ts +5 -0
- package/__tests__/Status.test.jsx +312 -0
- package/dist/Teaser.cjs.js +54 -26
- package/dist/Teaser.es5.js +55 -26
- package/dist/Teaser.esm.js +54 -26
- package/package.json +2 -2
- package/readme.md +7 -0
- package/src/LiveBlogStatus.jsx +11 -8
- package/src/PremiumLabel.jsx +4 -1
- package/src/ScoopLabel.jsx +10 -0
- package/src/Status.jsx +23 -13
- package/src/Teaser.scss +4 -0
- package/src/concerns/presets.js +8 -0
- package/src/concerns/rules.js +1 -1
- package/storybook/argTypes.js +5 -1
- package/storybook/article.js +3 -1
- package/storybook/index.jsx +3 -3
package/Props.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export interface Features {
|
|
|
31
31
|
showTitle?: boolean
|
|
32
32
|
showStandfirst?: boolean
|
|
33
33
|
showPremiumLabel?: boolean
|
|
34
|
+
showScoopLabel?: boolean
|
|
34
35
|
showStatus?: boolean
|
|
35
36
|
showImage?: boolean
|
|
36
37
|
showHeadshot?: boolean
|
|
@@ -40,6 +41,10 @@ export interface Features {
|
|
|
40
41
|
showPromotionalContent?: boolean
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
export interface SpecialStylings {
|
|
45
|
+
allowLiveTeaserStyling?: boolean
|
|
46
|
+
}
|
|
47
|
+
|
|
43
48
|
export interface General {
|
|
44
49
|
id: string
|
|
45
50
|
url?: string
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
const { h } = require('@financial-times/x-engine')
|
|
2
|
+
const { mount } = require('@financial-times/x-test-utils/enzyme')
|
|
3
|
+
|
|
4
|
+
import * as LiveBlogStatus from '../src/LiveBlogStatus'
|
|
5
|
+
import * as ScoopLabel from '../src/ScoopLabel'
|
|
6
|
+
import * as PremiumLabel from '../src/PremiumLabel'
|
|
7
|
+
import * as AlwaysShowTimestamp from '../src/AlwaysShowTimestamp'
|
|
8
|
+
import * as RelativeTime from '../src/RelativeTime'
|
|
9
|
+
import * as TimeStamp from '../src/TimeStamp'
|
|
10
|
+
|
|
11
|
+
const LiveBlogStatusSpy = jest
|
|
12
|
+
.spyOn(LiveBlogStatus, 'default')
|
|
13
|
+
.mockReturnValue(<div className="live-blog-status">LiveBlogStatus</div>)
|
|
14
|
+
const ScoopLabelSpy = jest
|
|
15
|
+
.spyOn(ScoopLabel, 'default')
|
|
16
|
+
.mockReturnValue(<div className="scoop-label">ScoopLabel</div>)
|
|
17
|
+
const PremiumLabelSpy = jest
|
|
18
|
+
.spyOn(PremiumLabel, 'default')
|
|
19
|
+
.mockReturnValue(<div className="premium-label">PremiumLabel</div>)
|
|
20
|
+
const AlwaysShowTimestampSpy = jest
|
|
21
|
+
.spyOn(AlwaysShowTimestamp, 'default')
|
|
22
|
+
.mockReturnValue(<div className="always-show-timestamp">AlwaysShowTimestamp</div>)
|
|
23
|
+
const RelativeTimeSpy = jest
|
|
24
|
+
.spyOn(RelativeTime, 'default')
|
|
25
|
+
.mockReturnValue(<div className="relative-time">RelativeTimeSpy</div>)
|
|
26
|
+
const TimeStampSpy = jest
|
|
27
|
+
.spyOn(TimeStamp, 'default')
|
|
28
|
+
.mockReturnValue(<div className="timestamp">TimeStamp</div>)
|
|
29
|
+
|
|
30
|
+
import Status from '../src/Status'
|
|
31
|
+
|
|
32
|
+
describe('Status - Display Logic', () => {
|
|
33
|
+
let props
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
props = {
|
|
37
|
+
showStatus: true,
|
|
38
|
+
showPremiumLabel: true,
|
|
39
|
+
showScoopLabel: true,
|
|
40
|
+
status: 'inprogress',
|
|
41
|
+
indicators: { isScoop: true, accessLevel: 'premium' },
|
|
42
|
+
firstPublishedDate: '2025-10-01T00:00:00.000Z',
|
|
43
|
+
publishedDate: '2025-10-01T00:00:00.000Z',
|
|
44
|
+
useRelativeTimeIfToday: true,
|
|
45
|
+
useRelativeTime: true
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
jest.clearAllMocks()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('LiveBlogStatus', () => {
|
|
54
|
+
it('renders only LiveBlogStatus when LiveBlogStatus props are present', () => {
|
|
55
|
+
mount(<Status {...props} />)
|
|
56
|
+
|
|
57
|
+
expect(LiveBlogStatusSpy).toHaveBeenCalledTimes(1)
|
|
58
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
59
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
60
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
61
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
62
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should not render LiveBlogStatus when status is not present', () => {
|
|
66
|
+
delete props.status
|
|
67
|
+
mount(<Status {...props} />)
|
|
68
|
+
|
|
69
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should not render LiveBlogStatus when showStatus is false', () => {
|
|
73
|
+
props.showStatus = false
|
|
74
|
+
mount(<Status {...props} />)
|
|
75
|
+
|
|
76
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('ScoopLabel', () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
delete props.status
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('renders only ScoopLabel when higher-level render props are absent and ScoopLabel props are present', () => {
|
|
86
|
+
mount(<Status {...props} />)
|
|
87
|
+
|
|
88
|
+
expect(ScoopLabelSpy).toHaveBeenCalledTimes(1)
|
|
89
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
90
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
91
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
92
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
93
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should not render ScoopLabel when showScoopLabel = false', () => {
|
|
97
|
+
props.showScoopLabel = false
|
|
98
|
+
mount(<Status {...props} />)
|
|
99
|
+
|
|
100
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should not render ScoopLabel when props.indicators.isScoop = false', () => {
|
|
104
|
+
props.indicators.isScoop = false
|
|
105
|
+
mount(<Status {...props} />)
|
|
106
|
+
|
|
107
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should not render ScoopLabel when firstPublishedDate is older than the cutoff date', () => {
|
|
111
|
+
props.firstPublishedDate = '2025-09-01T00:00:00.000Z'
|
|
112
|
+
mount(<Status {...props} />)
|
|
113
|
+
|
|
114
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
115
|
+
})
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
describe('PremiumLabel', () => {
|
|
119
|
+
beforeEach(() => {
|
|
120
|
+
delete props.status
|
|
121
|
+
props.showScoopLabel = false
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('renders only PremiumLabel when higher-level render props are absent and PremiumLabel props are present', () => {
|
|
125
|
+
mount(<Status {...props} />)
|
|
126
|
+
|
|
127
|
+
expect(PremiumLabelSpy).toHaveBeenCalledTimes(1)
|
|
128
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
129
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
130
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
131
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
132
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should not render PremiumLabel when showPremiumLabel = false', () => {
|
|
136
|
+
props.showPremiumLabel = false
|
|
137
|
+
mount(<Status {...props} />)
|
|
138
|
+
|
|
139
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should not render PremiumLabel when accessLevel is not premium', () => {
|
|
143
|
+
props.indicators.accessLevel = 'subscribed'
|
|
144
|
+
mount(<Status {...props} />)
|
|
145
|
+
|
|
146
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
describe('AlwaysShowTimestamp', () => {
|
|
151
|
+
beforeEach(() => {
|
|
152
|
+
delete props.status
|
|
153
|
+
props.showScoopLabel = false
|
|
154
|
+
props.showPremiumLabel = false
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('renders only AlwaysShowTimestamp when higher-level render props are absent and AlwaysShowTimestamp props are present', () => {
|
|
158
|
+
mount(<Status {...props} />)
|
|
159
|
+
|
|
160
|
+
expect(AlwaysShowTimestampSpy).toHaveBeenCalledTimes(1)
|
|
161
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
162
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
163
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
164
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
165
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
it('should not render AlwaysShowTimestamp when showStatus = false', () => {
|
|
169
|
+
props.showStatus = false
|
|
170
|
+
mount(<Status {...props} />)
|
|
171
|
+
|
|
172
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
it('should not render AlwaysShowTimestamp when publishedDate is not present', () => {
|
|
176
|
+
delete props.publishedDate
|
|
177
|
+
mount(<Status {...props} />)
|
|
178
|
+
|
|
179
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
it('should not render AlwaysShowTimestamp when useRelativeTimeIfToday = false', () => {
|
|
183
|
+
props.useRelativeTimeIfToday = false
|
|
184
|
+
mount(<Status {...props} />)
|
|
185
|
+
|
|
186
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
describe('RelativeTime', () => {
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
delete props.status
|
|
193
|
+
props.showScoopLabel = false
|
|
194
|
+
props.showPremiumLabel = false
|
|
195
|
+
props.useRelativeTimeIfToday = false
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
it('renders only RelativeTime when higher-level render props are absent and RelativeTime props are present', () => {
|
|
199
|
+
mount(<Status {...props} />)
|
|
200
|
+
|
|
201
|
+
expect(RelativeTimeSpy).toHaveBeenCalledTimes(1)
|
|
202
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
203
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
204
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
205
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
206
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should not render RelativeTime when showStatus = false', () => {
|
|
210
|
+
props.showStatus = false
|
|
211
|
+
mount(<Status {...props} />)
|
|
212
|
+
|
|
213
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should not render RelativeTime when publishedDate is not present', () => {
|
|
217
|
+
delete props.publishedDate
|
|
218
|
+
mount(<Status {...props} />)
|
|
219
|
+
|
|
220
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
it('should not render RelativeTime when useRelativeTime = false', () => {
|
|
224
|
+
props.useRelativeTime = false
|
|
225
|
+
mount(<Status {...props} />)
|
|
226
|
+
|
|
227
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
describe('TimeStamp', () => {
|
|
232
|
+
beforeEach(() => {
|
|
233
|
+
delete props.status
|
|
234
|
+
props.showScoopLabel = false
|
|
235
|
+
props.showPremiumLabel = false
|
|
236
|
+
props.useRelativeTimeIfToday = false
|
|
237
|
+
props.useRelativeTime = false
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('renders only TimeStamp when higher-level render props are absent and TimeStamp props are present', () => {
|
|
241
|
+
mount(<Status {...props} />)
|
|
242
|
+
|
|
243
|
+
expect(TimeStampSpy).toHaveBeenCalledTimes(1)
|
|
244
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
245
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
246
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
247
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
248
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('should not render TimeStamp when showStatus = false', () => {
|
|
252
|
+
props.showStatus = false
|
|
253
|
+
mount(<Status {...props} />)
|
|
254
|
+
|
|
255
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('should not render TimeStamp when publishedDate is not present', () => {
|
|
259
|
+
delete props.publishedDate
|
|
260
|
+
mount(<Status {...props} />)
|
|
261
|
+
|
|
262
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe('No Status rendered', () => {
|
|
267
|
+
it('should not render any Status - Case 1: all showXXX properties are set to false', () => {
|
|
268
|
+
props = {
|
|
269
|
+
showStatus: false,
|
|
270
|
+
showPremiumLabel: false,
|
|
271
|
+
showScoopLabel: false,
|
|
272
|
+
status: 'inprogress',
|
|
273
|
+
indicators: { isScoop: true, accessLevel: 'premium' },
|
|
274
|
+
firstPublishedDate: '2025-10-01T00:00:00.000Z',
|
|
275
|
+
publishedDate: '2025-10-01T00:00:00.000Z',
|
|
276
|
+
useRelativeTimeIfToday: true,
|
|
277
|
+
useRelativeTime: true
|
|
278
|
+
}
|
|
279
|
+
const subject = mount(<Status {...props} />)
|
|
280
|
+
|
|
281
|
+
expect(subject.isEmptyRender()).toBeTruthy()
|
|
282
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
283
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
284
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
285
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
286
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
287
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
it('should not render any Status - Case 2: showStatus = true but other properties are not present', () => {
|
|
291
|
+
// status & publishedDate are missing
|
|
292
|
+
props = {
|
|
293
|
+
showStatus: true,
|
|
294
|
+
showPremiumLabel: false,
|
|
295
|
+
showScoopLabel: false,
|
|
296
|
+
indicators: { isScoop: true, accessLevel: 'premium' },
|
|
297
|
+
firstPublishedDate: '2025-10-01T00:00:00.000Z',
|
|
298
|
+
useRelativeTimeIfToday: true,
|
|
299
|
+
useRelativeTime: true
|
|
300
|
+
}
|
|
301
|
+
const subject = mount(<Status {...props} />)
|
|
302
|
+
|
|
303
|
+
expect(subject.isEmptyRender()).toBeTruthy()
|
|
304
|
+
expect(LiveBlogStatusSpy).not.toHaveBeenCalled()
|
|
305
|
+
expect(ScoopLabelSpy).not.toHaveBeenCalled()
|
|
306
|
+
expect(PremiumLabelSpy).not.toHaveBeenCalled()
|
|
307
|
+
expect(AlwaysShowTimestampSpy).not.toHaveBeenCalled()
|
|
308
|
+
expect(RelativeTimeSpy).not.toHaveBeenCalled()
|
|
309
|
+
expect(TimeStampSpy).not.toHaveBeenCalled()
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
})
|
package/dist/Teaser.cjs.js
CHANGED
|
@@ -32,7 +32,7 @@ const rulesets = {
|
|
|
32
32
|
if (props.theme) {
|
|
33
33
|
return props.theme;
|
|
34
34
|
}
|
|
35
|
-
if (props.status === 'inprogress') {
|
|
35
|
+
if (props.status === 'inprogress' && props.allowLiveTeaserStyling) {
|
|
36
36
|
return 'live';
|
|
37
37
|
}
|
|
38
38
|
if (props.indicators && props.indicators.isOpinion) {
|
|
@@ -437,23 +437,23 @@ var RelativeTime = ({
|
|
|
437
437
|
}, displayTime(relativeDate))) : null;
|
|
438
438
|
};
|
|
439
439
|
|
|
440
|
-
const LiveBlogLabels = {
|
|
441
|
-
inprogress: 'Live',
|
|
442
|
-
comingsoon: 'Coming Soon',
|
|
443
|
-
closed: ''
|
|
444
|
-
};
|
|
445
440
|
const LiveBlogModifiers = {
|
|
446
441
|
inprogress: 'live',
|
|
447
442
|
comingsoon: 'pending',
|
|
448
443
|
closed: 'closed'
|
|
449
444
|
};
|
|
450
445
|
var LiveBlogStatus = ({
|
|
451
|
-
status
|
|
446
|
+
status,
|
|
447
|
+
allowLiveTeaserStyling = false
|
|
452
448
|
}) => status && status !== 'closed' ? xEngine.h("div", {
|
|
453
449
|
className: `o-teaser__timestamp o-teaser__timestamp--${LiveBlogModifiers[status]}`
|
|
454
|
-
}, xEngine.h("span", {
|
|
450
|
+
}, status === 'comingsoon' && xEngine.h("span", {
|
|
455
451
|
className: "o-teaser__timestamp-prefix"
|
|
456
|
-
}, `
|
|
452
|
+
}, ` Coming Soon `), status === 'inprogress' && xEngine.h("span", {
|
|
453
|
+
className: `o-labels-indicator o-labels-indicator--live ${allowLiveTeaserStyling ? null : 'o-labels-indicator--badge'}`
|
|
454
|
+
}, xEngine.h("span", {
|
|
455
|
+
className: "o-labels-indicator__status"
|
|
456
|
+
}, ` Live `))) : null;
|
|
457
457
|
|
|
458
458
|
/**
|
|
459
459
|
* Timestamp shown always, the default 4h limit does not apply here
|
|
@@ -473,32 +473,52 @@ var AlwaysShowTimestamp = props => {
|
|
|
473
473
|
};
|
|
474
474
|
|
|
475
475
|
function PremiumLabel() {
|
|
476
|
+
return (
|
|
477
|
+
// WARNING: Do not use the x-teaser__premium-label class to override styling.
|
|
478
|
+
// The styling should be in o-teaser, not x-teaser.
|
|
479
|
+
// Use o-teaser__labels or o-teaser__labels--premium instead of x-teaser__premium-label.
|
|
480
|
+
xEngine.h("div", {
|
|
481
|
+
className: "x-teaser__premium-label o-teaser__labels o-teaser__labels--premium"
|
|
482
|
+
}, xEngine.h("span", {
|
|
483
|
+
className: "o-labels o-labels--premium o-labels--content-premium"
|
|
484
|
+
}, "Premium"), xEngine.h("span", {
|
|
485
|
+
className: "o3-visually-hidden"
|
|
486
|
+
}, "\xA0content"))
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function ScoopLabel() {
|
|
476
491
|
return xEngine.h("div", {
|
|
477
|
-
className: "
|
|
492
|
+
className: "o-teaser__labels o-teaser__labels--scoop"
|
|
478
493
|
}, xEngine.h("span", {
|
|
479
|
-
className: "o-labels o-labels--
|
|
480
|
-
}, "
|
|
494
|
+
className: "o-labels o-labels--content-scoop"
|
|
495
|
+
}, "Exclusive"), xEngine.h("span", {
|
|
481
496
|
className: "o3-visually-hidden"
|
|
482
497
|
}, "\xA0content"));
|
|
483
498
|
}
|
|
484
499
|
|
|
485
500
|
var Status = props => {
|
|
486
|
-
var _props$indicators;
|
|
487
|
-
if (props.
|
|
501
|
+
var _props$indicators, _props$indicators2;
|
|
502
|
+
if (props.showStatus && props.status) {
|
|
503
|
+
return xEngine.h(LiveBlogStatus, props);
|
|
504
|
+
}
|
|
505
|
+
if (props.showScoopLabel && props !== null && props !== void 0 && (_props$indicators = props.indicators) !== null && _props$indicators !== void 0 && _props$indicators.isScoop &&
|
|
506
|
+
// We plan to show the Scoop label only on homepages.
|
|
507
|
+
// If we later show it on other pages, this cutoff date will need review.
|
|
508
|
+
// The `isScoop` property already exists, but Editorial will use it differently after 2025-10-01.
|
|
509
|
+
new Date(props.firstPublishedDate) >= new Date('2025-10-01T00:00:00.000Z')) {
|
|
510
|
+
return xEngine.h(ScoopLabel, props);
|
|
511
|
+
}
|
|
512
|
+
if (props.showPremiumLabel && (props === null || props === void 0 || (_props$indicators2 = props.indicators) === null || _props$indicators2 === void 0 ? void 0 : _props$indicators2.accessLevel) === 'premium') {
|
|
488
513
|
return xEngine.h(PremiumLabel, props);
|
|
489
514
|
}
|
|
490
|
-
if (props.showStatus) {
|
|
491
|
-
if (props.
|
|
492
|
-
return xEngine.h(
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
} else if (props.useRelativeTime) {
|
|
498
|
-
return xEngine.h(RelativeTime, props);
|
|
499
|
-
} else {
|
|
500
|
-
return xEngine.h(TimeStamp, props);
|
|
501
|
-
}
|
|
515
|
+
if (props.showStatus && props.publishedDate) {
|
|
516
|
+
if (props.useRelativeTimeIfToday) {
|
|
517
|
+
return xEngine.h(AlwaysShowTimestamp, props);
|
|
518
|
+
} else if (props.useRelativeTime) {
|
|
519
|
+
return xEngine.h(RelativeTime, props);
|
|
520
|
+
} else {
|
|
521
|
+
return xEngine.h(TimeStamp, props);
|
|
502
522
|
}
|
|
503
523
|
}
|
|
504
524
|
return null;
|
|
@@ -604,6 +624,7 @@ const Small = {
|
|
|
604
624
|
showMeta: true,
|
|
605
625
|
showTitle: true,
|
|
606
626
|
showPremiumLabel: true,
|
|
627
|
+
showScoopLabel: false,
|
|
607
628
|
showStatus: true
|
|
608
629
|
};
|
|
609
630
|
const SmallHeavy = {
|
|
@@ -613,6 +634,7 @@ const SmallHeavy = {
|
|
|
613
634
|
showTitle: true,
|
|
614
635
|
showStandfirst: true,
|
|
615
636
|
showPremiumLabel: true,
|
|
637
|
+
showScoopLabel: false,
|
|
616
638
|
showStatus: true,
|
|
617
639
|
showImage: true,
|
|
618
640
|
imageSize: 'Small'
|
|
@@ -624,6 +646,7 @@ const Large = {
|
|
|
624
646
|
showTitle: true,
|
|
625
647
|
showStandfirst: true,
|
|
626
648
|
showPremiumLabel: true,
|
|
649
|
+
showScoopLabel: false,
|
|
627
650
|
showStatus: true,
|
|
628
651
|
showImage: true,
|
|
629
652
|
imageSize: 'Medium'
|
|
@@ -634,6 +657,7 @@ const Hero = {
|
|
|
634
657
|
showMeta: true,
|
|
635
658
|
showTitle: true,
|
|
636
659
|
showPremiumLabel: true,
|
|
660
|
+
showScoopLabel: false,
|
|
637
661
|
showStatus: true,
|
|
638
662
|
showImage: true,
|
|
639
663
|
imageSize: 'Medium'
|
|
@@ -645,6 +669,7 @@ const HeroNarrow = {
|
|
|
645
669
|
showTitle: true,
|
|
646
670
|
showStandfirst: true,
|
|
647
671
|
showPremiumLabel: true,
|
|
672
|
+
showScoopLabel: false,
|
|
648
673
|
showStatus: true
|
|
649
674
|
};
|
|
650
675
|
const HeroVideo = {
|
|
@@ -661,6 +686,7 @@ const HeroOverlay = {
|
|
|
661
686
|
showMeta: true,
|
|
662
687
|
showTitle: true,
|
|
663
688
|
showPremiumLabel: true,
|
|
689
|
+
showScoopLabel: false,
|
|
664
690
|
showStatus: true,
|
|
665
691
|
showImage: true,
|
|
666
692
|
imageSize: 'XL',
|
|
@@ -673,6 +699,7 @@ const TopStory = {
|
|
|
673
699
|
showTitle: true,
|
|
674
700
|
showStandfirst: true,
|
|
675
701
|
showPremiumLabel: true,
|
|
702
|
+
showScoopLabel: false,
|
|
676
703
|
showStatus: true,
|
|
677
704
|
showRelatedLinks: true
|
|
678
705
|
};
|
|
@@ -683,6 +710,7 @@ const TopStoryLandscape = {
|
|
|
683
710
|
showTitle: true,
|
|
684
711
|
showStandfirst: true,
|
|
685
712
|
showPremiumLabel: true,
|
|
713
|
+
showScoopLabel: false,
|
|
686
714
|
showStatus: true,
|
|
687
715
|
showImage: true,
|
|
688
716
|
imageSize: 'XL',
|
package/dist/Teaser.es5.js
CHANGED
|
@@ -32,7 +32,7 @@ var rulesets = {
|
|
|
32
32
|
if (props.theme) {
|
|
33
33
|
return props.theme;
|
|
34
34
|
}
|
|
35
|
-
if (props.status === 'inprogress') {
|
|
35
|
+
if (props.status === 'inprogress' && props.allowLiveTeaserStyling) {
|
|
36
36
|
return 'live';
|
|
37
37
|
}
|
|
38
38
|
if (props.indicators && props.indicators.isOpinion) {
|
|
@@ -524,23 +524,24 @@ var RelativeTime = (function (_ref) {
|
|
|
524
524
|
}, displayTime(relativeDate))) : null;
|
|
525
525
|
});
|
|
526
526
|
|
|
527
|
-
var LiveBlogLabels = {
|
|
528
|
-
inprogress: 'Live',
|
|
529
|
-
comingsoon: 'Coming Soon',
|
|
530
|
-
closed: ''
|
|
531
|
-
};
|
|
532
527
|
var LiveBlogModifiers = {
|
|
533
528
|
inprogress: 'live',
|
|
534
529
|
comingsoon: 'pending',
|
|
535
530
|
closed: 'closed'
|
|
536
531
|
};
|
|
537
532
|
var LiveBlogStatus = (function (_ref) {
|
|
538
|
-
var status = _ref.status
|
|
533
|
+
var status = _ref.status,
|
|
534
|
+
_ref$allowLiveTeaserS = _ref.allowLiveTeaserStyling,
|
|
535
|
+
allowLiveTeaserStyling = _ref$allowLiveTeaserS === void 0 ? false : _ref$allowLiveTeaserS;
|
|
539
536
|
return status && status !== 'closed' ? xEngine.h("div", {
|
|
540
537
|
className: "o-teaser__timestamp o-teaser__timestamp--".concat(LiveBlogModifiers[status])
|
|
541
|
-
}, xEngine.h("span", {
|
|
538
|
+
}, status === 'comingsoon' && xEngine.h("span", {
|
|
542
539
|
className: "o-teaser__timestamp-prefix"
|
|
543
|
-
}, " "
|
|
540
|
+
}, " Coming Soon "), status === 'inprogress' && xEngine.h("span", {
|
|
541
|
+
className: "o-labels-indicator o-labels-indicator--live ".concat(allowLiveTeaserStyling ? null : 'o-labels-indicator--badge')
|
|
542
|
+
}, xEngine.h("span", {
|
|
543
|
+
className: "o-labels-indicator__status"
|
|
544
|
+
}, " Live "))) : null;
|
|
544
545
|
});
|
|
545
546
|
|
|
546
547
|
/**
|
|
@@ -561,32 +562,52 @@ var AlwaysShowTimestamp = (function (props) {
|
|
|
561
562
|
});
|
|
562
563
|
|
|
563
564
|
function PremiumLabel() {
|
|
565
|
+
return (
|
|
566
|
+
// WARNING: Do not use the x-teaser__premium-label class to override styling.
|
|
567
|
+
// The styling should be in o-teaser, not x-teaser.
|
|
568
|
+
// Use o-teaser__labels or o-teaser__labels--premium instead of x-teaser__premium-label.
|
|
569
|
+
xEngine.h("div", {
|
|
570
|
+
className: "x-teaser__premium-label o-teaser__labels o-teaser__labels--premium"
|
|
571
|
+
}, xEngine.h("span", {
|
|
572
|
+
className: "o-labels o-labels--premium o-labels--content-premium"
|
|
573
|
+
}, "Premium"), xEngine.h("span", {
|
|
574
|
+
className: "o3-visually-hidden"
|
|
575
|
+
}, "\xA0content"))
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function ScoopLabel() {
|
|
564
580
|
return xEngine.h("div", {
|
|
565
|
-
className: "
|
|
581
|
+
className: "o-teaser__labels o-teaser__labels--scoop"
|
|
566
582
|
}, xEngine.h("span", {
|
|
567
|
-
className: "o-labels o-labels--
|
|
568
|
-
}, "
|
|
583
|
+
className: "o-labels o-labels--content-scoop"
|
|
584
|
+
}, "Exclusive"), xEngine.h("span", {
|
|
569
585
|
className: "o3-visually-hidden"
|
|
570
586
|
}, "\xA0content"));
|
|
571
587
|
}
|
|
572
588
|
|
|
573
589
|
var Status = (function (props) {
|
|
574
|
-
var _props$indicators;
|
|
575
|
-
if (props.
|
|
590
|
+
var _props$indicators, _props$indicators2;
|
|
591
|
+
if (props.showStatus && props.status) {
|
|
592
|
+
return xEngine.h(LiveBlogStatus, props);
|
|
593
|
+
}
|
|
594
|
+
if (props.showScoopLabel && props !== null && props !== void 0 && (_props$indicators = props.indicators) !== null && _props$indicators !== void 0 && _props$indicators.isScoop &&
|
|
595
|
+
// We plan to show the Scoop label only on homepages.
|
|
596
|
+
// If we later show it on other pages, this cutoff date will need review.
|
|
597
|
+
// The `isScoop` property already exists, but Editorial will use it differently after 2025-10-01.
|
|
598
|
+
new Date(props.firstPublishedDate) >= new Date('2025-10-01T00:00:00.000Z')) {
|
|
599
|
+
return xEngine.h(ScoopLabel, props);
|
|
600
|
+
}
|
|
601
|
+
if (props.showPremiumLabel && (props === null || props === void 0 || (_props$indicators2 = props.indicators) === null || _props$indicators2 === void 0 ? void 0 : _props$indicators2.accessLevel) === 'premium') {
|
|
576
602
|
return xEngine.h(PremiumLabel, props);
|
|
577
603
|
}
|
|
578
|
-
if (props.showStatus) {
|
|
579
|
-
if (props.
|
|
580
|
-
return xEngine.h(
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
} else if (props.useRelativeTime) {
|
|
586
|
-
return xEngine.h(RelativeTime, props);
|
|
587
|
-
} else {
|
|
588
|
-
return xEngine.h(TimeStamp, props);
|
|
589
|
-
}
|
|
604
|
+
if (props.showStatus && props.publishedDate) {
|
|
605
|
+
if (props.useRelativeTimeIfToday) {
|
|
606
|
+
return xEngine.h(AlwaysShowTimestamp, props);
|
|
607
|
+
} else if (props.useRelativeTime) {
|
|
608
|
+
return xEngine.h(RelativeTime, props);
|
|
609
|
+
} else {
|
|
610
|
+
return xEngine.h(TimeStamp, props);
|
|
590
611
|
}
|
|
591
612
|
}
|
|
592
613
|
return null;
|
|
@@ -697,6 +718,7 @@ var Small = {
|
|
|
697
718
|
showMeta: true,
|
|
698
719
|
showTitle: true,
|
|
699
720
|
showPremiumLabel: true,
|
|
721
|
+
showScoopLabel: false,
|
|
700
722
|
showStatus: true
|
|
701
723
|
};
|
|
702
724
|
var SmallHeavy = {
|
|
@@ -706,6 +728,7 @@ var SmallHeavy = {
|
|
|
706
728
|
showTitle: true,
|
|
707
729
|
showStandfirst: true,
|
|
708
730
|
showPremiumLabel: true,
|
|
731
|
+
showScoopLabel: false,
|
|
709
732
|
showStatus: true,
|
|
710
733
|
showImage: true,
|
|
711
734
|
imageSize: 'Small'
|
|
@@ -717,6 +740,7 @@ var Large = {
|
|
|
717
740
|
showTitle: true,
|
|
718
741
|
showStandfirst: true,
|
|
719
742
|
showPremiumLabel: true,
|
|
743
|
+
showScoopLabel: false,
|
|
720
744
|
showStatus: true,
|
|
721
745
|
showImage: true,
|
|
722
746
|
imageSize: 'Medium'
|
|
@@ -727,6 +751,7 @@ var Hero = {
|
|
|
727
751
|
showMeta: true,
|
|
728
752
|
showTitle: true,
|
|
729
753
|
showPremiumLabel: true,
|
|
754
|
+
showScoopLabel: false,
|
|
730
755
|
showStatus: true,
|
|
731
756
|
showImage: true,
|
|
732
757
|
imageSize: 'Medium'
|
|
@@ -738,6 +763,7 @@ var HeroNarrow = {
|
|
|
738
763
|
showTitle: true,
|
|
739
764
|
showStandfirst: true,
|
|
740
765
|
showPremiumLabel: true,
|
|
766
|
+
showScoopLabel: false,
|
|
741
767
|
showStatus: true
|
|
742
768
|
};
|
|
743
769
|
var HeroVideo = {
|
|
@@ -754,6 +780,7 @@ var HeroOverlay = {
|
|
|
754
780
|
showMeta: true,
|
|
755
781
|
showTitle: true,
|
|
756
782
|
showPremiumLabel: true,
|
|
783
|
+
showScoopLabel: false,
|
|
757
784
|
showStatus: true,
|
|
758
785
|
showImage: true,
|
|
759
786
|
imageSize: 'XL',
|
|
@@ -766,6 +793,7 @@ var TopStory = {
|
|
|
766
793
|
showTitle: true,
|
|
767
794
|
showStandfirst: true,
|
|
768
795
|
showPremiumLabel: true,
|
|
796
|
+
showScoopLabel: false,
|
|
769
797
|
showStatus: true,
|
|
770
798
|
showRelatedLinks: true
|
|
771
799
|
};
|
|
@@ -776,6 +804,7 @@ var TopStoryLandscape = {
|
|
|
776
804
|
showTitle: true,
|
|
777
805
|
showStandfirst: true,
|
|
778
806
|
showPremiumLabel: true,
|
|
807
|
+
showScoopLabel: false,
|
|
779
808
|
showStatus: true,
|
|
780
809
|
showImage: true,
|
|
781
810
|
imageSize: 'XL',
|
package/dist/Teaser.esm.js
CHANGED
|
@@ -26,7 +26,7 @@ const rulesets = {
|
|
|
26
26
|
if (props.theme) {
|
|
27
27
|
return props.theme;
|
|
28
28
|
}
|
|
29
|
-
if (props.status === 'inprogress') {
|
|
29
|
+
if (props.status === 'inprogress' && props.allowLiveTeaserStyling) {
|
|
30
30
|
return 'live';
|
|
31
31
|
}
|
|
32
32
|
if (props.indicators && props.indicators.isOpinion) {
|
|
@@ -431,23 +431,23 @@ var RelativeTime = ({
|
|
|
431
431
|
}, displayTime(relativeDate))) : null;
|
|
432
432
|
};
|
|
433
433
|
|
|
434
|
-
const LiveBlogLabels = {
|
|
435
|
-
inprogress: 'Live',
|
|
436
|
-
comingsoon: 'Coming Soon',
|
|
437
|
-
closed: ''
|
|
438
|
-
};
|
|
439
434
|
const LiveBlogModifiers = {
|
|
440
435
|
inprogress: 'live',
|
|
441
436
|
comingsoon: 'pending',
|
|
442
437
|
closed: 'closed'
|
|
443
438
|
};
|
|
444
439
|
var LiveBlogStatus = ({
|
|
445
|
-
status
|
|
440
|
+
status,
|
|
441
|
+
allowLiveTeaserStyling = false
|
|
446
442
|
}) => status && status !== 'closed' ? h("div", {
|
|
447
443
|
className: `o-teaser__timestamp o-teaser__timestamp--${LiveBlogModifiers[status]}`
|
|
448
|
-
}, h("span", {
|
|
444
|
+
}, status === 'comingsoon' && h("span", {
|
|
449
445
|
className: "o-teaser__timestamp-prefix"
|
|
450
|
-
}, `
|
|
446
|
+
}, ` Coming Soon `), status === 'inprogress' && h("span", {
|
|
447
|
+
className: `o-labels-indicator o-labels-indicator--live ${allowLiveTeaserStyling ? null : 'o-labels-indicator--badge'}`
|
|
448
|
+
}, h("span", {
|
|
449
|
+
className: "o-labels-indicator__status"
|
|
450
|
+
}, ` Live `))) : null;
|
|
451
451
|
|
|
452
452
|
/**
|
|
453
453
|
* Timestamp shown always, the default 4h limit does not apply here
|
|
@@ -467,32 +467,52 @@ var AlwaysShowTimestamp = props => {
|
|
|
467
467
|
};
|
|
468
468
|
|
|
469
469
|
function PremiumLabel() {
|
|
470
|
+
return (
|
|
471
|
+
// WARNING: Do not use the x-teaser__premium-label class to override styling.
|
|
472
|
+
// The styling should be in o-teaser, not x-teaser.
|
|
473
|
+
// Use o-teaser__labels or o-teaser__labels--premium instead of x-teaser__premium-label.
|
|
474
|
+
h("div", {
|
|
475
|
+
className: "x-teaser__premium-label o-teaser__labels o-teaser__labels--premium"
|
|
476
|
+
}, h("span", {
|
|
477
|
+
className: "o-labels o-labels--premium o-labels--content-premium"
|
|
478
|
+
}, "Premium"), h("span", {
|
|
479
|
+
className: "o3-visually-hidden"
|
|
480
|
+
}, "\xA0content"))
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function ScoopLabel() {
|
|
470
485
|
return h("div", {
|
|
471
|
-
className: "
|
|
486
|
+
className: "o-teaser__labels o-teaser__labels--scoop"
|
|
472
487
|
}, h("span", {
|
|
473
|
-
className: "o-labels o-labels--
|
|
474
|
-
}, "
|
|
488
|
+
className: "o-labels o-labels--content-scoop"
|
|
489
|
+
}, "Exclusive"), h("span", {
|
|
475
490
|
className: "o3-visually-hidden"
|
|
476
491
|
}, "\xA0content"));
|
|
477
492
|
}
|
|
478
493
|
|
|
479
494
|
var Status = props => {
|
|
480
|
-
var _props$indicators;
|
|
481
|
-
if (props.
|
|
495
|
+
var _props$indicators, _props$indicators2;
|
|
496
|
+
if (props.showStatus && props.status) {
|
|
497
|
+
return h(LiveBlogStatus, props);
|
|
498
|
+
}
|
|
499
|
+
if (props.showScoopLabel && props !== null && props !== void 0 && (_props$indicators = props.indicators) !== null && _props$indicators !== void 0 && _props$indicators.isScoop &&
|
|
500
|
+
// We plan to show the Scoop label only on homepages.
|
|
501
|
+
// If we later show it on other pages, this cutoff date will need review.
|
|
502
|
+
// The `isScoop` property already exists, but Editorial will use it differently after 2025-10-01.
|
|
503
|
+
new Date(props.firstPublishedDate) >= new Date('2025-10-01T00:00:00.000Z')) {
|
|
504
|
+
return h(ScoopLabel, props);
|
|
505
|
+
}
|
|
506
|
+
if (props.showPremiumLabel && (props === null || props === void 0 || (_props$indicators2 = props.indicators) === null || _props$indicators2 === void 0 ? void 0 : _props$indicators2.accessLevel) === 'premium') {
|
|
482
507
|
return h(PremiumLabel, props);
|
|
483
508
|
}
|
|
484
|
-
if (props.showStatus) {
|
|
485
|
-
if (props.
|
|
486
|
-
return h(
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} else if (props.useRelativeTime) {
|
|
492
|
-
return h(RelativeTime, props);
|
|
493
|
-
} else {
|
|
494
|
-
return h(TimeStamp, props);
|
|
495
|
-
}
|
|
509
|
+
if (props.showStatus && props.publishedDate) {
|
|
510
|
+
if (props.useRelativeTimeIfToday) {
|
|
511
|
+
return h(AlwaysShowTimestamp, props);
|
|
512
|
+
} else if (props.useRelativeTime) {
|
|
513
|
+
return h(RelativeTime, props);
|
|
514
|
+
} else {
|
|
515
|
+
return h(TimeStamp, props);
|
|
496
516
|
}
|
|
497
517
|
}
|
|
498
518
|
return null;
|
|
@@ -598,6 +618,7 @@ const Small = {
|
|
|
598
618
|
showMeta: true,
|
|
599
619
|
showTitle: true,
|
|
600
620
|
showPremiumLabel: true,
|
|
621
|
+
showScoopLabel: false,
|
|
601
622
|
showStatus: true
|
|
602
623
|
};
|
|
603
624
|
const SmallHeavy = {
|
|
@@ -607,6 +628,7 @@ const SmallHeavy = {
|
|
|
607
628
|
showTitle: true,
|
|
608
629
|
showStandfirst: true,
|
|
609
630
|
showPremiumLabel: true,
|
|
631
|
+
showScoopLabel: false,
|
|
610
632
|
showStatus: true,
|
|
611
633
|
showImage: true,
|
|
612
634
|
imageSize: 'Small'
|
|
@@ -618,6 +640,7 @@ const Large = {
|
|
|
618
640
|
showTitle: true,
|
|
619
641
|
showStandfirst: true,
|
|
620
642
|
showPremiumLabel: true,
|
|
643
|
+
showScoopLabel: false,
|
|
621
644
|
showStatus: true,
|
|
622
645
|
showImage: true,
|
|
623
646
|
imageSize: 'Medium'
|
|
@@ -628,6 +651,7 @@ const Hero = {
|
|
|
628
651
|
showMeta: true,
|
|
629
652
|
showTitle: true,
|
|
630
653
|
showPremiumLabel: true,
|
|
654
|
+
showScoopLabel: false,
|
|
631
655
|
showStatus: true,
|
|
632
656
|
showImage: true,
|
|
633
657
|
imageSize: 'Medium'
|
|
@@ -639,6 +663,7 @@ const HeroNarrow = {
|
|
|
639
663
|
showTitle: true,
|
|
640
664
|
showStandfirst: true,
|
|
641
665
|
showPremiumLabel: true,
|
|
666
|
+
showScoopLabel: false,
|
|
642
667
|
showStatus: true
|
|
643
668
|
};
|
|
644
669
|
const HeroVideo = {
|
|
@@ -655,6 +680,7 @@ const HeroOverlay = {
|
|
|
655
680
|
showMeta: true,
|
|
656
681
|
showTitle: true,
|
|
657
682
|
showPremiumLabel: true,
|
|
683
|
+
showScoopLabel: false,
|
|
658
684
|
showStatus: true,
|
|
659
685
|
showImage: true,
|
|
660
686
|
imageSize: 'XL',
|
|
@@ -667,6 +693,7 @@ const TopStory = {
|
|
|
667
693
|
showTitle: true,
|
|
668
694
|
showStandfirst: true,
|
|
669
695
|
showPremiumLabel: true,
|
|
696
|
+
showScoopLabel: false,
|
|
670
697
|
showStatus: true,
|
|
671
698
|
showRelatedLinks: true
|
|
672
699
|
};
|
|
@@ -677,6 +704,7 @@ const TopStoryLandscape = {
|
|
|
677
704
|
showTitle: true,
|
|
678
705
|
showStandfirst: true,
|
|
679
706
|
showPremiumLabel: true,
|
|
707
|
+
showScoopLabel: false,
|
|
680
708
|
showStatus: true,
|
|
681
709
|
showImage: true,
|
|
682
710
|
imageSize: 'XL',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@financial-times/x-teaser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "18.1.0",
|
|
4
4
|
"description": "This module provides templates for use with o-teaser. Teasers are used to present content.",
|
|
5
5
|
"source": "src/Teaser.jsx",
|
|
6
6
|
"main": "dist/Teaser.cjs.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"author": "",
|
|
19
19
|
"license": "ISC",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@financial-times/x-engine": "^
|
|
21
|
+
"@financial-times/x-engine": "^18.1.0",
|
|
22
22
|
"date-fns": "^2.30.0",
|
|
23
23
|
"dateformat": "^3.0.3"
|
|
24
24
|
},
|
package/readme.md
CHANGED
|
@@ -114,6 +114,7 @@ As covered in the [features](#features) documentation the teaser properties, or
|
|
|
114
114
|
| `showTitle` | Boolean |
|
|
115
115
|
| `showStandfirst` | Boolean |
|
|
116
116
|
| `showPremiumLabel` | Boolean |
|
|
117
|
+
| `showScoopLabel` | Boolean |
|
|
117
118
|
| `showStatus` | Boolean |
|
|
118
119
|
| `showImage` | Boolean |
|
|
119
120
|
| `showHeadshot` | Boolean | Takes precedence over image |
|
|
@@ -257,6 +258,12 @@ As covered in the [features](#features) documentation the teaser properties, or
|
|
|
257
258
|
| `isExclusive` | Boolean |
|
|
258
259
|
| `isScoop` | Boolean |
|
|
259
260
|
|
|
261
|
+
#### Special Styling Props
|
|
262
|
+
|
|
263
|
+
| Property | Type | Notes |
|
|
264
|
+
| ------------------------ | ------- | ------------------------------------------ |
|
|
265
|
+
| `allowLiveTeaserStyling` | Boolean | Apply `o-teaser--live` class to Container. :memo: *Consumers need to include the o-teaser styling in their applications too.* |
|
|
266
|
+
|
|
260
267
|
### Presets
|
|
261
268
|
|
|
262
269
|
Because there are so many options presets are available for the most commonly used configurations, these are:-
|
package/src/LiveBlogStatus.jsx
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { h } from '@financial-times/x-engine'
|
|
2
2
|
|
|
3
|
-
const LiveBlogLabels = {
|
|
4
|
-
inprogress: 'Live',
|
|
5
|
-
comingsoon: 'Coming Soon',
|
|
6
|
-
closed: ''
|
|
7
|
-
}
|
|
8
|
-
|
|
9
3
|
const LiveBlogModifiers = {
|
|
10
4
|
inprogress: 'live',
|
|
11
5
|
comingsoon: 'pending',
|
|
12
6
|
closed: 'closed'
|
|
13
7
|
}
|
|
14
8
|
|
|
15
|
-
export default ({ status }) =>
|
|
9
|
+
export default ({ status, allowLiveTeaserStyling = false }) =>
|
|
16
10
|
status && status !== 'closed' ? (
|
|
17
11
|
<div className={`o-teaser__timestamp o-teaser__timestamp--${LiveBlogModifiers[status]}`}>
|
|
18
|
-
<span className="o-teaser__timestamp-prefix">{`
|
|
12
|
+
{status === 'comingsoon' && <span className="o-teaser__timestamp-prefix">{` Coming Soon `}</span>}
|
|
13
|
+
{status === 'inprogress' && (
|
|
14
|
+
<span
|
|
15
|
+
className={`o-labels-indicator o-labels-indicator--live ${
|
|
16
|
+
allowLiveTeaserStyling ? null : 'o-labels-indicator--badge'
|
|
17
|
+
}`}
|
|
18
|
+
>
|
|
19
|
+
<span className="o-labels-indicator__status">{` Live `}</span>
|
|
20
|
+
</span>
|
|
21
|
+
)}
|
|
19
22
|
</div>
|
|
20
23
|
) : null
|
package/src/PremiumLabel.jsx
CHANGED
|
@@ -2,7 +2,10 @@ import { h } from '@financial-times/x-engine'
|
|
|
2
2
|
|
|
3
3
|
export default function PremiumLabel() {
|
|
4
4
|
return (
|
|
5
|
-
|
|
5
|
+
// WARNING: Do not use the x-teaser__premium-label class to override styling.
|
|
6
|
+
// The styling should be in o-teaser, not x-teaser.
|
|
7
|
+
// Use o-teaser__labels or o-teaser__labels--premium instead of x-teaser__premium-label.
|
|
8
|
+
<div className="x-teaser__premium-label o-teaser__labels o-teaser__labels--premium">
|
|
6
9
|
<span className="o-labels o-labels--premium o-labels--content-premium">Premium</span>
|
|
7
10
|
<span className="o3-visually-hidden"> content</span>
|
|
8
11
|
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { h } from '@financial-times/x-engine'
|
|
2
|
+
|
|
3
|
+
export default function ScoopLabel() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="o-teaser__labels o-teaser__labels--scoop">
|
|
6
|
+
<span className="o-labels o-labels--content-scoop">Exclusive</span>
|
|
7
|
+
<span className="o3-visually-hidden"> content</span>
|
|
8
|
+
</div>
|
|
9
|
+
)
|
|
10
|
+
}
|
package/src/Status.jsx
CHANGED
|
@@ -4,25 +4,35 @@ import RelativeTime from './RelativeTime'
|
|
|
4
4
|
import LiveBlogStatus from './LiveBlogStatus'
|
|
5
5
|
import AlwaysShowTimestamp from './AlwaysShowTimestamp'
|
|
6
6
|
import PremiumLabel from './PremiumLabel'
|
|
7
|
+
import ScoopLabel from './ScoopLabel'
|
|
7
8
|
|
|
8
9
|
export default (props) => {
|
|
10
|
+
if (props.showStatus && props.status) {
|
|
11
|
+
return <LiveBlogStatus {...props} />
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (
|
|
15
|
+
props.showScoopLabel &&
|
|
16
|
+
props?.indicators?.isScoop &&
|
|
17
|
+
// We plan to show the Scoop label only on homepages.
|
|
18
|
+
// If we later show it on other pages, this cutoff date will need review.
|
|
19
|
+
// The `isScoop` property already exists, but Editorial will use it differently after 2025-10-01.
|
|
20
|
+
new Date(props.firstPublishedDate) >= new Date('2025-10-01T00:00:00.000Z')
|
|
21
|
+
) {
|
|
22
|
+
return <ScoopLabel {...props} />
|
|
23
|
+
}
|
|
24
|
+
|
|
9
25
|
if (props.showPremiumLabel && props?.indicators?.accessLevel === 'premium') {
|
|
10
26
|
return <PremiumLabel {...props} />
|
|
11
27
|
}
|
|
12
28
|
|
|
13
|
-
if (props.showStatus) {
|
|
14
|
-
if (props.
|
|
15
|
-
return <
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return <AlwaysShowTimestamp {...props} />
|
|
21
|
-
} else if (props.useRelativeTime) {
|
|
22
|
-
return <RelativeTime {...props} />
|
|
23
|
-
} else {
|
|
24
|
-
return <TimeStamp {...props} />
|
|
25
|
-
}
|
|
29
|
+
if (props.showStatus && props.publishedDate) {
|
|
30
|
+
if (props.useRelativeTimeIfToday) {
|
|
31
|
+
return <AlwaysShowTimestamp {...props} />
|
|
32
|
+
} else if (props.useRelativeTime) {
|
|
33
|
+
return <RelativeTime {...props} />
|
|
34
|
+
} else {
|
|
35
|
+
return <TimeStamp {...props} />
|
|
26
36
|
}
|
|
27
37
|
}
|
|
28
38
|
|
package/src/Teaser.scss
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
@import '@financial-times/o3-foundation/css/core.css';
|
|
2
2
|
|
|
3
|
+
// WARNING: Do not use the x-teaser__premium-label class to override styling.
|
|
4
|
+
// The styling should be in o-teaser, not x-teaser.
|
|
5
|
+
// Use o-teaser__labels or o-teaser__labels--premium instead of x-teaser__premium-label.
|
|
6
|
+
|
|
3
7
|
// Theses styles copy the spacing from o-teaser__timestamp
|
|
4
8
|
// as the premium label is replacing it
|
|
5
9
|
.x-teaser__premium-label {
|
package/src/concerns/presets.js
CHANGED
|
@@ -6,6 +6,7 @@ const Small = {
|
|
|
6
6
|
showMeta: true,
|
|
7
7
|
showTitle: true,
|
|
8
8
|
showPremiumLabel: true,
|
|
9
|
+
showScoopLabel: false,
|
|
9
10
|
showStatus: true
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -16,6 +17,7 @@ const SmallHeavy = {
|
|
|
16
17
|
showTitle: true,
|
|
17
18
|
showStandfirst: true,
|
|
18
19
|
showPremiumLabel: true,
|
|
20
|
+
showScoopLabel: false,
|
|
19
21
|
showStatus: true,
|
|
20
22
|
showImage: true,
|
|
21
23
|
imageSize: 'Small'
|
|
@@ -28,6 +30,7 @@ const Large = {
|
|
|
28
30
|
showTitle: true,
|
|
29
31
|
showStandfirst: true,
|
|
30
32
|
showPremiumLabel: true,
|
|
33
|
+
showScoopLabel: false,
|
|
31
34
|
showStatus: true,
|
|
32
35
|
showImage: true,
|
|
33
36
|
imageSize: 'Medium'
|
|
@@ -39,6 +42,7 @@ const Hero = {
|
|
|
39
42
|
showMeta: true,
|
|
40
43
|
showTitle: true,
|
|
41
44
|
showPremiumLabel: true,
|
|
45
|
+
showScoopLabel: false,
|
|
42
46
|
showStatus: true,
|
|
43
47
|
showImage: true,
|
|
44
48
|
imageSize: 'Medium'
|
|
@@ -51,6 +55,7 @@ const HeroNarrow = {
|
|
|
51
55
|
showTitle: true,
|
|
52
56
|
showStandfirst: true,
|
|
53
57
|
showPremiumLabel: true,
|
|
58
|
+
showScoopLabel: false,
|
|
54
59
|
showStatus: true
|
|
55
60
|
}
|
|
56
61
|
|
|
@@ -69,6 +74,7 @@ const HeroOverlay = {
|
|
|
69
74
|
showMeta: true,
|
|
70
75
|
showTitle: true,
|
|
71
76
|
showPremiumLabel: true,
|
|
77
|
+
showScoopLabel: false,
|
|
72
78
|
showStatus: true,
|
|
73
79
|
showImage: true,
|
|
74
80
|
imageSize: 'XL',
|
|
@@ -82,6 +88,7 @@ const TopStory = {
|
|
|
82
88
|
showTitle: true,
|
|
83
89
|
showStandfirst: true,
|
|
84
90
|
showPremiumLabel: true,
|
|
91
|
+
showScoopLabel: false,
|
|
85
92
|
showStatus: true,
|
|
86
93
|
showRelatedLinks: true
|
|
87
94
|
}
|
|
@@ -93,6 +100,7 @@ const TopStoryLandscape = {
|
|
|
93
100
|
showTitle: true,
|
|
94
101
|
showStandfirst: true,
|
|
95
102
|
showPremiumLabel: true,
|
|
103
|
+
showScoopLabel: false,
|
|
96
104
|
showStatus: true,
|
|
97
105
|
showImage: true,
|
|
98
106
|
imageSize: 'XL',
|
package/src/concerns/rules.js
CHANGED
package/storybook/argTypes.js
CHANGED
|
@@ -47,5 +47,9 @@ exports.argTypes = {
|
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
publishedDate: { name: 'Published Date', control: { type: 'date' } },
|
|
50
|
-
firstPublishedDate: { name: 'First Published Date', control: { type: 'date' } }
|
|
50
|
+
firstPublishedDate: { name: 'First Published Date', control: { type: 'date' } },
|
|
51
|
+
allowLiveTeaserStyling: {
|
|
52
|
+
name: 'allowLiveTeaserStyling',
|
|
53
|
+
control: 'boolean'
|
|
54
|
+
}
|
|
51
55
|
}
|
package/storybook/article.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const { presets } = require('../')
|
|
2
2
|
|
|
3
|
-
exports.args = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy
|
|
3
|
+
exports.args = Object.assign(require('../__fixtures__/article.json'), presets.SmallHeavy, {
|
|
4
|
+
allowLiveTeaserStyling: false
|
|
5
|
+
})
|
|
4
6
|
|
|
5
7
|
// This reference is only required for hot module loading in development
|
|
6
8
|
// <https://webpack.js.org/concepts/hot-module-replacement/>
|
package/storybook/index.jsx
CHANGED
|
@@ -5,9 +5,9 @@ import BuildService from '../../../.storybook/build-service'
|
|
|
5
5
|
import '../src/Teaser.scss'
|
|
6
6
|
|
|
7
7
|
const dependencies = {
|
|
8
|
-
'o-date': '^
|
|
9
|
-
'o-labels': '^7.0
|
|
10
|
-
'o-teaser': '^
|
|
8
|
+
'o-date': '^7.0.1',
|
|
9
|
+
'o-labels': '^7.1.0',
|
|
10
|
+
'o-teaser': '^9.1.0',
|
|
11
11
|
'o-video': '^8.0.0'
|
|
12
12
|
}
|
|
13
13
|
|