use Context for sticky, support multiple stickies
This commit is contained in:
parent
ec29a04089
commit
fc5d935e53
@ -6,6 +6,7 @@ import { ScrollSync, ScrollSyncPane } from '../ScrollSync';
|
|||||||
import ControlPane from '../ControlPanel/ControlPane';
|
import ControlPane from '../ControlPanel/ControlPane';
|
||||||
import PreviewPane from '../PreviewPane/PreviewPane';
|
import PreviewPane from '../PreviewPane/PreviewPane';
|
||||||
import Toolbar from './EntryEditorToolbar';
|
import Toolbar from './EntryEditorToolbar';
|
||||||
|
import { StickyContext } from '../UI/Sticky/Sticky';
|
||||||
import styles from './EntryEditor.css';
|
import styles from './EntryEditor.css';
|
||||||
import stickyStyles from '../UI/Sticky/Sticky.css';
|
import stickyStyles from '../UI/Sticky/Sticky.css';
|
||||||
|
|
||||||
@ -36,35 +37,6 @@ class EntryEditor extends Component {
|
|||||||
localStorage.setItem(PREVIEW_VISIBLE, newPreviewVisible);
|
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() {
|
render() {
|
||||||
const {
|
const {
|
||||||
collection,
|
collection,
|
||||||
@ -90,7 +62,7 @@ class EntryEditor extends Component {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const editor = (
|
const editor = (
|
||||||
<div className={controlClassName} ref={this.handleControlPaneRef}>
|
<StickyContext className={controlClassName}>
|
||||||
{ collectionPreviewEnabled ? togglePreviewButton : null }
|
{ collectionPreviewEnabled ? togglePreviewButton : null }
|
||||||
<ControlPane
|
<ControlPane
|
||||||
collection={collection}
|
collection={collection}
|
||||||
@ -105,7 +77,7 @@ class EntryEditor extends Component {
|
|||||||
onRemoveAsset={onRemoveAsset}
|
onRemoveAsset={onRemoveAsset}
|
||||||
ref={c => this.controlPaneRef = c} // eslint-disable-line
|
ref={c => this.controlPaneRef = c} // eslint-disable-line
|
||||||
/>
|
/>
|
||||||
</div>
|
</StickyContext>
|
||||||
);
|
);
|
||||||
|
|
||||||
const editorWithPreview = (
|
const editorWithPreview = (
|
||||||
|
@ -1,9 +1,18 @@
|
|||||||
.top {
|
.stickyContainer {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sticky {
|
||||||
|
position: relative !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stickyActive:not(.stickyAtBottom) {
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 64px !important;
|
top: 64px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom {
|
.stickyAtBottom {
|
||||||
|
position: absolute !important;
|
||||||
top: auto !important;
|
top: auto !important;
|
||||||
bottom: 30px !important;
|
bottom: 30px !important;
|
||||||
}
|
}
|
||||||
|
116
src/components/UI/Sticky/Sticky.js
Normal file
116
src/components/UI/Sticky/Sticky.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import registry from '../../lib/registry';
|
|||||||
import RawEditor from './MarkdownControlElements/RawEditor';
|
import RawEditor from './MarkdownControlElements/RawEditor';
|
||||||
import VisualEditor from './MarkdownControlElements/VisualEditor';
|
import VisualEditor from './MarkdownControlElements/VisualEditor';
|
||||||
import { processEditorPlugins } from './richText';
|
import { processEditorPlugins } from './richText';
|
||||||
|
import { StickyContainer } from '../UI/Sticky/Sticky';
|
||||||
|
|
||||||
const MODE_STORAGE_KEY = 'cms.md-mode';
|
const MODE_STORAGE_KEY = 'cms.md-mode';
|
||||||
|
|
||||||
@ -32,9 +33,8 @@ export default class MarkdownControl extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const { onChange, onAddAsset, onRemoveAsset, getAsset, value } = this.props;
|
const { onChange, onAddAsset, onRemoveAsset, getAsset, value } = this.props;
|
||||||
const { mode } = this.state;
|
const { mode } = this.state;
|
||||||
if (mode === 'visual') {
|
const visualEditor = (
|
||||||
return (
|
<div className="cms-editor-visual">
|
||||||
<div className="cms-editor-visual stickyContainer">
|
|
||||||
<VisualEditor
|
<VisualEditor
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onAddAsset={onAddAsset}
|
onAddAsset={onAddAsset}
|
||||||
@ -45,10 +45,8 @@ export default class MarkdownControl extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
const rawEditor = (
|
||||||
|
<div className="cms-editor-raw">
|
||||||
return (
|
|
||||||
<div className="cms-editor-raw stickyContainer">
|
|
||||||
<RawEditor
|
<RawEditor
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onAddAsset={onAddAsset}
|
onAddAsset={onAddAsset}
|
||||||
@ -60,5 +58,6 @@ export default class MarkdownControl extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
return <StickyContainer>{ mode === 'visual' ? visualEditor : rawEditor }</StickyContainer>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import registry from '../../../../lib/registry';
|
|||||||
import { createAssetProxy } from '../../../../valueObjects/AssetProxy';
|
import { createAssetProxy } from '../../../../valueObjects/AssetProxy';
|
||||||
import Toolbar from '../Toolbar';
|
import Toolbar from '../Toolbar';
|
||||||
import ToolbarPlugins from '../ToolbarPlugins';
|
import ToolbarPlugins from '../ToolbarPlugins';
|
||||||
|
import { Sticky } from '../../../UI/Sticky/Sticky';
|
||||||
import styles from './index.css';
|
import styles from './index.css';
|
||||||
|
|
||||||
const HAS_LINE_BREAK = /\n/m;
|
const HAS_LINE_BREAK = /\n/m;
|
||||||
@ -319,7 +320,7 @@ export default class RawEditor extends React.Component {
|
|||||||
onDragOver={this.handleDragOver}
|
onDragOver={this.handleDragOver}
|
||||||
onDrop={this.handleDrop}
|
onDrop={this.handleDrop}
|
||||||
>
|
>
|
||||||
<div className={styles.editorControlBar}>
|
<Sticky className={styles.editorControlBar} fillContainerWidth={true}>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
selectionPosition={selectionPosition}
|
selectionPosition={selectionPosition}
|
||||||
onH1={this.handleHeader('#')}
|
onH1={this.handleHeader('#')}
|
||||||
@ -339,7 +340,7 @@ export default class RawEditor extends React.Component {
|
|||||||
getAsset={getAsset}
|
getAsset={getAsset}
|
||||||
rawMode
|
rawMode
|
||||||
/>
|
/>
|
||||||
</div>
|
</Sticky>
|
||||||
<textarea
|
<textarea
|
||||||
ref={this.handleRef}
|
ref={this.handleRef}
|
||||||
value={this.props.value || ''}
|
value={this.props.value || ''}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
background-color: var(--controlBGColor);
|
background-color: var(--controlBGColor);
|
||||||
border-bottom: 1px solid var(--backgroundTertiaryColorDark);
|
border-bottom: 1px solid var(--backgroundTertiaryColorDark);
|
||||||
border-radius: var(--borderRadius) var(--borderRadius) 0 0;
|
border-radius: var(--borderRadius) var(--borderRadius) 0 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
|
@ -17,6 +17,7 @@ import { buildKeymap } from './keymap';
|
|||||||
import createMarkdownParser from './parser';
|
import createMarkdownParser from './parser';
|
||||||
import Toolbar from '../Toolbar';
|
import Toolbar from '../Toolbar';
|
||||||
import ToolbarPlugins from '../ToolbarPlugins';
|
import ToolbarPlugins from '../ToolbarPlugins';
|
||||||
|
import { Sticky } from '../../../UI/Sticky/Sticky';
|
||||||
import styles from './index.css';
|
import styles from './index.css';
|
||||||
|
|
||||||
function processUrl(url) {
|
function processUrl(url) {
|
||||||
@ -275,7 +276,7 @@ export default class Editor extends Component {
|
|||||||
onDragOver={this.handleDragOver}
|
onDragOver={this.handleDragOver}
|
||||||
onDrop={this.handleDrop}
|
onDrop={this.handleDrop}
|
||||||
>
|
>
|
||||||
<div className={styles.editorControlBar}>
|
<Sticky className={styles.editorControlBar} fillContainerWidth={true}>
|
||||||
<Toolbar
|
<Toolbar
|
||||||
selectionPosition={selectionPosition}
|
selectionPosition={selectionPosition}
|
||||||
onH1={this.handleHeader(1)}
|
onH1={this.handleHeader(1)}
|
||||||
@ -293,7 +294,7 @@ export default class Editor extends Component {
|
|||||||
onRemoveAsset={onRemoveAsset}
|
onRemoveAsset={onRemoveAsset}
|
||||||
getAsset={getAsset}
|
getAsset={getAsset}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Sticky>
|
||||||
<div ref={this.handleRef} />
|
<div ref={this.handleRef} />
|
||||||
<div className={styles.shim} />
|
<div className={styles.shim} />
|
||||||
</div>);
|
</div>);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user