diff --git a/entry_types/scrolled/config/locales/new/scroll_indicator.de.yml b/entry_types/scrolled/config/locales/new/scroll_indicator.de.yml
new file mode 100644
index 0000000000..07c80670db
--- /dev/null
+++ b/entry_types/scrolled/config/locales/new/scroll_indicator.de.yml
@@ -0,0 +1,33 @@
+de:
+ pageflow:
+ icon_scroll_indicator:
+ feature_name: Icon Scroll-Indikator
+ iconScrollIndicator:
+ widget_type_name: Icon unten am Viewport-Rand
+ editor:
+ widgets:
+ attributes:
+ iconScrollIndicator:
+ animation:
+ label: Animation
+ values:
+ none: Keine
+ smallBounce: Subtil
+ largeBounce: Deutlich
+ alignment:
+ label: Ausrichtung
+ values:
+ centerContent: Zentriert zum Inhalt
+ centerViewport: Zentriert zum Viewport
+ size:
+ label: Größe
+ values:
+ large: Groß
+ small: Klein
+ ui:
+ configuration_editor:
+ tabs:
+ iconScrollIndicator: Icon Scroll-Indikator
+ widgets:
+ roles:
+ scrollIndicator: Scroll-Indikator
diff --git a/entry_types/scrolled/config/locales/new/scroll_indicator.en.yml b/entry_types/scrolled/config/locales/new/scroll_indicator.en.yml
new file mode 100644
index 0000000000..c69adabb6f
--- /dev/null
+++ b/entry_types/scrolled/config/locales/new/scroll_indicator.en.yml
@@ -0,0 +1,33 @@
+en:
+ pageflow:
+ icon_scroll_indicator:
+ feature_name: Icon Scroll Indicator
+ iconScrollIndicator:
+ widget_type_name: Icon at bottom of viewport
+ editor:
+ widgets:
+ attributes:
+ iconScrollIndicator:
+ animation:
+ label: Animation
+ values:
+ none: None
+ smallBounce: Subtle
+ largeBounce: Obvious
+ alignment:
+ label: Alignment
+ values:
+ centerContent: Center with content
+ centerViewport: Center with viewport
+ size:
+ label: Size
+ values:
+ large: Large
+ small: Small
+ ui:
+ configuration_editor:
+ tabs:
+ iconScrollIndicator: Icon Scroll Indicator
+ widgets:
+ roles:
+ scrollIndicator: Scroll indicator
diff --git a/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb b/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
index 908cd0a20d..1b7d82c997 100644
--- a/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
+++ b/entry_types/scrolled/lib/pageflow_scrolled/plugin.rb
@@ -34,6 +34,14 @@ def configure(config)
c.widget_types.register(ReactWidgetType.new(name: 'textInlineFileRights',
role: 'inlineFileRights'))
+ c.features.register('icon_scroll_indicator') do |feature_config|
+ feature_config.widget_types.register(
+ ReactWidgetType.new(name: 'iconScrollIndicator',
+ role: 'scrollIndicator'),
+ default: true
+ )
+ end
+
c.features.register('datawrapper_chart_embed_opt_in')
c.features.enable_by_default('datawrapper_chart_embed_opt_in')
c.features.register('iframe_embed_content_element')
diff --git a/entry_types/scrolled/package/config/webpack.js b/entry_types/scrolled/package/config/webpack.js
index 97bfdd44fd..1194f74588 100644
--- a/entry_types/scrolled/package/config/webpack.js
+++ b/entry_types/scrolled/package/config/webpack.js
@@ -48,6 +48,12 @@ module.exports = {
'pageflow-scrolled/widgets/textInlineFileRights',
'pageflow-scrolled/widgets/textInlineFileRights.css'
]
+ },
+ 'pageflow-scrolled/widgets/iconScrollIndicator': {
+ import: [
+ 'pageflow-scrolled/widgets/iconScrollIndicator',
+ 'pageflow-scrolled/widgets/iconScrollIndicator.css'
+ ]
}
}
};
diff --git a/entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js b/entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js
index b42ab81bb5..39067b9cc3 100644
--- a/entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js
+++ b/entry_types/scrolled/package/spec/editor/controllers/PreviewMessageController-spec.js
@@ -208,6 +208,32 @@ describe('PreviewMessageController', () => {
})).resolves.toMatchObject({type: 'SELECT', payload: {id: 1, type: 'sectionTransition'}});
});
+ it('sends SELECT message to iframe on selectWidget event on model', async () => {
+ const entry = factories.entry(ScrolledEntry, {}, {
+ widgetTypes: factories.widgetTypes([{
+ role: 'header', name: 'someNavigation'
+ }]),
+ widgetsAttributes: [{
+ type_name: 'someNavigation',
+ role: 'header'
+ }],
+ entryTypeSeed: normalizeSeed()
+ });
+ const iframeWindow = createIframeWindow();
+ controller = new PreviewMessageController({entry, iframeWindow});
+
+ await postReadyMessageAndWaitForAcknowledgement(iframeWindow);
+
+ return expect(new Promise(resolve => {
+ iframeWindow.addEventListener('message', event => {
+ if (event.data.type === 'SELECT') {
+ resolve(event.data);
+ }
+ });
+ entry.trigger('selectWidget', entry.widgets.first());
+ })).resolves.toMatchObject({type: 'SELECT', payload: {id: 'header', type: 'widget'}});
+ });
+
it('supports sending CONTENT_ELEMENT_EDITOR_COMMAND message to iframe', async () => {
const entry = factories.entry(ScrolledEntry, {}, {
entryTypeSeed: normalizeSeed({
@@ -298,6 +324,18 @@ describe('PreviewMessageController', () => {
})).resolves.toBe('/scrolled/sections/1/transition');
});
+ it('navigates to edit widget route on SELECTED message for widget role', () => {
+ const editor = factories.editorApi();
+ const entry = factories.entry(ScrolledEntry);
+ const iframeWindow = createIframeWindow();
+ controller = new PreviewMessageController({entry, iframeWindow, editor});
+
+ return expect(new Promise(resolve => {
+ editor.on('navigate', resolve);
+ window.postMessage({type: 'SELECTED', payload: {id: 'header', type: 'widget'}}, '*');
+ })).resolves.toBe('/widgets/header');
+ });
+
it('displays insert dialog on INSERT_CONTENT_ELEMENT message', () => {
const editor = factories.editorApi();
const entry = factories.entry(ScrolledEntry, {}, {
diff --git a/entry_types/scrolled/package/spec/frontend/features/scrollIndicatorWidget-spec.js b/entry_types/scrolled/package/spec/frontend/features/scrollIndicatorWidget-spec.js
new file mode 100644
index 0000000000..7af7b16077
--- /dev/null
+++ b/entry_types/scrolled/package/spec/frontend/features/scrollIndicatorWidget-spec.js
@@ -0,0 +1,31 @@
+import React from 'react';
+
+import {renderEntry, usePageObjects} from 'support/pageObjects';
+
+import {frontend} from 'frontend';
+
+describe('scroll indicator widget', () => {
+ usePageObjects();
+
+ it('is rendered once', () => {
+ frontend.widgetTypes.register('testScrollIndicator', {
+ component: function ({children}) {
+ return (
+
Scroll Indicator
+ )
+ }
+ });
+
+ const {getByTestId} = renderEntry({
+ seed: {
+ sections: [{id: 5}, {id: 6}],
+ widgets: [{
+ typeName: 'testScrollIndicator',
+ role:'scrollIndicator'
+ }]
+ }
+ });
+
+ expect(getByTestId('scrollIndicator')).not.toBeNull();
+ });
+});
diff --git a/entry_types/scrolled/package/spec/frontend/features/selectedMessage-spec.js b/entry_types/scrolled/package/spec/frontend/features/selectedMessage-spec.js
index 3dda645ac8..8199389f2f 100644
--- a/entry_types/scrolled/package/spec/frontend/features/selectedMessage-spec.js
+++ b/entry_types/scrolled/package/spec/frontend/features/selectedMessage-spec.js
@@ -1,4 +1,5 @@
-import {frontend} from 'frontend';
+import React from 'react';
+import {frontend, WidgetSelectionRect} from 'frontend';
import {useInlineEditingPageObjects, renderEntry} from 'support/pageObjects';
import {fakeParentWindow} from 'support';
@@ -121,4 +122,31 @@ describe('SELECTED message', () => {
payload: {id: 2, type: 'sectionTransition'}
}, expect.anything());
});
+
+ it('is posted when widget selection rect is clicked', () => {
+ frontend.widgetTypes.register('customNavigation', {
+ component: function ({children}) {
+ return (
+
+
+ Custom navigation
+
+
+ )
+ }
+ });
+
+ const {getByText} = renderEntry({
+ seed: {
+ widgets: [{typeName: 'customNavigation', role: 'header'}]
+ }
+ });
+
+ fireEvent.click(getByText('Custom navigation'));
+
+ expect(window.parent.postMessage).toHaveBeenCalledWith({
+ type: 'SELECTED',
+ payload: {id: 'header', type: 'widget'}
+ }, expect.anything());
+ });
});
diff --git a/entry_types/scrolled/package/src/editor/config.js b/entry_types/scrolled/package/src/editor/config.js
index 96956574c3..447ed3a1c3 100644
--- a/entry_types/scrolled/package/src/editor/config.js
+++ b/entry_types/scrolled/package/src/editor/config.js
@@ -10,7 +10,13 @@ import {SideBarRouter} from './routers/SideBarRouter';
import {SideBarController} from './controllers/SideBarController';
import {browser} from 'pageflow/frontend';
-import {CheckBoxInputView, ConfigurationEditorView} from 'pageflow/ui';
+
+import {
+ CheckBoxInputView,
+ ConfigurationEditorView,
+ SelectInputView
+} from 'pageflow/ui';
+
import {ColorSelectInputView} from './views/inputs/ColorSelectInputView';
import {BrowserNotSupportedView} from './views/BrowserNotSupportedView';
@@ -52,6 +58,10 @@ editor.widgetTypes.registerRole('header', {
isOptional: true
});
+editor.widgetTypes.registerRole('scrollIndicator', {
+ isOptional: true
+});
+
editor.widgetTypes.register('defaultNavigation', {
configurationEditorView: ConfigurationEditorView.extend({
configure: function() {
@@ -92,3 +102,30 @@ editor.widgetTypes.register('textInlineFileRights', {
}
}
});
+
+editor.widgetTypes.register('iconScrollIndicator', {
+ configurationEditorView: ConfigurationEditorView.extend({
+ configure: function() {
+ const firstSection = this.options.entry.sections.first();
+
+ if (firstSection) {
+ this.options.entry.trigger('scrollToSection', firstSection);
+ }
+
+ this.tab('iconScrollIndicator', function() {
+ this.input('alignment', SelectInputView, {
+ values: ['centerContent', 'centerViewport']
+ });
+ this.input('size', SelectInputView, {
+ defaultValue: 'small',
+ values: ['large', 'small']
+ });
+ this.input('animation', SelectInputView, {
+ defaultValue: 'smallBounce',
+ values: ['none', 'smallBounce', 'largeBounce']
+ });
+
+ });
+ }
+ })
+});
diff --git a/entry_types/scrolled/package/src/editor/controllers/PreviewMessageController.js b/entry_types/scrolled/package/src/editor/controllers/PreviewMessageController.js
index ac02181599..b6b72f9901 100644
--- a/entry_types/scrolled/package/src/editor/controllers/PreviewMessageController.js
+++ b/entry_types/scrolled/package/src/editor/controllers/PreviewMessageController.js
@@ -91,6 +91,16 @@ export const PreviewMessageController = Object.extend({
})
});
+ this.listenTo(this.entry, 'selectWidget', widget => {
+ postMessage({
+ type: 'SELECT',
+ payload: {
+ id: widget.get('role'),
+ type: 'widget'
+ }
+ })
+ });
+
this.listenTo(this.entry, 'resetSelection', contentElement =>
postMessage({
type: 'SELECT',
@@ -123,6 +133,9 @@ export const PreviewMessageController = Object.extend({
else if (type === 'sectionTransition') {
this.editor.navigate(`/scrolled/sections/${id}/transition`, {trigger: true})
}
+ else if (type === 'widget') {
+ this.editor.navigate(`/widgets/${id}`, {trigger: true})
+ }
else {
this.editor.navigate('/', {trigger: true})
}
diff --git a/entry_types/scrolled/package/src/frontend/Entry.js b/entry_types/scrolled/package/src/frontend/Entry.js
index 9827076af7..10212a5d67 100644
--- a/entry_types/scrolled/package/src/frontend/Entry.js
+++ b/entry_types/scrolled/package/src/frontend/Entry.js
@@ -2,14 +2,17 @@ import React from 'react';
import {Content} from './Content';
import {Widget} from './Widget';
+import {SelectableWidget} from './SelectableWidget';
-export function Entry() {
+import {withInlineEditingDecorator} from './inlineEditing';
+
+export const Entry = withInlineEditingDecorator('EntryDecorator', function Entry() {
return (
<>
-
+
>
- )
-}
+ );
+});
diff --git a/entry_types/scrolled/package/src/frontend/Section.js b/entry_types/scrolled/package/src/frontend/Section.js
index 890d4504d3..75d0f02771 100644
--- a/entry_types/scrolled/package/src/frontend/Section.js
+++ b/entry_types/scrolled/package/src/frontend/Section.js
@@ -8,6 +8,7 @@ import {
useAdditionalSeedData,
useFileWithInlineRights
} from '../entryState';
+
import Foreground from './Foreground';
import {SectionInlineFileRights} from './SectionInlineFileRights';
import {Layout, widths as contentElementWidths} from './layouts';
@@ -17,6 +18,8 @@ import {SectionLifecycleProvider, useSectionLifecycle} from './useSectionLifecyc
import {SectionViewTimelineProvider} from './SectionViewTimelineProvider';
import {withInlineEditingDecorator} from './inlineEditing';
import {BackgroundColorProvider} from './backgroundColor';
+import {SelectableWidget} from './SelectableWidget';
+
import * as v1 from './v1';
import * as v2 from './v2';
@@ -75,6 +78,10 @@ const Section = withInlineEditingDecorator('SectionDecorator', function Section(
backdrop={backdrop}
atmoAudioFile={atmoAudioFile}
state={state} />
+
+ {section.sectionIndex === 0 &&
+ }
diff --git a/entry_types/scrolled/package/src/frontend/SelectableWidget.js b/entry_types/scrolled/package/src/frontend/SelectableWidget.js
new file mode 100644
index 0000000000..b7a1989828
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/SelectableWidget.js
@@ -0,0 +1,8 @@
+import {withInlineEditingDecorator} from './inlineEditing';
+
+import {Widget} from './Widget'
+
+export const SelectableWidget = withInlineEditingDecorator(
+ 'SelectableWidgetDecorator',
+ Widget
+);
diff --git a/entry_types/scrolled/package/src/frontend/ThemeIcon.js b/entry_types/scrolled/package/src/frontend/ThemeIcon.js
index 01dff10d71..697a7e93a1 100644
--- a/entry_types/scrolled/package/src/frontend/ThemeIcon.js
+++ b/entry_types/scrolled/package/src/frontend/ThemeIcon.js
@@ -20,6 +20,7 @@ import whatsApp from './icons/social/whatsApp.svg';
import arrowLeft from './icons/arrowLeft.svg';
import arrowRight from './icons/arrowRight.svg';
+import scrollDown from './icons/scrollDown.svg';
import enterFullscreen from './icons/enterFullscreen.svg';
import exitFullscreen from './icons/exitFullscreen.svg';
@@ -51,6 +52,7 @@ const icons = {
arrowLeft,
arrowRight,
+ scrollDown,
enterFullscreen,
exitFullscreen,
@@ -68,7 +70,7 @@ const icons = {
* enterFullscreen, exitFullscreen, expand, facebook, gear, information,
* linkedIn, menu, muted, pause, play, share, telegram,
* textTracks, twitter, unmuted, world, whatsApp,
- * arrowLeft, arrowRight, world
+ * arrowLeft, arrowRight, scrollDown, world
* @params {number} [props.width] - Image width.
* @params {number} [props.height] - Image height.
*/
diff --git a/entry_types/scrolled/package/src/frontend/WidgetSelectionRect.js b/entry_types/scrolled/package/src/frontend/WidgetSelectionRect.js
new file mode 100644
index 0000000000..9f29bc0cac
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/WidgetSelectionRect.js
@@ -0,0 +1,8 @@
+import {withInlineEditingAlternative} from './inlineEditing';
+
+export const WidgetSelectionRect = withInlineEditingAlternative(
+ 'WidgetSelectionRect',
+ function WidgetSelectionRect({children}) {
+ return children;
+ }
+);
diff --git a/entry_types/scrolled/package/src/frontend/icons/scrollDown.svg b/entry_types/scrolled/package/src/frontend/icons/scrollDown.svg
new file mode 100644
index 0000000000..6e2ef97489
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/icons/scrollDown.svg
@@ -0,0 +1,4 @@
+
diff --git a/entry_types/scrolled/package/src/frontend/index.js b/entry_types/scrolled/package/src/frontend/index.js
index 6ce57e8f86..7c4d5f3c45 100644
--- a/entry_types/scrolled/package/src/frontend/index.js
+++ b/entry_types/scrolled/package/src/frontend/index.js
@@ -120,6 +120,8 @@ export {getTransitionNames, getAvailableTransitionNames} from './transitions';
export {RootProviders, registerConsentVendors};
export {default as registerTemplateWidgetType} from './registerTemplateWidgetType';
export {Widget} from './Widget';
+export {SelectableWidget} from './SelectableWidget';
+export {WidgetSelectionRect} from './WidgetSelectionRect';
export {utils} from './utils';
export {paletteColor} from './paletteColor';
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/ContentDecorator.js b/entry_types/scrolled/package/src/frontend/inlineEditing/ContentDecorator.js
index d477dc9f61..ee9c51e3f9 100644
--- a/entry_types/scrolled/package/src/frontend/inlineEditing/ContentDecorator.js
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/ContentDecorator.js
@@ -4,7 +4,7 @@ import {HTML5Backend} from 'react-dnd-html5-backend';
import {useEntryStateDispatch} from '../../entryState';
import {usePostMessageListener} from '../usePostMessageListener';
-import {EditorStateProvider, useEditorSelection} from './EditorState';
+import {useEditorSelection} from './EditorState';
import {
useContentElementEditorCommandEmitter,
ContentElementEditorCommandSubscriptionProvider
@@ -15,7 +15,7 @@ export function ContentDecorator(props) {
const contentElementEditorCommandEmitter = useContentElementEditorCommandEmitter();
return (
-
+ <>
@@ -23,7 +23,7 @@ export function ContentDecorator(props) {
{props.children}
-
+ >
);
}
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/EntryDecorator.js b/entry_types/scrolled/package/src/frontend/inlineEditing/EntryDecorator.js
new file mode 100644
index 0000000000..2732013ff1
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/EntryDecorator.js
@@ -0,0 +1,11 @@
+import React from 'react';
+
+import {EditorStateProvider} from './EditorState';
+
+export function EntryDecorator({children}) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/SectionDecorator.js b/entry_types/scrolled/package/src/frontend/inlineEditing/SectionDecorator.js
index 3cfdce6d7a..969eafd483 100644
--- a/entry_types/scrolled/package/src/frontend/inlineEditing/SectionDecorator.js
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/SectionDecorator.js
@@ -3,6 +3,7 @@ import classNames from 'classnames';
import styles from './SectionDecorator.module.css';
import backdropStyles from './BackdropDecorator.module.css';
import contentElementStyles from './ContentElementDecorator.module.css';
+import widgetSelectionRectStyles from './WidgetSelectionRect.module.css';
import {Toolbar} from './Toolbar';
import {ForcePaddingContext} from '../Foreground';
@@ -50,6 +51,7 @@ export function SectionDecorator({backdrop, section, contentElements, transition
function selectIfOutsideContentItem(event) {
if (!event.target.closest(`.${contentElementStyles.wrapper}`) &&
!event.target.closest(`.${backdropStyles.wrapper}`) &&
+ !event.target.closest(`.${widgetSelectionRectStyles.wrapper}`) &&
!event.target.closest('#fullscreenRoot') &&
!event.target.closest('[data-floating-ui-portal]')) {
isSelected ? resetSelection() : select();
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/SelectableWidgetDecorator.js b/entry_types/scrolled/package/src/frontend/inlineEditing/SelectableWidgetDecorator.js
new file mode 100644
index 0000000000..44d79efaf0
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/SelectableWidgetDecorator.js
@@ -0,0 +1,26 @@
+import React, {createContext, useContext, useMemo} from 'react';
+
+import {useEditorSelection} from './EditorState';
+
+const WidgetEditorStateContext = createContext();
+
+export function useWidgetEditorState() {
+ return useContext(WidgetEditorStateContext);
+}
+
+export function SelectableWidgetDecorator({role, props, children}) {
+ const {isSelected, select} = useEditorSelection(
+ useMemo(() => ({id: role, type: 'widget'}), [role])
+ );
+
+ const value = useMemo(() => ({
+ isSelected,
+ select
+ }), [isSelected, select]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.js b/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.js
new file mode 100644
index 0000000000..ba4b94411c
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import classNames from 'classnames';
+
+import {useWidgetEditorState} from './SelectableWidgetDecorator';
+
+import styles from './WidgetSelectionRect.module.css';
+
+export function WidgetSelectionRect({children}) {
+ const {select, isSelected} = useWidgetEditorState();
+
+ return (
+ select()}>
+ {children}
+
+ );
+}
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.module.css b/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.module.css
new file mode 100644
index 0000000000..5bb9baf924
--- /dev/null
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/WidgetSelectionRect.module.css
@@ -0,0 +1,9 @@
+.wrapper {
+ pointer-events: auto;
+ cursor: default;
+ outline-offset: 2px;
+}
+
+.selected {
+ outline: solid 1px #fff;
+}
diff --git a/entry_types/scrolled/package/src/frontend/inlineEditing/components.js b/entry_types/scrolled/package/src/frontend/inlineEditing/components.js
index ba09d4908b..a95e348159 100644
--- a/entry_types/scrolled/package/src/frontend/inlineEditing/components.js
+++ b/entry_types/scrolled/package/src/frontend/inlineEditing/components.js
@@ -1,3 +1,4 @@
+export {EntryDecorator} from './EntryDecorator';
export {ContentDecorator} from './ContentDecorator';
export {SectionDecorator} from './SectionDecorator';
export {ContentElementDecorator} from './ContentElementDecorator';
@@ -9,6 +10,9 @@ export {EditableInlineText} from './EditableInlineText';
export {EditableTable} from './EditableTable';
export {EditableLink} from './EditableLink';
+export {SelectableWidgetDecorator} from './SelectableWidgetDecorator';
+export {WidgetSelectionRect} from './WidgetSelectionRect';
+
export {ActionButton} from './ActionButton';
export {PhonePlatformProvider} from './PhonePlatformProvider';
diff --git a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js
index fcdbf934a6..a3bda8f267 100644
--- a/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js
+++ b/entry_types/scrolled/package/src/widgets/defaultNavigation/DefaultNavigation.js
@@ -2,7 +2,8 @@ import React, {useState, useCallback} from 'react';
import classNames from 'classnames';
import {
- Widget,
+ SelectableWidget,
+ WidgetSelectionRect,
paletteColor,
useScrollPosition,
useChapters,
@@ -135,34 +136,36 @@ export function DefaultNavigation({
),
[styles.hasChapters]: hasChapters
})} style={{'--theme-accent-color': paletteColor(configuration.accentColor)}}>
-
- {(hasChapters || MobileMenu) &&
}
-
-
-
-
- {renderNav()}
- {MobileMenu && setMobileNavHidden(true)} />}
-
-
- {!configuration.hideToggleMuteButton &&
}
-
- {!theme.options.hideLegalInfoButton &&
}
- {!hideSharingButton &&
}
- {ExtraButtons &&
}
+
+
+ {(hasChapters || MobileMenu) &&
}
+
+
+
+
+ {renderNav()}
+ {MobileMenu && setMobileNavHidden(true)} />}
+
+
+ {!configuration.hideToggleMuteButton && }
+
+ {!theme.options.hideLegalInfoButton &&}
+ {!hideSharingButton && }
+ {ExtraButtons && }
+
-
-
-
-
+
+
+
+
-
+
>
);
}
diff --git a/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.js b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.js
new file mode 100644
index 0000000000..b4a79a927d
--- /dev/null
+++ b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.js
@@ -0,0 +1,58 @@
+import React, {useEffect, useRef} from 'react';
+import classNames from 'classnames';
+
+import {
+ ThemeIcon,
+ WidgetSelectionRect
+} from 'pageflow-scrolled/frontend';
+
+import styles from './IconScrollIndicator.module.css';
+
+export function IconScrollIndicator({configuration, sectionLayout}) {
+ const ref = useRef();
+
+ useEffect(() => {
+ const animation = ref.current.animate(
+ {
+ opacity: ['100%', '0%'],
+ visibility: ['visible', 'hidden']
+ },
+ {
+ fill: 'forwards',
+ timeline: new window.ViewTimeline({
+ subject: ref.current.closest('section')
+ }),
+ rangeStart: 'exit-crossing 0%',
+ rangeEnd: 'exit-crossing 100px'
+ }
+ );
+
+ return () => animation.cancel();
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
+
+function getAlignment(configuration, sectionLayout) {
+ if (configuration.alignment === 'centerViewport' ||
+ sectionLayout === 'center' ||
+ sectionLayout === 'centerRagged') {
+ return 'center';
+ }
+ else if (sectionLayout === 'right') {
+ return 'right';
+ }
+ else {
+ return 'left';
+ }
+}
diff --git a/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.module.css b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.module.css
new file mode 100644
index 0000000000..19f62b1da4
--- /dev/null
+++ b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/IconScrollIndicator.module.css
@@ -0,0 +1,62 @@
+.indicator {
+ --size: 40px;
+ --animation-dist: -5px;
+
+ position: fixed;
+ bottom: 1.3rem;
+ z-index: 10;
+ filter: drop-shadow(0 0 3px #000);
+ display: flex;
+ justify-content: center;
+ pointer-events: none;
+}
+
+.indicator > * {
+ display: inline-block;
+}
+
+.size-large {
+ --size: 60px;
+}
+
+.align-center {
+ left: 0;
+ width: 100%;
+}
+
+.align-left,
+.align-right {
+ width: 84%;
+ max-width: var(--two-column-inline-content-max-width, 500px);
+}
+
+.align-right {
+ right: 8%;
+}
+
+.align-left {
+ left: 8%;
+}
+
+.indicator svg {
+ display: block;
+ fill: #fff;
+ width: var(--size);
+ height: var(--size);
+ opacity: 0.9;
+}
+
+.animation-smallBounce,
+.animation-largeBounce {
+ animation: bounce 1s infinite;
+}
+
+.animation-largeBounce {
+ --animation-dist: -15px;
+}
+
+@keyframes bounce {
+ 30% {
+ transform: translateY(var(--animation-dist));
+ }
+}
diff --git a/entry_types/scrolled/package/src/widgets/iconScrollIndicator/index.js b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/index.js
new file mode 100644
index 0000000000..0145f6032e
--- /dev/null
+++ b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/index.js
@@ -0,0 +1,6 @@
+import {frontend} from 'pageflow-scrolled/frontend';
+import {IconScrollIndicator} from './IconScrollIndicator';
+
+frontend.widgetTypes.register('iconScrollIndicator', {
+ component: IconScrollIndicator
+});
diff --git a/entry_types/scrolled/package/src/widgets/iconScrollIndicator/stories.js b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/stories.js
new file mode 100644
index 0000000000..497289ab89
--- /dev/null
+++ b/entry_types/scrolled/package/src/widgets/iconScrollIndicator/stories.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import {storiesOf} from '@storybook/react';
+import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';
+
+import {
+ normalizeAndMergeFixture,
+ filePermaId,
+ exampleHeading,
+ exampleTextBlock,
+ ExampleEntry
+} from 'pageflow-scrolled/spec/support/stories';
+
+import './index';
+
+const stories = storiesOf('Widgets/Icon Scroll Indicator', module);
+
+function getSeed({widgetConfiguration = {}, sectionConfiguration} = {}) {
+ return {
+ widgets: [{
+ role: 'scrollIndicator',
+ typeName: 'iconScrollIndicator',
+ configuration: widgetConfiguration
+ }],
+ sections: [{
+ id: 1,
+ configuration: {
+ fullHeight: true,
+ backdrop: {
+ image: filePermaId('imageFiles', 'turtle')
+ },
+ ...sectionConfiguration
+ }
+ }],
+ contentElements: [
+ exampleHeading({sectionId: 1, text: 'A Heading'}),
+ exampleTextBlock({sectionId: 1}),
+ ]
+ }
+};
+
+stories.add(
+ 'Desktop',
+ () =>
+);
+
+stories.add(
+ 'Desktop - Right',
+ () =>
+
+);
+
+stories.add(
+ 'Desktop - Large/Center Viewport',
+ () =>
+
+);
+
+stories.add(
+ 'Mobile',
+ () => ,
+ {
+ percy: {
+ widths: [320]
+ },
+ viewport: {
+ viewports: INITIAL_VIEWPORTS,
+ defaultViewport: 'iphone6'
+ }
+ }
+);
diff --git a/entry_types/scrolled/package/widgets-server.js b/entry_types/scrolled/package/widgets-server.js
index 8c760f20b7..a6f8b26021 100644
--- a/entry_types/scrolled/package/widgets-server.js
+++ b/entry_types/scrolled/package/widgets-server.js
@@ -2,3 +2,4 @@ import 'pageflow-scrolled/widgets/defaultNavigation';
import 'pageflow-scrolled/widgets/consentBar';
import 'pageflow-scrolled/widgets/iconInlineFileRights';
import 'pageflow-scrolled/widgets/textInlineFileRights';
+import 'pageflow-scrolled/widgets/iconScrollIndicator';
diff --git a/package/src/editor/controllers/SidebarController.js b/package/src/editor/controllers/SidebarController.js
index c8285248b3..bbd01b0b44 100644
--- a/package/src/editor/controllers/SidebarController.js
+++ b/package/src/editor/controllers/SidebarController.js
@@ -77,9 +77,13 @@ export const SidebarController = Marionette.Controller.extend({
},
widget: function(id) {
+ const model = this.entry.widgets.get(id);
+
this.region.show(new EditWidgetView({
entry: this.entry,
- model: this.entry.widgets.get(id)
+ model
}));
+
+ this.entry.trigger('selectWidget', model);
},
});
diff --git a/rollup.config.js b/rollup.config.js
index 33014fbe0b..8d58dbcb45 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -314,7 +314,11 @@ const pageflowScrolled = [
...ignoreJSXWarning
},
- ...(['defaultNavigation', 'consentBar', 'textInlineFileRights', 'iconInlineFileRights'].map(name => (
+ ...([
+ 'defaultNavigation', 'consentBar',
+ 'textInlineFileRights', 'iconInlineFileRights',
+ 'iconScrollIndicator'
+ ].map(name => (
{
input: `${pageflowScrolledPackageRoot}/src/widgets/${name}/index.js`,
output: {