From fc5d935e530010f7c564090661f2c7aeb255d750 Mon Sep 17 00:00:00 2001 From: Shawn Erquhart Date: Thu, 23 Mar 2017 17:15:48 -0400 Subject: [PATCH] use Context for sticky, support multiple stickies --- src/components/EntryEditor/EntryEditor.js | 34 +---- src/components/UI/Sticky/Sticky.css | 13 +- src/components/UI/Sticky/Sticky.js | 116 ++++++++++++++++++ src/components/Widgets/MarkdownControl.js | 33 +++-- .../RawEditor/index.js | 5 +- .../VisualEditor/index.css | 1 + .../VisualEditor/index.js | 5 +- 7 files changed, 153 insertions(+), 54 deletions(-) create mode 100644 src/components/UI/Sticky/Sticky.js diff --git a/src/components/EntryEditor/EntryEditor.js b/src/components/EntryEditor/EntryEditor.js index 01fc0198..ac68c2c0 100644 --- a/src/components/EntryEditor/EntryEditor.js +++ b/src/components/EntryEditor/EntryEditor.js @@ -6,6 +6,7 @@ import { ScrollSync, ScrollSyncPane } from '../ScrollSync'; import ControlPane from '../ControlPanel/ControlPane'; import PreviewPane from '../PreviewPane/PreviewPane'; import Toolbar from './EntryEditorToolbar'; +import { StickyContext } from '../UI/Sticky/Sticky'; import styles from './EntryEditor.css'; import stickyStyles from '../UI/Sticky/Sticky.css'; @@ -36,35 +37,6 @@ class EntryEditor extends Component { localStorage.setItem(PREVIEW_VISIBLE, newPreviewVisible); }; - setSticky = (contextTop, containerRect, sticky) => { - if (contextTop >= containerRect.top) { - if (contextTop < containerRect.bottom - 60) { - sticky.classList.add(stickyStyles.top); - sticky.classList.remove(stickyStyles.bottom); - } else if (contextTop >= containerRect.bottom - 60) { - sticky.classList.remove(stickyStyles.top); - sticky.classList.add(stickyStyles.bottom); - } - } else { - sticky.classList.remove(stickyStyles.top); - sticky.classList.remove(stickyStyles.bottom); - } - }; - - handleControlPaneRef = (ref) => { - const sticky = ref.querySelector('.cms__index__editorControlBar'); - const stickyContainer = ref.querySelector('.stickyContainer'); - stickyContainer.style.paddingTop = `${ sticky.offsetHeight }px`; - sticky.style.position = 'absolute'; - sticky.style.top = `${ -sticky.offsetHeight }px`; - sticky.style.width = `${ stickyContainer.getBoundingClientRect().width }px`; - - ref && ref.addEventListener('scroll', (e) => { - const contextTop = e.target.getBoundingClientRect().top; - this.setSticky(contextTop, stickyContainer.getBoundingClientRect(), sticky); - }); - }; - render() { const { collection, @@ -90,7 +62,7 @@ class EntryEditor extends Component { ); const editor = ( -
+ { collectionPreviewEnabled ? togglePreviewButton : null } this.controlPaneRef = c} // eslint-disable-line /> -
+ ); const editorWithPreview = ( diff --git a/src/components/UI/Sticky/Sticky.css b/src/components/UI/Sticky/Sticky.css index 7f6b8378..37259174 100644 --- a/src/components/UI/Sticky/Sticky.css +++ b/src/components/UI/Sticky/Sticky.css @@ -1,9 +1,18 @@ -.top { +.stickyContainer { + position: relative !important; +} + +.sticky { + position: relative !important; +} + +.stickyActive:not(.stickyAtBottom) { position: fixed !important; top: 64px !important; } -.bottom { +.stickyAtBottom { + position: absolute !important; top: auto !important; bottom: 30px !important; } diff --git a/src/components/UI/Sticky/Sticky.js b/src/components/UI/Sticky/Sticky.js new file mode 100644 index 00000000..af89f34a --- /dev/null +++ b/src/components/UI/Sticky/Sticky.js @@ -0,0 +1,116 @@ +import React, { Component, PropTypes } from 'react'; +import classnames from 'classnames'; +import styles from './Sticky.css'; + +export class StickyContext extends Component { + static childContextTypes = { subscribeToStickyContext: PropTypes.func }; + + constructor(props) { + super(props); + this.subscriptions = []; + } + + getChildContext() { + return { subscribeToStickyContext: (fn) => { this.subscriptions.push(fn); } }; + } + + updateStickies = (ref) => { + const stickyContextTop = ref.getBoundingClientRect().top; + this.subscriptions.forEach((fn) => { fn(stickyContextTop); }); + }; + + componentDidMount() { + this.updateStickies(this.ref); + } + + handleScroll = (event) => { + this.updateStickies(event.target); + }; + + render() { + return ( +
{ this.ref = ref; }}> + {this.props.children} +
+ ); + } +} + +export class StickyContainer extends Component { + constructor(props) { + super(props); + this.subscriptions = []; + } + + static contextTypes = { subscribeToStickyContext: PropTypes.func }; + static childContextTypes = { subscribeToStickyContainer: PropTypes.func }; + + getChildContext() { + return { subscribeToStickyContainer: (fn) => { this.subscriptions.push(fn); } }; + } + + getPosition = (contextTop) => { + const rect = this.ref.getBoundingClientRect(); + const shouldStick = rect.top < contextTop; + const shouldStickAtBottom = rect.bottom - 60 < contextTop; + this.subscriptions.forEach((fn) => { fn(shouldStick, shouldStickAtBottom, rect.width) }); + }; + + componentDidMount() { + this.context.subscribeToStickyContext(this.getPosition); + } + + render() { + return ( +
{ this.ref = ref }} + > + {this.props.children} +
+ ); + } +} + +export class Sticky extends Component { + static contextTypes = { subscribeToStickyContainer: PropTypes.func }; + + constructor(props, context) { + super(props, context); + this.state = {}; + } + + updateSticky = (shouldStick, shouldStickAtBottom, containerWidth) => { + this.setState({ shouldStick, shouldStickAtBottom, containerWidth }); + }; + + componentDidMount() { + this.context.subscribeToStickyContainer(this.updateSticky); + } + + render() { + const { props, state } = this; + const stickyPlaceholderHeight = state.shouldStick ? this.ref.getBoundingClientRect().height : 0; + + return ( +
+
+
{this.ref = ref}} + > + {props.children} +
+
+ ); + } +} diff --git a/src/components/Widgets/MarkdownControl.js b/src/components/Widgets/MarkdownControl.js index 4f829e0d..205d8bc7 100644 --- a/src/components/Widgets/MarkdownControl.js +++ b/src/components/Widgets/MarkdownControl.js @@ -3,6 +3,7 @@ import registry from '../../lib/registry'; import RawEditor from './MarkdownControlElements/RawEditor'; import VisualEditor from './MarkdownControlElements/VisualEditor'; import { processEditorPlugins } from './richText'; +import { StickyContainer } from '../UI/Sticky/Sticky'; const MODE_STORAGE_KEY = 'cms.md-mode'; @@ -32,23 +33,20 @@ export default class MarkdownControl extends React.Component { render() { const { onChange, onAddAsset, onRemoveAsset, getAsset, value } = this.props; const { mode } = this.state; - if (mode === 'visual') { - return ( -
- -
- ); - } - - return ( -
+ const visualEditor = ( +
+ +
+ ); + const rawEditor = ( +
); + return { mode === 'visual' ? visualEditor : rawEditor }; } } diff --git a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js index b94b9358..b8ca39c2 100644 --- a/src/components/Widgets/MarkdownControlElements/RawEditor/index.js +++ b/src/components/Widgets/MarkdownControlElements/RawEditor/index.js @@ -7,6 +7,7 @@ import registry from '../../../../lib/registry'; import { createAssetProxy } from '../../../../valueObjects/AssetProxy'; import Toolbar from '../Toolbar'; import ToolbarPlugins from '../ToolbarPlugins'; +import { Sticky } from '../../../UI/Sticky/Sticky'; import styles from './index.css'; const HAS_LINE_BREAK = /\n/m; @@ -319,7 +320,7 @@ export default class RawEditor extends React.Component { onDragOver={this.handleDragOver} onDrop={this.handleDrop} > -
+ -
+