mirror of
https://github.com/glitch-soc/mastodon.git
synced 2026-03-29 03:00:33 +02:00
Break ScrollableList component into parts (#38059)
This commit is contained in:
@@ -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';
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Children, cloneElement, PureComponent } from 'react';
|
import { Children, cloneElement, PureComponent } from 'react';
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
@@ -12,13 +11,14 @@ import { throttle } from 'lodash';
|
|||||||
|
|
||||||
import { ScrollContainer } from 'mastodon/containers/scroll_container';
|
import { ScrollContainer } from 'mastodon/containers/scroll_container';
|
||||||
|
|
||||||
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
|
import IntersectionObserverArticleContainer from '../../containers/intersection_observer_article_container';
|
||||||
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../features/ui/util/fullscreen';
|
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../../features/ui/util/fullscreen';
|
||||||
import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper';
|
import IntersectionObserverWrapper from '../../features/ui/util/intersection_observer_wrapper';
|
||||||
|
|
||||||
import { LoadMore } from './load_more';
|
import { LoadMore } from '../load_more';
|
||||||
import { LoadPending } from './load_pending';
|
import { LoadPending } from '../load_pending';
|
||||||
import { LoadingIndicator } from './loading_indicator';
|
import { LoadingIndicator } from '../loading_indicator';
|
||||||
|
import { Scrollable, ItemList } from './components';
|
||||||
|
|
||||||
const MOUSE_IDLE_DELAY = 300;
|
const MOUSE_IDLE_DELAY = 300;
|
||||||
|
|
||||||
@@ -336,24 +336,20 @@ class ScrollableList extends PureComponent {
|
|||||||
|
|
||||||
if (showLoading) {
|
if (showLoading) {
|
||||||
scrollableArea = (
|
scrollableArea = (
|
||||||
<div className='scrollable scrollable--flex' ref={this.setRef}>
|
<Scrollable ref={this.setRef}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div role='feed' className='item-list' />
|
<ItemList isLoading />
|
||||||
|
|
||||||
<div className='scrollable__append'>
|
|
||||||
<LoadingIndicator />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</Scrollable>
|
||||||
);
|
);
|
||||||
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
} else if (isLoading || childrenCount > 0 || numPending > 0 || hasMore || !emptyMessage) {
|
||||||
scrollableArea = (
|
scrollableArea = (
|
||||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
<Scrollable fullscreen={fullscreen} ref={this.setRef} onMouseMove={this.handleMouseMove}>
|
||||||
{prepend}
|
{prepend}
|
||||||
|
|
||||||
<div role='feed' className={classNames('item-list', className)}>
|
<ItemList className={className}>
|
||||||
{loadPending}
|
{loadPending}
|
||||||
|
|
||||||
{Children.map(this.props.children, (child, index) => (
|
{Children.map(this.props.children, (child, index) => (
|
||||||
@@ -378,14 +374,14 @@ class ScrollableList extends PureComponent {
|
|||||||
{loadMore}
|
{loadMore}
|
||||||
|
|
||||||
{!hasMore && append}
|
{!hasMore && append}
|
||||||
</div>
|
</ItemList>
|
||||||
|
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</Scrollable>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
scrollableArea = (
|
scrollableArea = (
|
||||||
<div className={classNames('scrollable scrollable--flex', { fullscreen })} ref={this.setRef}>
|
<Scrollable fullscreen={fullscreen} ref={this.setRef}>
|
||||||
{alwaysPrepend && prepend}
|
{alwaysPrepend && prepend}
|
||||||
|
|
||||||
<div className='empty-column-indicator'>
|
<div className='empty-column-indicator'>
|
||||||
@@ -393,7 +389,7 @@ class ScrollableList extends PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{footer}
|
{footer}
|
||||||
</div>
|
</Scrollable>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { cloneElement, Component } from 'react';
|
import { cloneElement, Component } from 'react';
|
||||||
|
|
||||||
import getRectFromEntry from '../features/ui/util/get_rect_from_entry';
|
import getRectFromEntry from '../../features/ui/util/get_rect_from_entry';
|
||||||
import scheduleIdleTask from '../features/ui/util/schedule_idle_task';
|
import scheduleIdleTask from '../../features/ui/util/schedule_idle_task';
|
||||||
|
import { Article } from './components';
|
||||||
|
|
||||||
// Diff these props in the "unrendered" state
|
// Diff these props in the "unrendered" state
|
||||||
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
|
const updateOnPropsForUnrendered = ['id', 'index', 'listLength', 'cachedHeight'];
|
||||||
@@ -108,23 +109,22 @@ export default class IntersectionObserverArticle extends Component {
|
|||||||
|
|
||||||
if (!isIntersecting && (isHidden || cachedHeight)) {
|
if (!isIntersecting && (isHidden || cachedHeight)) {
|
||||||
return (
|
return (
|
||||||
<article
|
<Article
|
||||||
ref={this.handleRef}
|
ref={this.handleRef}
|
||||||
aria-posinset={index + 1}
|
aria-posinset={index + 1}
|
||||||
aria-setsize={listLength}
|
aria-setsize={listLength}
|
||||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
tabIndex={-1}
|
|
||||||
>
|
>
|
||||||
{children && cloneElement(children, { hidden: true })}
|
{children && cloneElement(children, { hidden: true })}
|
||||||
</article>
|
</Article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 })}
|
{children && cloneElement(children, { hidden: false })}
|
||||||
</article>
|
</Article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { setHeight } from '../actions/height_cache';
|
import { setHeight } from '../actions/height_cache';
|
||||||
import IntersectionObserverArticle from '../components/intersection_observer_article';
|
import IntersectionObserverArticle from '../components/scrollable_list/intersection_observer_article';
|
||||||
|
|
||||||
const makeMapStateToProps = (state, props) => ({
|
const makeMapStateToProps = (state, props) => ({
|
||||||
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
|
cachedHeight: state.getIn(['height_cache', props.saveHeightKey, props.id]),
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ import { Account } from 'mastodon/components/account';
|
|||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
import { RemoteHint } from 'mastodon/components/remote_hint';
|
||||||
|
import {
|
||||||
|
Article,
|
||||||
|
ItemList,
|
||||||
|
Scrollable,
|
||||||
|
} from 'mastodon/components/scrollable_list/components';
|
||||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import Column from 'mastodon/features/ui/components/column';
|
||||||
@@ -115,7 +120,7 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
<Column>
|
<Column>
|
||||||
<ColumnBackButton />
|
<ColumnBackButton />
|
||||||
|
|
||||||
<div className='scrollable scrollable--flex'>
|
<Scrollable>
|
||||||
{accountId && (
|
{accountId && (
|
||||||
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
||||||
)}
|
)}
|
||||||
@@ -127,15 +132,17 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
defaultMessage='Collections'
|
defaultMessage='Collections'
|
||||||
/>
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
<section>
|
<ItemList>
|
||||||
{publicCollections.map((item, index) => (
|
{publicCollections.map((item, index) => (
|
||||||
<CollectionListItem
|
<CollectionListItem
|
||||||
key={item.id}
|
key={item.id}
|
||||||
collection={item}
|
collection={item}
|
||||||
withoutBorder={index === publicCollections.length - 1}
|
withoutBorder={index === publicCollections.length - 1}
|
||||||
|
positionInList={index + 1}
|
||||||
|
listSize={publicCollections.length}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</section>
|
</ItemList>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!featuredTags.isEmpty() && (
|
{!featuredTags.isEmpty() && (
|
||||||
@@ -146,9 +153,18 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
defaultMessage='Hashtags'
|
defaultMessage='Hashtags'
|
||||||
/>
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
{featuredTags.map((tag) => (
|
<ItemList>
|
||||||
<FeaturedTag key={tag.get('id')} tag={tag} account={acct} />
|
{featuredTags.map((tag, index) => (
|
||||||
))}
|
<Article
|
||||||
|
focusable
|
||||||
|
key={tag.get('id')}
|
||||||
|
aria-posinset={index + 1}
|
||||||
|
aria-setsize={featuredTags.size}
|
||||||
|
>
|
||||||
|
<FeaturedTag tag={tag} account={acct} />
|
||||||
|
</Article>
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!featuredAccountIds.isEmpty() && (
|
{!featuredAccountIds.isEmpty() && (
|
||||||
@@ -159,13 +175,22 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
defaultMessage='Profiles'
|
defaultMessage='Profiles'
|
||||||
/>
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
{featuredAccountIds.map((featuredAccountId) => (
|
<ItemList>
|
||||||
<Account key={featuredAccountId} id={featuredAccountId} />
|
{featuredAccountIds.map((featuredAccountId, index) => (
|
||||||
))}
|
<Article
|
||||||
|
focusable
|
||||||
|
key={featuredAccountId}
|
||||||
|
aria-posinset={index + 1}
|
||||||
|
aria-setsize={featuredAccountIds.size}
|
||||||
|
>
|
||||||
|
<Account id={featuredAccountId} />
|
||||||
|
</Article>
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RemoteHint accountId={accountId} />
|
<RemoteHint accountId={accountId} />
|
||||||
</div>
|
</Scrollable>
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
import type { ApiCollectionJSON } from 'mastodon/api_types/collections';
|
||||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||||
|
import { Article } from 'mastodon/components/scrollable_list/components';
|
||||||
|
|
||||||
import classes from './collection_list_item.module.scss';
|
import classes from './collection_list_item.module.scss';
|
||||||
import { CollectionMenu } from './collection_menu';
|
import { CollectionMenu } from './collection_menu';
|
||||||
@@ -68,19 +69,22 @@ export const CollectionMetaData: React.FC<{
|
|||||||
export const CollectionListItem: React.FC<{
|
export const CollectionListItem: React.FC<{
|
||||||
collection: ApiCollectionJSON;
|
collection: ApiCollectionJSON;
|
||||||
withoutBorder?: boolean;
|
withoutBorder?: boolean;
|
||||||
}> = ({ collection, withoutBorder }) => {
|
positionInList: number;
|
||||||
|
listSize: number;
|
||||||
|
}> = ({ collection, withoutBorder, positionInList, listSize }) => {
|
||||||
const { id, name } = collection;
|
const { id, name } = collection;
|
||||||
const linkId = useId();
|
const linkId = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<Article
|
||||||
|
focusable
|
||||||
className={classNames(
|
className={classNames(
|
||||||
classes.wrapper,
|
classes.wrapper,
|
||||||
'focusable',
|
|
||||||
withoutBorder && classes.wrapperWithoutBorder,
|
withoutBorder && classes.wrapperWithoutBorder,
|
||||||
)}
|
)}
|
||||||
tabIndex={-1}
|
|
||||||
aria-labelledby={linkId}
|
aria-labelledby={linkId}
|
||||||
|
aria-posinset={positionInList}
|
||||||
|
aria-setsize={listSize}
|
||||||
>
|
>
|
||||||
<div className={classes.content}>
|
<div className={classes.content}>
|
||||||
<h2 id={linkId}>
|
<h2 id={linkId}>
|
||||||
@@ -92,6 +96,6 @@ export const CollectionListItem: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CollectionMenu context='list' collection={collection} />
|
<CollectionMenu context='list' collection={collection} />
|
||||||
</article>
|
</Article>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,11 @@ import {
|
|||||||
LinkedDisplayName,
|
LinkedDisplayName,
|
||||||
} from 'mastodon/components/display_name';
|
} from 'mastodon/components/display_name';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import {
|
||||||
|
Article,
|
||||||
|
ItemList,
|
||||||
|
Scrollable,
|
||||||
|
} from 'mastodon/components/scrollable_list/components';
|
||||||
import { Tag } from 'mastodon/components/tags/tag';
|
import { Tag } from 'mastodon/components/tags/tag';
|
||||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
@@ -202,24 +206,27 @@ export const CollectionDetailPage: React.FC<{
|
|||||||
multiColumn={multiColumn}
|
multiColumn={multiColumn}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollableList
|
<Scrollable>
|
||||||
scrollKey='collection-detail'
|
{collection && <CollectionHeader collection={collection} />}
|
||||||
emptyMessage={intl.formatMessage(messages.empty)}
|
<ItemList
|
||||||
showLoading={isLoading}
|
isLoading={isLoading}
|
||||||
bindToDocument={!multiColumn}
|
emptyMessage={intl.formatMessage(messages.empty)}
|
||||||
alwaysPrepend
|
>
|
||||||
prepend={
|
{collection?.items.map(({ account_id }, index, items) => (
|
||||||
collection ? <CollectionHeader collection={collection} /> : null
|
<Article
|
||||||
}
|
key={account_id}
|
||||||
>
|
data-id={account_id}
|
||||||
{collection?.items.map(({ account_id }) => (
|
aria-posinset={index + 1}
|
||||||
<CollectionAccountItem
|
aria-setsize={items.length}
|
||||||
key={account_id}
|
>
|
||||||
accountId={account_id}
|
<CollectionAccountItem
|
||||||
collectionOwnerId={collection.account_id}
|
accountId={account_id}
|
||||||
/>
|
collectionOwnerId={collection.account_id}
|
||||||
))}
|
/>
|
||||||
</ScrollableList>
|
</Article>
|
||||||
|
))}
|
||||||
|
</ItemList>
|
||||||
|
</Scrollable>
|
||||||
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{pageTitle}</title>
|
<title>{pageTitle}</title>
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ import { EmptyState } from 'mastodon/components/empty_state';
|
|||||||
import { FormStack, Combobox } from 'mastodon/components/form_fields';
|
import { FormStack, Combobox } from 'mastodon/components/form_fields';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import { IconButton } from 'mastodon/components/icon_button';
|
import { IconButton } from 'mastodon/components/icon_button';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import {
|
||||||
|
Article,
|
||||||
|
ItemList,
|
||||||
|
Scrollable,
|
||||||
|
} from 'mastodon/components/scrollable_list/components';
|
||||||
import { useSearchAccounts } from 'mastodon/features/lists/use_search_accounts';
|
import { useSearchAccounts } from 'mastodon/features/lists/use_search_accounts';
|
||||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
@@ -390,9 +394,8 @@ export const CollectionAccounts: React.FC<{
|
|||||||
</Callout>
|
</Callout>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={classes.scrollableWrapper}>
|
<Scrollable className={classes.scrollableWrapper}>
|
||||||
<ScrollableList
|
<ItemList
|
||||||
scrollKey='collection-items'
|
|
||||||
className={classes.scrollableInner}
|
className={classes.scrollableInner}
|
||||||
emptyMessage={
|
emptyMessage={
|
||||||
<EmptyState
|
<EmptyState
|
||||||
@@ -413,18 +416,22 @@ export const CollectionAccounts: React.FC<{
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
// TODO: Re-add `bindToDocument={!multiColumn}`
|
|
||||||
>
|
>
|
||||||
{accountIds.map((accountId) => (
|
{accountIds.map((accountId, index) => (
|
||||||
<AddedAccountItem
|
<Article
|
||||||
key={accountId}
|
key={accountId}
|
||||||
accountId={accountId}
|
aria-posinset={index}
|
||||||
isRemovable={!isEditMode || !hasMinAccounts}
|
aria-setsize={accountIds.length}
|
||||||
onRemove={handleRemoveAccountItem}
|
>
|
||||||
/>
|
<AddedAccountItem
|
||||||
|
accountId={accountId}
|
||||||
|
isRemovable={!isEditMode || !hasMinAccounts}
|
||||||
|
onRemove={handleRemoveAccountItem}
|
||||||
|
/>
|
||||||
|
</Article>
|
||||||
))}
|
))}
|
||||||
</ScrollableList>
|
</ItemList>
|
||||||
</div>
|
</Scrollable>
|
||||||
</FormStack>
|
</FormStack>
|
||||||
{!isEditMode && (
|
{!isEditMode && (
|
||||||
<div className={classes.stickyFooter}>
|
<div className={classes.stickyFooter}>
|
||||||
|
|||||||
@@ -49,12 +49,7 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollableWrapper {
|
.scrollableWrapper,
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
margin-inline: -8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollableInner {
|
.scrollableInner {
|
||||||
margin-inline: -8px;
|
margin-inline: -8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import SquigglyArrow from '@/svg-icons/squiggly_arrow.svg?react';
|
|||||||
import { Column } from 'mastodon/components/column';
|
import { Column } from 'mastodon/components/column';
|
||||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import {
|
||||||
|
ItemList,
|
||||||
|
Scrollable,
|
||||||
|
} from 'mastodon/components/scrollable_list/components';
|
||||||
import {
|
import {
|
||||||
fetchAccountCollections,
|
fetchAccountCollections,
|
||||||
selectAccountCollections,
|
selectAccountCollections,
|
||||||
@@ -85,16 +88,18 @@ export const Collections: React.FC<{
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollableList
|
<Scrollable>
|
||||||
scrollKey='collections'
|
<ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}>
|
||||||
emptyMessage={emptyMessage}
|
{collections.map((item, index) => (
|
||||||
isLoading={status === 'loading'}
|
<CollectionListItem
|
||||||
bindToDocument={!multiColumn}
|
key={item.id}
|
||||||
>
|
collection={item}
|
||||||
{collections.map((item) => (
|
positionInList={index + 1}
|
||||||
<CollectionListItem key={item.id} collection={item} />
|
listSize={collections.length}
|
||||||
))}
|
/>
|
||||||
</ScrollableList>
|
))}
|
||||||
|
</ItemList>
|
||||||
|
</Scrollable>
|
||||||
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{intl.formatMessage(messages.heading)}</title>
|
<title>{intl.formatMessage(messages.heading)}</title>
|
||||||
|
|||||||
@@ -169,7 +169,9 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the sibling is a post or a 'follow suggestions' widget
|
// Check if the sibling is a post or a 'follow suggestions' widget
|
||||||
let targetElement = siblingItem.querySelector<HTMLElement>('.focusable');
|
let targetElement = siblingItem.matches('.focusable')
|
||||||
|
? siblingItem
|
||||||
|
: siblingItem.querySelector<HTMLElement>('.focusable');
|
||||||
|
|
||||||
// Otherwise, check if the item is a 'load more' button.
|
// Otherwise, check if the item is a 'load more' button.
|
||||||
if (!targetElement && siblingItem.matches('.load-more')) {
|
if (!targetElement && siblingItem.matches('.load-more')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user