Skip to content

Commit

Permalink
Merge pull request #2022 from tf/heading-subtitles
Browse files Browse the repository at this point in the history
Add tagline and subtitle to heading content element
  • Loading branch information
tf authored Nov 2, 2023
2 parents 088ed98 + 3c09c86 commit f08a9ff
Show file tree
Hide file tree
Showing 9 changed files with 333 additions and 38 deletions.
17 changes: 17 additions & 0 deletions entry_types/scrolled/config/locales/new/heading_subtitles.de.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
de:
pageflow_scrolled:
inline_editing:
type_subtitle: "Untertitel eingeben"
type_tagline: "Tagline eingeben"
editor:
content_elements:
heading:
attributes:
entranceAnimation:
inline_help: Lege die Animation fest, mit der die Überschrift eingeblendet werden soll, wenn sie die Mitte des Viewports erreicht.
label: Eingangsanimation
values:
none: "(Keine)"
fadeIn: Einblenden
fadeInFast: Einblenden (Schnell)
fadeInSlow: Einblenden (Langsam)
17 changes: 17 additions & 0 deletions entry_types/scrolled/config/locales/new/heading_subtitles.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
en:
pageflow_scrolled:
inline_editing:
type_subtitle: "Type subtitle"
type_tagline: "Type tagline"
editor:
content_elements:
heading:
attributes:
entranceAnimation:
inline_help: Determine how the heading becomes visible once it reaches the center of the viewport
label: Entrance Animation
values:
fadeIn: Fade in
fadeInFast: Fade in (Fast)
fadeInSlow: Fade in (Slow)
none: "(None)"
133 changes: 104 additions & 29 deletions entry_types/scrolled/package/src/contentElements/heading/Heading.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import React from 'react';
import React, {useEffect, useState, useRef} from 'react';
import classNames from 'classnames';
import {
withShadowClassName,
paletteColor,
Text,
EditableInlineText,
useContentElementConfigurationUpdate,
useContentElementEditorState,
useContentElementLifecycle,
useDarkBackground,
useIsStaticPreview,
useI18n,
contentElementWidths
contentElementWidths,
utils
} from 'pageflow-scrolled/frontend';

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

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

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

return (
<Tag className={classNames(styles.root,
'scope-headings',
configuration.typographyVariant &&
`typography-heading-${configuration.typographyVariant}`,
darkBackground ? styles.light : styles.dark,
{[styles.forcePaddingTop]: forcePaddingTop},
{[styles[sectionProps.layout]]:
contentElementWidth > contentElementWidths.md ||
sectionProps.layout === 'centerRagged'},
{[withShadowClassName]: !sectionProps.invert})}
style={{color: paletteColor(configuration.color)}}>
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry)}
inline={true}>
<EditableInlineText value={configuration.value}
defaultValue={legacyValue}
hyphens={configuration.hyphens}
placeholder={firstSectionInEntry ?
t('pageflow_scrolled.inline_editing.type_title') :
t('pageflow_scrolled.inline_editing.type_heading')}
onChange={value => updateConfiguration({value})} />
const entranceAnimation = (!useIsStaticPreview() && configuration.entranceAnimation) || 'none';
const [animating, setAnimating] = useState(false);

useContentElementLifecycle({
onActivate() {
setAnimating(entranceAnimation !== 'none');
},

onInvisible() {
if (isEditable) {
setAnimating(false);
}
}
});

const previousAnimation = useRef(entranceAnimation);
const previouslySelected = useRef(isSelected);

useEffect(() => {
if (isEditable && previousAnimation.current !== entranceAnimation) {
previousAnimation.current = entranceAnimation;

setAnimating(false)
setTimeout(() => setAnimating(true), 100);
}
}, [entranceAnimation, isEditable]);

useEffect(() => {
if (!previouslySelected.current && isSelected) {
setAnimating(true)
}

previouslySelected.current = isSelected;
}, [isSelected]);

function renderSubtitle(name) {
const value = configuration[name];

if (!isSelected && utils.isBlankEditableTextValue(value)) {
return null;
}

return (
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry, name)}>
<div className={styles[name]}
role="doc-subtitle">
<EditableInlineText value={value}
hyphens={configuration.hyphens}
placeholder={t(`pageflow_scrolled.inline_editing.type_${name}`)}
onChange={value => updateConfiguration({[name]: value})} />
</div>
</Text>
</Tag>
);
}

return (
<header className={classNames(styles.root,
styles[`animation-${entranceAnimation}`],
{[styles.animating]: animating},
{[styles.hasTagline]: !utils.isBlankEditableTextValue(
configuration.tagline
) || isSelected},
{[styles.forcePaddingTop]: forcePaddingTop},
{[styles[sectionProps.layout]]:
contentElementWidth > contentElementWidths.md ||
sectionProps.layout === 'centerRagged'},
{[withShadowClassName]: !sectionProps.invert})}>
{renderSubtitle('tagline')}
<Tag className={classNames(styles.main,
'scope-headings',
configuration.typographyVariant &&
`typography-heading-${configuration.typographyVariant}`,
darkBackground ? styles.light : styles.dark)}
style={{color: paletteColor(configuration.color)}}>
<Text scaleCategory={getScaleCategory(configuration, firstSectionInEntry)}
inline={true}>
<EditableInlineText value={configuration.value}
defaultValue={legacyValue}
hyphens={configuration.hyphens}
placeholder={firstSectionInEntry ?
t('pageflow_scrolled.inline_editing.type_title') :
t('pageflow_scrolled.inline_editing.type_heading')}
onChange={value => updateConfiguration({value})} />
</Text>
</Tag>
{renderSubtitle('subtitle')}
</header>
);
}

function getScaleCategory(configuration, firstSectionInEntry) {
function getScaleCategory(configuration, firstSectionInEntry, suffix = '') {
const base = `heading${capitalize(suffix)}`;

switch (configuration.textSize) {
case 'large':
return 'heading-lg';
return `${base}-lg`;
case 'medium':
return 'heading-md';
return `${base}-md`;
case 'small':
return 'heading-sm';
return `${base}-sm`;
default:
return firstSectionInEntry ? 'heading-lg' : 'heading-sm';
return firstSectionInEntry ? `${base}-lg` : `${base}-sm`;
}
}

function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,74 @@
) from "pageflow-scrolled/values/colors.module.css";

.root {
margin-top: 0.2em;
margin-top: 0.3em;
margin-bottom: 0;
padding-top: 0.3em;
padding-top: 0.45em;
}

.animation-fadeIn {
--fade-in-duration: 2s;
--fade-in-delay: 0.4s;
}

.animation-fadeInFast {
composes: animation-fadeIn;
--fade-in-duration: 1s;
--fade-in-delay: 0.4s;
}

.animation-fadeInSlow {
composes: animation-fadeIn;
--fade-in-duration: 3s;
--fade-in-delay: 0.8s;
}

.animation-fadeIn .main,
.animation-fadeIn .tagline,
.animation-fadeIn .subtitle {
opacity: 0;
}

.animation-fadeIn.animating .main,
.animation-fadeIn.animating .tagline,
.animation-fadeIn.animating .subtitle {
transition: opacity var(--fade-in-duration) ease;
opacity: 1;
}

.animation-fadeIn.animating .subtitle {
transition-delay: var(--fade-in-delay);
}

.animation-fadeIn.hasTagline.animating .main {
transition-delay: var(--fade-in-delay);
}

.animation-fadeIn.hasTagline.animating .subtitle {
transition-delay: calc(var(--fade-in-delay) * 2);
}

.main {
margin: 0;
}

.tagline {
margin-bottom: 0.8em;
}

.subtitle {
margin-top: 0.6em;
margin-bottom: 2em;
}

@media (max-width: 600px) {
.tagline {
margin-bottom: 0.4em;
}

.subtitle {
margin-top: 0.4em;
}
}

@media (min-width: 951px) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ editor.contentElementTypes.register('heading', {
model: modelDelegator,
propertyName: 'color'
});
this.input('entranceAnimation', SelectInputView, {
values: ['none', 'fadeInSlow', 'fadeIn', 'fadeInFast'],
});

this.input('hyphens', SelectInputView, {
values: ['auto', 'manual']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import {frontend} from 'pageflow-scrolled/frontend';
import {Heading} from './Heading';

frontend.contentElementTypes.register('heading', {
component: Heading
component: Heading,
lifecycle: true
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '../frontend';
import {storiesOfContentElement} from 'pageflow-scrolled/spec/support/stories';
import {contentElementWidths} from 'pageflow-scrolled/frontend';

storiesOfContentElement(module, {
typeName: 'heading',
Expand Down Expand Up @@ -27,11 +28,60 @@ storiesOfContentElement(module, {
}
},
{
name: 'Small',
name: 'Small ',
configuration: {
textSize: 'small'
}
},
{
name: 'With subtitles - Large',
configuration: {
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
width: contentElementWidths.xl,
textSize: 'large'
}
},
{
name: 'With subtitles - Medium',
configuration: {
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
width: contentElementWidths.xl,
textSize: 'medium'
}
},
{
name: 'With subtitles - Small',
configuration: {
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
width: contentElementWidths.xl,
textSize: 'small'
}
},
{
name: 'With subtitles - Center',
sectionConfiguration: {
layout: 'center',
},
configuration: {
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
width: contentElementWidths.xl
}
},
{
name: 'With subtitles - Right',
sectionConfiguration: {
layout: 'right',
},
configuration: {
tagline: [{type: 'heading', children: [{text: 'Some Tagline'}]}],
subtitle: [{type: 'heading', children: [{text: 'Some Subtitle'}]}],
width: contentElementWidths.xl
}
},
{
name: 'With custom content text colors',
themeOptions: {
Expand Down
14 changes: 9 additions & 5 deletions entry_types/scrolled/package/src/frontend/Text.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import styles from './Text.module.css';
*
* @param {Object} props
* @param {string} props.scaleCategory -
* One of the styles `'heading-lg'`, `'heading-md'`, `'heading-sm'`,
* `'heading-xs'`, `'body'`, `'caption'`, `'question'`,
* `'quoteText-lg`', `'quoteText-md`', `'quoteText-sm`', `'quoteText-xs`', `'quoteAttribution`',
* `'counterNumber-lg`', `'counterNumber-md`', `'counterNumber-sm`',
* `'counterNumber-xs`', `'counterDescription`'.
* One of the styles `'heading-lg'`, `'heading-md'`, `'heading-sm'`,`'heading-xs'`,
* `'headingTagline-lg'`, `'headingTagline-md'`, `'headingTagline-sm'`,
* `'headingSubtitle-lg'`, `'headingSubtitle-md'`, `'headingSubtitle-sm'`,
* `'body'`, `'caption'`, `'question'`,
* `'quoteText-lg'`, `'quoteText-md'`, `'quoteText-sm'`, `'quoteText-xs'`, `'quoteAttribution'`,
* `'counterNumber-lg'`, `'counterNumber-md'`, `'counterNumber-sm'`,
* `'counterNumber-xs'`, `'counterDescription`'.
* @param {string} [props.inline] - Render a span instread of a div.
* @param {string} props.children - Nodes to render with specified typography.
*/
Expand All @@ -27,6 +29,8 @@ Text.propTypes = {
inline: PropTypes.bool,
scaleCategory: PropTypes.oneOf([
'heading-lg', 'heading-md', 'heading-sm', 'heading-xs',
'headingTagline-lg', 'headingTagline-md', 'headingTagline-sm',
'headingSubtitle-lg', 'headingSubtitle-md', 'headingSubtitle-sm',
'quoteText-lg', 'quoteText-md', 'quoteText-sm', 'quoteText-xs', 'quoteAttribution',
'counterNumber-lg', 'counterNumber-md', 'counterNumber-sm', 'counterNumber-xs',
'counterDescription',
Expand Down
Loading

0 comments on commit f08a9ff

Please sign in to comment.