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: {