use Context for sticky, support multiple stickies

This commit is contained in:
Shawn Erquhart 2017-03-23 17:15:48 -04:00
parent ec29a04089
commit fc5d935e53
7 changed files with 153 additions and 54 deletions

View File

@ -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 = (
<div className={controlClassName} ref={this.handleControlPaneRef}>
<StickyContext className={controlClassName}>
{ collectionPreviewEnabled ? togglePreviewButton : null }
<ControlPane
collection={collection}
@ -105,7 +77,7 @@ class EntryEditor extends Component {
onRemoveAsset={onRemoveAsset}
ref={c => this.controlPaneRef = c} // eslint-disable-line
/>
</div>
</StickyContext>
);
const editorWithPreview = (

View File

@ -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;
}

View File

@ -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 (
<div className={this.props.className} onScroll={this.handleScroll} ref={(ref) => { this.ref = ref; }}>
{this.props.children}
</div>
);
}
}
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 (
<div
id={this.context.string}
className={classnames(this.props.className, styles.stickyContainer)}
ref={(ref) => { this.ref = ref }}
>
{this.props.children}
</div>
);
}
}
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 (
<div>
<div style={{paddingBottom: stickyPlaceholderHeight}}></div>
<div
className={classnames(
props.className,
styles.sticky,
{
[styles.stickyActive]: state.shouldStick,
[styles.stickyAtBottom]: state.shouldStickAtBottom,
},
)}
style={props.fillContainerWidth && state.containerWidth ? { width: state.containerWidth } : null}
ref={(ref) => {this.ref = ref}}
>
{props.children}
</div>
</div>
);
}
}

View File

@ -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 (
<div className="cms-editor-visual stickyContainer">
<VisualEditor
onChange={onChange}
onAddAsset={onAddAsset}
onRemoveAsset={onRemoveAsset}
onMode={this.handleMode}
getAsset={getAsset}
value={value}
/>
</div>
);
}
return (
<div className="cms-editor-raw stickyContainer">
const visualEditor = (
<div className="cms-editor-visual">
<VisualEditor
onChange={onChange}
onAddAsset={onAddAsset}
onRemoveAsset={onRemoveAsset}
onMode={this.handleMode}
getAsset={getAsset}
value={value}
/>
</div>
);
const rawEditor = (
<div className="cms-editor-raw">
<RawEditor
onChange={onChange}
onAddAsset={onAddAsset}
@ -60,5 +58,6 @@ export default class MarkdownControl extends React.Component {
/>
</div>
);
return <StickyContainer>{ mode === 'visual' ? visualEditor : rawEditor }</StickyContainer>;
}
}

View File

@ -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}
>
<div className={styles.editorControlBar}>
<Sticky className={styles.editorControlBar} fillContainerWidth={true}>
<Toolbar
selectionPosition={selectionPosition}
onH1={this.handleHeader('#')}
@ -339,7 +340,7 @@ export default class RawEditor extends React.Component {
getAsset={getAsset}
rawMode
/>
</div>
</Sticky>
<textarea
ref={this.handleRef}
value={this.props.value || ''}

View File

@ -4,6 +4,7 @@
background-color: var(--controlBGColor);
border-bottom: 1px solid var(--backgroundTertiaryColorDark);
border-radius: var(--borderRadius) var(--borderRadius) 0 0;
z-index: 1;
}
.editor {

View File

@ -17,6 +17,7 @@ import { buildKeymap } from './keymap';
import createMarkdownParser from './parser';
import Toolbar from '../Toolbar';
import ToolbarPlugins from '../ToolbarPlugins';
import { Sticky } from '../../../UI/Sticky/Sticky';
import styles from './index.css';
function processUrl(url) {
@ -275,7 +276,7 @@ export default class Editor extends Component {
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
>
<div className={styles.editorControlBar}>
<Sticky className={styles.editorControlBar} fillContainerWidth={true}>
<Toolbar
selectionPosition={selectionPosition}
onH1={this.handleHeader(1)}
@ -293,7 +294,7 @@ export default class Editor extends Component {
onRemoveAsset={onRemoveAsset}
getAsset={getAsset}
/>
</div>
</Sticky>
<div ref={this.handleRef} />
<div className={styles.shim} />
</div>);