feat: sticky header on mobile if plaintext note is scrolled (#1645)

This commit is contained in:
Aman Harwara
2022-09-26 21:15:11 +05:30
committed by GitHub
parent 58cd919267
commit f8f6a5212b

View File

@@ -14,7 +14,7 @@ import {
PayloadEmitSource, PayloadEmitSource,
WebAppEvent, WebAppEvent,
} from '@standardnotes/snjs' } from '@standardnotes/snjs'
import { debounce, isDesktopApplication } from '@/Utils' import { debounce, isDesktopApplication, isIOS } from '@/Utils'
import { EditorEventSource } from '../../Types/EditorEventSource' import { EditorEventSource } from '../../Types/EditorEventSource'
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services' import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services'
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings' import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings'
@@ -22,7 +22,6 @@ import { PureComponent } from '@/Components/Abstract/PureComponent'
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay' import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton' import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel' import NotesOptionsPanel from '@/Components/NotesOptions/NotesOptionsPanel'
import NoteTagsContainer from '@/Components/NoteTags/NoteTagsContainer'
import ComponentView from '@/Components/ComponentView/ComponentView' import ComponentView from '@/Components/ComponentView/ComponentView'
import PanelResizer, { PanelSide, PanelResizeType } from '@/Components/PanelResizer/PanelResizer' import PanelResizer, { PanelSide, PanelResizeType } from '@/Components/PanelResizer/PanelResizer'
import { ElementIds } from '@/Constants/ElementIDs' import { ElementIds } from '@/Constants/ElementIDs'
@@ -41,9 +40,10 @@ import AutoresizingNoteViewTextarea from './AutoresizingTextarea'
import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton' import MobileItemsListButton from '../NoteGroupView/MobileItemsListButton'
import NoteTagsPanel from '../NoteTags/NoteTagsPanel' import NoteTagsPanel from '../NoteTags/NoteTagsPanel'
const MINIMUM_STATUS_DURATION = 400 const MinimumStatusDuration = 400
const TEXTAREA_DEBOUNCE = 100 const TextareaDebounce = 100
const NOTE_EDITING_DISABLED_TEXT = 'Note editing disabled.' const NoteEditingDisabledText = 'Note editing disabled.'
const StickyHeaderScrollThresholdInPx = 20
type NoteStatus = { type NoteStatus = {
message?: string message?: string
@@ -81,6 +81,8 @@ type State = {
leftResizerOffset: number leftResizerOffset: number
rightResizerWidth: number rightResizerWidth: number
rightResizerOffset: number rightResizerOffset: number
shouldStickyHeader: boolean
} }
class NoteView extends PureComponent<NoteViewProps, State> { class NoteView extends PureComponent<NoteViewProps, State> {
@@ -112,7 +114,9 @@ class NoteView extends PureComponent<NoteViewProps, State> {
this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25) this.debounceReloadEditorComponent = debounce(this.debounceReloadEditorComponent.bind(this), 25)
this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TEXTAREA_DEBOUNCE) this.textAreaChangeDebounceSave = debounce(this.textAreaChangeDebounceSave, TextareaDebounce)
this.handleWindowScroll = debounce(this.handleWindowScroll, 10)
this.state = { this.state = {
availableStackComponents: [], availableStackComponents: [],
@@ -120,7 +124,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
editorText: '', editorText: '',
editorTitle: '', editorTitle: '',
isDesktop: isDesktopApplication(), isDesktop: isDesktopApplication(),
lockText: NOTE_EDITING_DISABLED_TEXT, lockText: NoteEditingDisabledText,
noteStatus: undefined, noteStatus: undefined,
noteLocked: this.controller.item.locked, noteLocked: this.controller.item.locked,
showLockedIcon: true, showLockedIcon: true,
@@ -133,12 +137,17 @@ class NoteView extends PureComponent<NoteViewProps, State> {
leftResizerOffset: 0, leftResizerOffset: 0,
rightResizerWidth: 0, rightResizerWidth: 0,
rightResizerOffset: 0, rightResizerOffset: 0,
shouldStickyHeader: false,
} }
this.editorContentRef = createRef<HTMLDivElement>() this.editorContentRef = createRef<HTMLDivElement>()
window.addEventListener('scroll', this.handleWindowScroll)
} }
override deinit() { override deinit() {
window.removeEventListener('scroll', this.handleWindowScroll)
this.removeComponentStreamObserver?.() this.removeComponentStreamObserver?.()
;(this.removeComponentStreamObserver as unknown) = undefined ;(this.removeComponentStreamObserver as unknown) = undefined
@@ -522,7 +531,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
this.setState({ this.setState({
noteStatus: status, noteStatus: status,
}) })
}, MINIMUM_STATUS_DURATION) }, MinimumStatusDuration)
} else { } else {
this.setState({ this.setState({
noteStatus: status, noteStatus: status,
@@ -872,6 +881,12 @@ class NoteView extends PureComponent<NoteViewProps, State> {
} }
} }
handleWindowScroll = () => {
this.setState({
shouldStickyHeader: window.scrollY > StickyHeaderScrollThresholdInPx,
})
}
override render() { override render() {
if (this.state.showProtectedWarning) { if (this.state.showProtectedWarning) {
return ( return (
@@ -890,7 +905,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
<EditingDisabledBanner <EditingDisabledBanner
onMouseLeave={() => { onMouseLeave={() => {
this.setState({ this.setState({
lockText: NOTE_EDITING_DISABLED_TEXT, lockText: NoteEditingDisabledText,
showLockedIcon: true, showLockedIcon: true,
}) })
}} }}
@@ -907,7 +922,14 @@ class NoteView extends PureComponent<NoteViewProps, State> {
)} )}
{this.note && ( {this.note && (
<div id="editor-title-bar" className="content-title-bar section-title-bar z-editor-title-bar w-full"> <div
id="editor-title-bar"
className={classNames(
'content-title-bar section-title-bar z-editor-title-bar w-full bg-default',
this.state.shouldStickyHeader && 'fixed top-0',
this.state.shouldStickyHeader ? (isIOS() ? 'pt-safe-top' : 'pt-4') : '',
)}
>
<div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0 xl:items-center"> <div className="mb-2 flex flex-wrap items-start justify-between gap-2 md:mb-0 md:flex-nowrap md:gap-0 xl:items-center">
<div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}> <div className={classNames(this.state.noteLocked && 'locked', 'flex flex-grow items-center')}>
<MobileItemsListButton /> <MobileItemsListButton />
@@ -927,6 +949,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
/> />
</div> </div>
</div> </div>
{!this.state.shouldStickyHeader && (
<div className="flex flex-row-reverse items-center gap-3 md:flex-col-reverse md:items-end xl:flex-row xl:flex-nowrap xl:items-center"> <div className="flex flex-row-reverse items-center gap-3 md:flex-col-reverse md:items-end xl:flex-row xl:flex-nowrap xl:items-center">
{this.state.noteStatus?.message?.length && ( {this.state.noteStatus?.message?.length && (
<div id="save-status-container" className={'xl:mr-5 xl:max-w-[16ch]'}> <div id="save-status-container" className={'xl:mr-5 xl:max-w-[16ch]'}>
@@ -940,7 +963,9 @@ class NoteView extends PureComponent<NoteViewProps, State> {
> >
{this.state.noteStatus?.message} {this.state.noteStatus?.message}
</div> </div>
{this.state.noteStatus?.desc && <div className="desc text-xs">{this.state.noteStatus.desc}</div>} {this.state.noteStatus?.desc && (
<div className="desc text-xs">{this.state.noteStatus.desc}</div>
)}
</div> </div>
</div> </div>
)} )}
@@ -978,11 +1003,8 @@ class NoteView extends PureComponent<NoteViewProps, State> {
/> />
</div> </div>
</div> </div>
)}
</div> </div>
<NoteTagsContainer
noteTagsController={this.viewControllerManager.noteTagsController}
navigationController={this.viewControllerManager.navigationController}
/>
</div> </div>
)} )}