1586106d6SBartłomiej Klocek// NOTE(jim): 2586106d6SBartłomiej Klocek// GETTING NESTED SCROLL RIGHT IS DELICATE BUSINESS. THEREFORE THIS COMPONENT 3586106d6SBartłomiej Klocek// IS THE ONLY PLACE WHERE SCROLL CODE SHOULD BE HANDLED. THANKS. 440836478SBartosz Kaszubowskiimport { css } from '@emotion/react'; 5*f4b1168bSBartosz Kaszubowskiimport { theme } from '@expo/styleguide'; 6*f4b1168bSBartosz Kaszubowskiimport { breakpoints } from '@expo/styleguide-base'; 7586106d6SBartłomiej Klocekimport * as React from 'react'; 8586106d6SBartłomiej Klocek 9b4bb5e4dSBartosz Kaszubowskiimport { SidebarHead, SidebarFooter } from '~/ui/components/Sidebar'; 10586106d6SBartłomiej Klocek 11586106d6SBartłomiej Klocekconst STYLES_CONTAINER = css` 12586106d6SBartłomiej Klocek width: 100%; 13586106d6SBartłomiej Klocek height: 100vh; 14586106d6SBartłomiej Klocek overflow: hidden; 15586106d6SBartłomiej Klocek margin: 0 auto 0 auto; 168bf05203SJon Samp border-right: 1px solid ${theme.border.default}; 178bf05203SJon Samp background: ${theme.background.default}; 18586106d6SBartłomiej Klocek 19586106d6SBartłomiej Klocek display: flex; 20586106d6SBartłomiej Klocek align-items: center; 21586106d6SBartłomiej Klocek justify-content: space-between; 22586106d6SBartłomiej Klocek flex-direction: column; 23586106d6SBartłomiej Klocek 24586106d6SBartłomiej Klocek @media screen and (max-width: 1440px) { 25586106d6SBartłomiej Klocek border-left: 0px; 26586106d6SBartłomiej Klocek border-right: 0px; 27586106d6SBartłomiej Klocek } 28586106d6SBartłomiej Klocek 29d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 30586106d6SBartłomiej Klocek display: block; 31586106d6SBartłomiej Klocek height: auto; 32586106d6SBartłomiej Klocek } 33586106d6SBartłomiej Klocek`; 34586106d6SBartłomiej Klocek 35586106d6SBartłomiej Klocekconst STYLES_HEADER = css` 36586106d6SBartłomiej Klocek flex-shrink: 0; 37586106d6SBartłomiej Klocek width: 100%; 38586106d6SBartłomiej Klocek 39d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 40586106d6SBartłomiej Klocek position: sticky; 41586106d6SBartłomiej Klocek top: -57px; 42586106d6SBartłomiej Klocek z-index: 3; 43d04fd43fSBartosz Kaszubowski max-height: 100vh; 44586106d6SBartłomiej Klocek } 45586106d6SBartłomiej Klocek`; 46586106d6SBartłomiej Klocek 47586106d6SBartłomiej Klocekconst STYLES_CONTENT = css` 48586106d6SBartłomiej Klocek display: flex; 49586106d6SBartłomiej Klocek align-items: flex-start; 50586106d6SBartłomiej Klocek margin: 0 auto; 51586106d6SBartłomiej Klocek justify-content: space-between; 52586106d6SBartłomiej Klocek width: 100%; 53586106d6SBartłomiej Klocek height: 100%; 54586106d6SBartłomiej Klocek min-height: 25%; 55586106d6SBartłomiej Klocek 56d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 57586106d6SBartłomiej Klocek height: auto; 58586106d6SBartłomiej Klocek } 59586106d6SBartłomiej Klocek`; 60586106d6SBartłomiej Klocek 61586106d6SBartłomiej Klocekconst STYLES_SIDEBAR = css` 62d04fd43fSBartosz Kaszubowski display: flex; 63d04fd43fSBartosz Kaszubowski flex-direction: column; 64586106d6SBartłomiej Klocek flex-shrink: 0; 65586106d6SBartłomiej Klocek max-width: 280px; 66586106d6SBartłomiej Klocek height: 100%; 67586106d6SBartłomiej Klocek overflow: hidden; 68586106d6SBartłomiej Klocek transition: 200ms ease max-width; 69d04fd43fSBartosz Kaszubowski background: ${theme.background.default}; 70586106d6SBartłomiej Klocek 71586106d6SBartłomiej Klocek @media screen and (max-width: 1200px) { 72586106d6SBartłomiej Klocek max-width: 280px; 73586106d6SBartłomiej Klocek } 74586106d6SBartłomiej Klocek 75d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 76586106d6SBartłomiej Klocek display: none; 77586106d6SBartłomiej Klocek } 78586106d6SBartłomiej Klocek`; 79586106d6SBartłomiej Klocek 80586106d6SBartłomiej Klocekconst STYLES_LEFT = css` 818bf05203SJon Samp border-right: 1px solid ${theme.border.default}; 82586106d6SBartłomiej Klocek`; 83586106d6SBartłomiej Klocek 84586106d6SBartłomiej Klocekconst STYLES_RIGHT = css` 858bf05203SJon Samp border-left: 1px solid ${theme.border.default}; 868bf05203SJon Samp background-color: ${theme.background.default}; 87586106d6SBartłomiej Klocek`; 88586106d6SBartłomiej Klocek 89586106d6SBartłomiej Klocekconst STYLES_CENTER = css` 908bf05203SJon Samp background: ${theme.background.default}; 91586106d6SBartłomiej Klocek min-width: 5%; 92586106d6SBartłomiej Klocek width: 100%; 93586106d6SBartłomiej Klocek height: 100%; 94586106d6SBartłomiej Klocek overflow: hidden; 95586106d6SBartłomiej Klocek display: flex; 96586106d6SBartłomiej Klocek 97d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 98586106d6SBartłomiej Klocek height: auto; 99586106d6SBartłomiej Klocek overflow: auto; 100586106d6SBartłomiej Klocek } 101586106d6SBartłomiej Klocek`; 102586106d6SBartłomiej Klocek 103586106d6SBartłomiej Klocek// NOTE(jim): 104586106d6SBartłomiej Klocek// All the other components tame the UI. this one allows a container to scroll. 105586106d6SBartłomiej Klocekconst STYLES_SCROLL_CONTAINER = css` 106586106d6SBartłomiej Klocek height: 100%; 107586106d6SBartłomiej Klocek width: 100%; 108586106d6SBartłomiej Klocek overflow-y: scroll; 109586106d6SBartłomiej Klocek overflow-x: hidden; 110586106d6SBartłomiej Klocek -webkit-overflow-scrolling: touch; 111586106d6SBartłomiej Klocek 112586106d6SBartłomiej Klocek /* width */ 113586106d6SBartłomiej Klocek ::-webkit-scrollbar { 114586106d6SBartłomiej Klocek width: 6px; 115586106d6SBartłomiej Klocek } 116586106d6SBartłomiej Klocek 117586106d6SBartłomiej Klocek /* Track */ 118586106d6SBartłomiej Klocek ::-webkit-scrollbar-track { 1198bf05203SJon Samp background: transparent; 120586106d6SBartłomiej Klocek cursor: pointer; 121586106d6SBartłomiej Klocek } 122586106d6SBartłomiej Klocek 123586106d6SBartłomiej Klocek /* Handle */ 124586106d6SBartłomiej Klocek ::-webkit-scrollbar-thumb { 125be43ea08SBartosz Kaszubowski background: ${theme.palette.gray5}; 126586106d6SBartłomiej Klocek } 127586106d6SBartłomiej Klocek 128586106d6SBartłomiej Klocek /* Handle on hover */ 129586106d6SBartłomiej Klocek ::-webkit-scrollbar-thumb:hover { 130be43ea08SBartosz Kaszubowski background: ${theme.palette.gray6}; 131586106d6SBartłomiej Klocek } 132586106d6SBartłomiej Klocek 133d04fd43fSBartosz Kaszubowski @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) { 134586106d6SBartłomiej Klocek overflow-y: auto; 135586106d6SBartłomiej Klocek } 136586106d6SBartłomiej Klocek`; 137586106d6SBartłomiej Klocek 138586106d6SBartłomiej Klocekconst STYLES_CENTER_WRAPPER = css` 139586106d6SBartłomiej Klocek max-width: 1200px; 140586106d6SBartłomiej Klocek margin: auto; 141586106d6SBartłomiej Klocek`; 142586106d6SBartłomiej Klocek 143d04fd43fSBartosz Kaszubowskiconst STYLES_HIDDEN = css` 144d04fd43fSBartosz Kaszubowski display: none; 145d04fd43fSBartosz Kaszubowski`; 146d04fd43fSBartosz Kaszubowski 147b8b69ebeSBartosz Kaszubowskitype ScrollContainerProps = React.PropsWithChildren<{ 148d04fd43fSBartosz Kaszubowski className?: string; 149586106d6SBartłomiej Klocek scrollPosition?: number; 150586106d6SBartłomiej Klocek scrollHandler?: () => void; 151b8b69ebeSBartosz Kaszubowski}>; 152586106d6SBartłomiej Klocek 153586106d6SBartłomiej Klocekclass ScrollContainer extends React.Component<ScrollContainerProps> { 154586106d6SBartłomiej Klocek scrollRef = React.createRef<HTMLDivElement>(); 155586106d6SBartłomiej Klocek 156586106d6SBartłomiej Klocek componentDidMount() { 157586106d6SBartłomiej Klocek if (this.props.scrollPosition && this.scrollRef.current) { 158586106d6SBartłomiej Klocek this.scrollRef.current.scrollTop = this.props.scrollPosition; 159586106d6SBartłomiej Klocek } 160586106d6SBartłomiej Klocek } 161586106d6SBartłomiej Klocek 162586106d6SBartłomiej Klocek public getScrollTop = () => { 163586106d6SBartłomiej Klocek return this.scrollRef.current?.scrollTop ?? 0; 164586106d6SBartłomiej Klocek }; 165586106d6SBartłomiej Klocek 166586106d6SBartłomiej Klocek public getScrollRef = () => { 167586106d6SBartłomiej Klocek return this.scrollRef; 168586106d6SBartłomiej Klocek }; 169586106d6SBartłomiej Klocek 170586106d6SBartłomiej Klocek render() { 171586106d6SBartłomiej Klocek return ( 172d04fd43fSBartosz Kaszubowski <div 173d04fd43fSBartosz Kaszubowski css={STYLES_SCROLL_CONTAINER} 174d04fd43fSBartosz Kaszubowski className={this.props.className} 175d04fd43fSBartosz Kaszubowski ref={this.scrollRef} 176d04fd43fSBartosz Kaszubowski onScroll={this.props.scrollHandler}> 177586106d6SBartłomiej Klocek {this.props.children} 178586106d6SBartłomiej Klocek </div> 179586106d6SBartłomiej Klocek ); 180586106d6SBartłomiej Klocek } 181586106d6SBartłomiej Klocek} 182586106d6SBartłomiej Klocek 183b8b69ebeSBartosz Kaszubowskitype Props = React.PropsWithChildren<{ 184586106d6SBartłomiej Klocek onContentScroll?: (scrollTop: number) => void; 185d04fd43fSBartosz Kaszubowski isMobileMenuVisible: boolean; 186586106d6SBartłomiej Klocek tocVisible: boolean; 187586106d6SBartłomiej Klocek header: React.ReactNode; 188586106d6SBartłomiej Klocek sidebarScrollPosition: number; 189586106d6SBartłomiej Klocek sidebar: React.ReactNode; 190d04fd43fSBartosz Kaszubowski sidebarActiveGroup: string; 191586106d6SBartłomiej Klocek sidebarRight: React.ReactElement; 192b8b69ebeSBartosz Kaszubowski}>; 193586106d6SBartłomiej Klocek 194586106d6SBartłomiej Klocekexport default class DocumentationNestedScrollLayout extends React.Component<Props> { 195586106d6SBartłomiej Klocek static defaultProps = { 196586106d6SBartłomiej Klocek sidebarScrollPosition: 0, 197586106d6SBartłomiej Klocek }; 198586106d6SBartłomiej Klocek 199586106d6SBartłomiej Klocek sidebarRef = React.createRef<ScrollContainer>(); 200586106d6SBartłomiej Klocek contentRef = React.createRef<ScrollContainer>(); 201586106d6SBartłomiej Klocek sidebarRightRef = React.createRef<ScrollContainer>(); 202586106d6SBartłomiej Klocek 203586106d6SBartłomiej Klocek public getSidebarScrollTop = () => { 204586106d6SBartłomiej Klocek return this.sidebarRef.current?.getScrollTop() ?? 0; 205586106d6SBartłomiej Klocek }; 206586106d6SBartłomiej Klocek 207586106d6SBartłomiej Klocek public getContentScrollTop = () => { 208586106d6SBartłomiej Klocek return this.contentRef.current?.getScrollTop() ?? 0; 209586106d6SBartłomiej Klocek }; 210586106d6SBartłomiej Klocek 211586106d6SBartłomiej Klocek render() { 212d04fd43fSBartosz Kaszubowski const { 213d04fd43fSBartosz Kaszubowski header, 214d04fd43fSBartosz Kaszubowski sidebar, 215d04fd43fSBartosz Kaszubowski sidebarActiveGroup, 216d04fd43fSBartosz Kaszubowski sidebarRight, 217d04fd43fSBartosz Kaszubowski sidebarScrollPosition, 218d04fd43fSBartosz Kaszubowski isMobileMenuVisible, 219d04fd43fSBartosz Kaszubowski tocVisible, 220d04fd43fSBartosz Kaszubowski children, 221d04fd43fSBartosz Kaszubowski } = this.props; 222586106d6SBartłomiej Klocek 223586106d6SBartłomiej Klocek return ( 224586106d6SBartłomiej Klocek <div css={STYLES_CONTAINER}> 225d04fd43fSBartosz Kaszubowski <div css={STYLES_HEADER}>{header}</div> 226586106d6SBartłomiej Klocek <div css={STYLES_CONTENT}> 227586106d6SBartłomiej Klocek <div css={[STYLES_SIDEBAR, STYLES_LEFT]}> 228d04fd43fSBartosz Kaszubowski <SidebarHead sidebarActiveGroup={sidebarActiveGroup} /> 229586106d6SBartłomiej Klocek <ScrollContainer ref={this.sidebarRef} scrollPosition={sidebarScrollPosition}> 230d04fd43fSBartosz Kaszubowski {sidebar} 231b4bb5e4dSBartosz Kaszubowski <SidebarFooter /> 232586106d6SBartłomiej Klocek </ScrollContainer> 233586106d6SBartłomiej Klocek </div> 234d04fd43fSBartosz Kaszubowski <div css={[STYLES_CENTER, isMobileMenuVisible && STYLES_HIDDEN]}> 235586106d6SBartłomiej Klocek <ScrollContainer ref={this.contentRef} scrollHandler={this.scrollHandler}> 236d04fd43fSBartosz Kaszubowski <div css={STYLES_CENTER_WRAPPER}>{children}</div> 237586106d6SBartłomiej Klocek </ScrollContainer> 238586106d6SBartłomiej Klocek </div> 239d04fd43fSBartosz Kaszubowski {tocVisible && ( 240586106d6SBartłomiej Klocek <div css={[STYLES_SIDEBAR, STYLES_RIGHT]}> 241586106d6SBartłomiej Klocek <ScrollContainer ref={this.sidebarRightRef}> 242d04fd43fSBartosz Kaszubowski {React.cloneElement(sidebarRight, { 243586106d6SBartłomiej Klocek selfRef: this.sidebarRightRef, 244586106d6SBartłomiej Klocek contentRef: this.contentRef, 245586106d6SBartłomiej Klocek })} 246586106d6SBartłomiej Klocek </ScrollContainer> 247586106d6SBartłomiej Klocek </div> 248586106d6SBartłomiej Klocek )} 249586106d6SBartłomiej Klocek </div> 250586106d6SBartłomiej Klocek </div> 251586106d6SBartłomiej Klocek ); 252586106d6SBartłomiej Klocek } 253586106d6SBartłomiej Klocek 254586106d6SBartłomiej Klocek private scrollHandler = () => { 255586106d6SBartłomiej Klocek this.props.onContentScroll && this.props.onContentScroll(this.getContentScrollTop()); 256586106d6SBartłomiej Klocek }; 257586106d6SBartłomiej Klocek} 258