Break ScrollableList component into parts (#38059)
This commit is contained in:
1 parent
3fbb7424fa
commit
a70079968c
11 files changed
+219
-93
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react';
|
||||
import { Children, forwardRef } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { LoadingIndicator } from '../loading_indicator';
|
||||
|
||||
export const Scrollable = forwardRef<
|
||||
HTMLDivElement,
|
||||
ComponentPropsWithoutRef<'div'> & {
|
||||
flex?: boolean;
|
||||
fullscreen?: boolean;
|
||||
}
|
||||
>(({ flex = true, fullscreen, className, children, ...otherProps }, ref) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'scrollable',
|
||||
{ 'scrollable--flex': flex, fullscreen },
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Scrollable.displayName = 'Scrollable';
|
||||
|
||||
export const ItemList = forwardRef<
|
||||
HTMLDivElement,
|
||||
ComponentPropsWithoutRef<'div'> & {
|
||||
isLoading?: boolean;
|
||||
emptyMessage?: React.ReactNode;
|
||||
}
|
||||
>(({ isLoading, emptyMessage, className, children, ...otherProps }, ref) => {
|
||||
if (Children.count(children) === 0 && emptyMessage) {
|
||||
return <div className='empty-column-indicator'>{emptyMessage}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
role='feed'
|
||||
className={classNames('item-list', className)}
|
||||
ref={ref}
|
||||
{...otherProps}
|
||||
>
|
||||
{!isLoading && children}
|
||||
</div>
|
||||
{isLoading && (
|
||||
<div className='scrollable__append'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
ItemList.displayName = 'ItemList';
|
||||
|
||||
export const Article = forwardRef<
|
||||
HTMLElement,
|
||||
ComponentPropsWithoutRef<'article'> & {
|
||||
focusable?: boolean;
|
||||
'data-id'?: string;
|
||||
'aria-posinset': number;
|
||||
'aria-setsize': number;
|
||||
}
|
||||
>(({ focusable, className, children, ...otherProps }, ref) => {
|
||||
return (
|
||||
<article
|
||||
ref={ref}
|
||||
className={classNames(className, { focusable })}
|
||||
tabIndex={-1}
|
||||
{...otherProps}
|
||||
>
|
||||
{children}
|
||||
</article>
|
||||
);
|
||||
});
|
||||
|
||||
Article.displayName = 'Article';
|
||||
+16
-20
@@ -1,7 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { Children, cloneElement, PureComponent } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
@@ -12,13 +11,14 @@ import { throttle } from 'lodash';
|
||||
|
||||
import { ScrollContainer } from 'mastodon/containers/scroll_container';
|
||||
|
||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
||||
import IntersectionObserverArticleContainer from '../../containers/intersection_observer_article_container';
|
||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
|
||||
import IntersectionObserverWrapper from '../../features/ui/util/intersection_observer_wrapper';
|
||||
|
||||
import { LoadMore } from './load_more';
|
||||
import { LoadPending } from './load_pending';
|
||||
import { LoadingIndicator } from './loading_indicator';
|
||||
import { LoadMore } from '../load_more';
|
||||
import { LoadPending } from '../load_pending';
|
||||
import { LoadingIndicator } from '../loading_indicator';
|
||||
import { Scrollable, ItemList } from './components';
|
||||
|
||||
const MOUSE_IDLE_DELAY = 300;
|
||||
|
||||
@@ -336,24 +336,20 @@ class ScrollableList extends PureComponent {
|
||||
|
||||
if (showLoading) {
|
||||
scrollableArea = (
|
||||
<div className='scrollable scrollable--flex' ref={this.setRef}>
|
||||
<Scrollable ref={this.setRef}>
|
||||
{prepend}
|
||||
|
||||
<div role='feed' className='item-list' />
|
||||
|
||||
<div className='scrollable__append'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
<ItemList isLoading />
|
||||
|
||||
{footer}
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||
<Scrollable fullscreen={fullscreen} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||
{prepend}
|
||||
|
||||
<div role='feed' className={classNames('item-list', className)}>
|
||||
<ItemList className={className}>
|
||||
{loadPending}
|
||||
|
||||
{Children.map(this.props.children, (child, index) => (
|
||||
@@ -378,14 +374,14 @@ class ScrollableList extends PureComponent {
|
||||
{loadMore}
|
||||
|
||||
{!hasMore && append}
|
||||
</div>
|
||||
</ItemList>
|
||||
|
||||
{footer}
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
} else {
|
||||
scrollableArea = (
|
||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef}>
|
||||
<Scrollable fullscreen={fullscreen} ref={this.setRef}>
|
||||
{alwaysPrepend && prepend}
|
||||
|
||||
<div className='empty-column-indicator'>
|
||||
@@ -393,7 +389,7 @@ class ScrollableList extends PureComponent {
|
||||
</div>
|
||||
|
||||
{footer}
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,8 +1,9 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { cloneElement, Component } from 'react';
|
||||
|
||||
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
||||
import getRectFromEntry from '../../features/ui/util/get_rect_from_entry';
|
||||
import scheduleIdleTask from '../../features/ui/util/schedule_idle_task';
|
||||
import { Article } from './components';
|
||||
|
||||
// Diff these props in the "unrendered" state
|
||||
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
|
||||
@@ -108,23 +109,22 @@ export default class IntersectionObserverArticle extends Component {
|
||||
|
||||
if (!isIntersecting && (isHidden || cachedHeight)) {
|
||||
return (
|
||||
<article
|
||||
<Article
|
||||
ref={this.handleRef}
|
||||
aria-posinset={index + 1}
|
||||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
data-id={id}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children && cloneElement(children, { hidden: true })}
|
||||
</article>
|
||||
</Article>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={-1}>
|
||||
<Article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id}>
|
||||
{children && cloneElement(children, { hidden: false })}
|
||||
</article>
|
||||
</Article>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user