Skip to content

Commit f08a9ff

Browse files
authored
Merge pull request #2022 from tf/heading-subtitles
Add tagline and subtitle to heading content element
2 parents 088ed98 + 3c09c86 commit f08a9ff

File tree

9 files changed

+333
-38
lines changed

9 files changed

+333
-38
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
de:
2+
pageflow_scrolled:
3+
inline_editing:
4+
type_subtitle: "Untertitel eingeben"
5+
type_tagline: "Tagline eingeben"
6+
editor:
7+
content_elements:
8+
heading:
9+
attributes:
10+
entranceAnimation:
11+
inline_help: Lege die Animation fest, mit der die Überschrift eingeblendet werden soll, wenn sie die Mitte des Viewports erreicht.
12+
label: Eingangsanimation
13+
values:
14+
none: "(Keine)"
15+
fadeIn: Einblenden
16+
fadeInFast: Einblenden (Schnell)
17+
fadeInSlow: Einblenden (Langsam)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
en:
2+
pageflow_scrolled:
3+
inline_editing:
4+
type_subtitle: "Type subtitle"
5+
type_tagline: "Type tagline"
6+
editor:
7+
content_elements:
8+
heading:
9+
attributes:
10+
entranceAnimation:
11+
inline_help: Determine how the heading becomes visible once it reaches the center of the viewport
12+
label: Entrance Animation
13+
values:
14+
fadeIn: Fade in
15+
fadeInFast: Fade in (Fast)
16+
fadeInSlow: Fade in (Slow)
17+
none: "(None)"
Lines changed: 104 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
import React from 'react';
1+
import React, {useEffect, useState, useRef} from 'react';
22
import classNames from 'classnames';
33
import {
44
withShadowClassName,
55
paletteColor,
66
Text,
77
EditableInlineText,
88
useContentElementConfigurationUpdate,
9+
useContentElementEditorState,
10+
useContentElementLifecycle,
911
useDarkBackground,
12+
useIsStaticPreview,
1013
useI18n,
11-
contentElementWidths
14+
contentElementWidths,
15+
utils
1216
} from 'pageflow-scrolled/frontend';
1317

1418
import styles from './Heading.module.css';
@@ -19,47 +23,118 @@ export function Heading({configuration, sectionProps, contentElementWidth}) {
1923
const updateConfiguration = useContentElementConfigurationUpdate();
2024
const {t} = useI18n({locale: 'ui'});
2125
const darkBackground = useDarkBackground();
26+
const {isSelected, isEditable} = useContentElementEditorState();
2227

2328
const legacyValue = configuration.children;
2429
const Tag = firstSectionInEntry ? 'h1' : 'h2';
2530

2631
const forcePaddingTop = firstSectionInEntry && !('marginTop' in configuration);
2732

28-
return (
29-
<Tag className={classNames(styles.root,
30-
'scope-headings',
31-
configuration.typographyVariant &&
32-
`typography-heading-${configuration.typographyVariant}`,
33-
darkBackground ? styles.light : styles.dark,
34-
{[styles.forcePaddingTop]: forcePaddingTop},
35-
{[styles[sectionProps.layout]]:
36-
contentElementWidth > contentElementWidths.md ||
37-
sectionProps.layout === 'centerRagged'},
38-
{[withShadowClassName]: !sectionProps.invert})}
39-
style={{color: paletteColor(configuration.color)}}>
40-
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry)}
41-
inline={true}>
42-
<EditableInlineText value={configuration.value}
43-
defaultValue={legacyValue}
44-
hyphens={configuration.hyphens}
45-
placeholder={firstSectionInEntry ?
46-
t('pageflow_scrolled.inline_editing.type_title') :
47-
t('pageflow_scrolled.inline_editing.type_heading')}
48-
onChange={value => updateConfiguration({value})} />
33+
const entranceAnimation = (!useIsStaticPreview() && configuration.entranceAnimation) || 'none';
34+
const [animating, setAnimating] = useState(false);
35+
36+
useContentElementLifecycle({
37+
onActivate() {
38+
setAnimating(entranceAnimation !== 'none');
39+
},
40+
41+
onInvisible() {
42+
if (isEditable) {
43+
setAnimating(false);
44+
}
45+
}
46+
});
47+
48+
const previousAnimation = useRef(entranceAnimation);
49+
const previouslySelected = useRef(isSelected);
50+
51+
useEffect(() => {
52+
if (isEditable && previousAnimation.current !== entranceAnimation) {
53+
previousAnimation.current = entranceAnimation;
54+
55+
setAnimating(false)
56+
setTimeout(() => setAnimating(true), 100);
57+
}
58+
}, [entranceAnimation, isEditable]);
59+
60+
useEffect(() => {
61+
if (!previouslySelected.current && isSelected) {
62+
setAnimating(true)
63+
}
64+
65+
previouslySelected.current = isSelected;
66+
}, [isSelected]);
67+
68+
function renderSubtitle(name) {
69+
const value = configuration[name];
70+
71+
if (!isSelected && utils.isBlankEditableTextValue(value)) {
72+
return null;
73+
}
74+
75+
return (
76+
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry, name)}>
77+
<div className={styles[name]}
78+
role="doc-subtitle">
79+
<EditableInlineText value={value}
80+
hyphens={configuration.hyphens}
81+
placeholder={t(`pageflow_scrolled.inline_editing.type_${name}`)}
82+
onChange={value => updateConfiguration({[name]: value})} />
83+
</div>
4984
</Text>
50-
</Tag>
85+
);
86+
}
87+
88+
return (
89+
<header className={classNames(styles.root,
90+
styles[`animation-${entranceAnimation}`],
91+
{[styles.animating]: animating},
92+
{[styles.hasTagline]: !utils.isBlankEditableTextValue(
93+
configuration.tagline
94+
) || isSelected},
95+
{[styles.forcePaddingTop]: forcePaddingTop},
96+
{[styles[sectionProps.layout]]:
97+
contentElementWidth > contentElementWidths.md ||
98+
sectionProps.layout === 'centerRagged'},
99+
{[withShadowClassName]: !sectionProps.invert})}>
100+
{renderSubtitle('tagline')}
101+
<Tag className={classNames(styles.main,
102+
'scope-headings',
103+
configuration.typographyVariant &&
104+
`typography-heading-${configuration.typographyVariant}`,
105+
darkBackground ? styles.light : styles.dark)}
106+
style={{color: paletteColor(configuration.color)}}>
107+
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry)}
108+
inline={true}>
109+
<EditableInlineText value={configuration.value}
110+
defaultValue={legacyValue}
111+
hyphens={configuration.hyphens}
112+
placeholder={firstSectionInEntry ?
113+
t('pageflow_scrolled.inline_editing.type_title') :
114+
t('pageflow_scrolled.inline_editing.type_heading')}
115+
onChange={value => updateConfiguration({value})} />
116+
</Text>
117+
</Tag>
118+
{renderSubtitle('subtitle')}
119+
</header>
51120
);
52121
}
53122

54-
function getScaleCategory(configuration, firstSectionInEntry) {
123+
function getScaleCategory(configuration, firstSectionInEntry, suffix = '') {
124+
const base = `heading${capitalize(suffix)}`;
125+
55126
switch (configuration.textSize) {
56127
case 'large':
57-
return 'heading-lg';
128+
return `${base}-lg`;
58129
case 'medium':
59-
return 'heading-md';
130+
return `${base}-md`;
60131
case 'small':
61-
return 'heading-sm';
132+
return `${base}-sm`;
62133
default:
63-
return firstSectionInEntry ? 'heading-lg' : 'heading-sm';
134+
return firstSectionInEntry ? `${base}-lg` : `${base}-sm`;
64135
}
65136
}
137+
138+
function capitalize(string) {
139+
return string.charAt(0).toUpperCase() + string.slice(1);
140+
}

entry_types/scrolled/package/src/contentElements/heading/Heading.module.css

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,74 @@
44
) from "pageflow-scrolled/values/colors.module.css";
55

66
.root {
7-
margin-top: 0.2em;
7+
margin-top: 0.3em;
88
margin-bottom: 0;
9-
padding-top: 0.3em;
9+
padding-top: 0.45em;
10+
}
11+
12+
.animation-fadeIn {
13+
--fade-in-duration: 2s;
14+
--fade-in-delay: 0.4s;
15+
}
16+
17+
.animation-fadeInFast {
18+
composes: animation-fadeIn;
19+
--fade-in-duration: 1s;
20+
--fade-in-delay: 0.4s;
21+
}
22+
23+
.animation-fadeInSlow {
24+
composes: animation-fadeIn;
25+
--fade-in-duration: 3s;
26+
--fade-in-delay: 0.8s;
27+
}
28+
29+
.animation-fadeIn .main,
30+
.animation-fadeIn .tagline,
31+
.animation-fadeIn .subtitle {
32+
opacity: 0;
33+
}
34+
35+
.animation-fadeIn.animating .main,
36+
.animation-fadeIn.animating .tagline,
37+
.animation-fadeIn.animating .subtitle {
38+
transition: opacity var(--fade-in-duration) ease;
39+
opacity: 1;
40+
}
41+
42+
.animation-fadeIn.animating .subtitle {
43+
transition-delay: var(--fade-in-delay);
44+
}
45+
46+
.animation-fadeIn.hasTagline.animating .main {
47+
transition-delay: var(--fade-in-delay);
48+
}
49+
50+
.animation-fadeIn.hasTagline.animating .subtitle {
51+
transition-delay: calc(var(--fade-in-delay) * 2);
52+
}
53+
54+
.main {
55+
margin: 0;
56+
}
57+
58+
.tagline {
59+
margin-bottom: 0.8em;
60+
}
61+
62+
.subtitle {
63+
margin-top: 0.6em;
64+
margin-bottom: 2em;
65+
}
66+
67+
@media (max-width: 600px) {
68+
.tagline {
69+
margin-bottom: 0.4em;
70+
}
71+
72+
.subtitle {
73+
margin-top: 0.4em;
74+
}
1075
}
1176

1277
@media (min-width: 951px) {

entry_types/scrolled/package/src/contentElements/heading/editor.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ editor.contentElementTypes.register('heading', {
4040
model: modelDelegator,
4141
propertyName: 'color'
4242
});
43+
this.input('entranceAnimation', SelectInputView, {
44+
values: ['none', 'fadeInSlow', 'fadeIn', 'fadeInFast'],
45+
});
4346

4447
this.input('hyphens', SelectInputView, {
4548
values: ['auto', 'manual']

entry_types/scrolled/package/src/contentElements/heading/frontend.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ import {frontend} from 'pageflow-scrolled/frontend';
22
import {Heading} from './Heading';
33

44
frontend.contentElementTypes.register('heading', {
5-
component: Heading
5+
component: Heading,
6+
lifecycle: true
67
});

entry_types/scrolled/package/src/contentElements/heading/stories.js

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import '../frontend';
22
import {storiesOfContentElement} from 'pageflow-scrolled/spec/support/stories';
3+
import {contentElementWidths} from 'pageflow-scrolled/frontend';
34

45
storiesOfContentElement(module, {
56
typeName: 'heading',
@@ -27,11 +28,60 @@ storiesOfContentElement(module, {
2728
}
2829
},
2930
{
30-
name: 'Small',
31+
name: 'Small ',
3132
configuration: {
3233
textSize: 'small'
3334
}
3435
},
36+
{
37+
name: 'With subtitles - Large',
38+
configuration: {
39+
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
40+
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
41+
width: contentElementWidths.xl,
42+
textSize: 'large'
43+
}
44+
},
45+
{
46+
name: 'With subtitles - Medium',
47+
configuration: {
48+
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
49+
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
50+
width: contentElementWidths.xl,
51+
textSize: 'medium'
52+
}
53+
},
54+
{
55+
name: 'With subtitles - Small',
56+
configuration: {
57+
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
58+
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
59+
width: contentElementWidths.xl,
60+
textSize: 'small'
61+
}
62+
},
63+
{
64+
name: 'With subtitles - Center',
65+
sectionConfiguration: {
66+
layout: 'center',
67+
},
68+
configuration: {
69+
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
70+
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
71+
width: contentElementWidths.xl
72+
}
73+
},
74+
{
75+
name: 'With subtitles - Right',
76+
sectionConfiguration: {
77+
layout: 'right',
78+
},
79+
configuration: {
80+
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
81+
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
82+
width: contentElementWidths.xl
83+
}
84+
},
3585
{
3686
name: 'With custom content text colors',
3787
themeOptions: {

entry_types/scrolled/package/src/frontend/Text.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ import styles from './Text.module.css';
88
*
99
* @param {Object} props
1010
* @param {string} props.scaleCategory -
11-
* One of the styles `'heading-lg'`, `'heading-md'`, `'heading-sm'`,
12-
* `'heading-xs'`, `'body'`, `'caption'`, `'question'`,
13-
* `'quoteText-lg`', `'quoteText-md`', `'quoteText-sm`', `'quoteText-xs`', `'quoteAttribution`',
14-
* `'counterNumber-lg`', `'counterNumber-md`', `'counterNumber-sm`',
15-
* `'counterNumber-xs`', `'counterDescription`'.
11+
* One of the styles `'heading-lg'`, `'heading-md'`, `'heading-sm'`,`'heading-xs'`,
12+
* `'headingTagline-lg'`, `'headingTagline-md'`, `'headingTagline-sm'`,
13+
* `'headingSubtitle-lg'`, `'headingSubtitle-md'`, `'headingSubtitle-sm'`,
14+
* `'body'`, `'caption'`, `'question'`,
15+
* `'quoteText-lg'`, `'quoteText-md'`, `'quoteText-sm'`, `'quoteText-xs'`, `'quoteAttribution'`,
16+
* `'counterNumber-lg'`, `'counterNumber-md'`, `'counterNumber-sm'`,
17+
* `'counterNumber-xs'`, `'counterDescription`'.
1618
* @param {string} [props.inline] - Render a span instread of a div.
1719
* @param {string} props.children - Nodes to render with specified typography.
1820
*/
@@ -27,6 +29,8 @@ Text.propTypes = {
2729
inline: PropTypes.bool,
2830
scaleCategory: PropTypes.oneOf([
2931
'heading-lg', 'heading-md', 'heading-sm', 'heading-xs',
32+
'headingTagline-lg', 'headingTagline-md', 'headingTagline-sm',
33+
'headingSubtitle-lg', 'headingSubtitle-md', 'headingSubtitle-sm',
3034
'quoteText-lg', 'quoteText-md', 'quoteText-sm', 'quoteText-xs', 'quoteAttribution',
3135
'counterNumber-lg', 'counterNumber-md', 'counterNumber-sm', 'counterNumber-xs',
3236
'counterDescription',

0 commit comments

Comments
 (0)