Merge branch 'mobile-condensed' into 'main'

Condense feeds on mobile

See merge request soapbox-pub/soapbox!3047
This commit is contained in:
Alex Gleason 2024-06-01 18:00:46 +00:00
commit 1e9a86ee26
15 changed files with 86 additions and 27 deletions

View File

@ -12,6 +12,7 @@ import PullToRefresh from 'soapbox/components/pull-to-refresh';
import StatusList from 'soapbox/components/status-list'; import StatusList from 'soapbox/components/status-list';
import { Column } from 'soapbox/components/ui'; import { Column } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch, useTheme } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import toast from 'soapbox/toast'; import toast from 'soapbox/toast';
const messages = defineMessages({ const messages = defineMessages({
@ -40,6 +41,7 @@ const Bookmarks: React.FC<IBookmarks> = ({ params }) => {
const intl = useIntl(); const intl = useIntl();
const history = useHistory(); const history = useHistory();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
const folderId = params?.id; const folderId = params?.id;
@ -106,7 +108,7 @@ const Bookmarks: React.FC<IBookmarks> = ({ params }) => {
action={ action={
<DropdownMenu items={items} src={require('@tabler/icons/outline/dots-vertical.svg')} /> <DropdownMenu items={items} src={require('@tabler/icons/outline/dots-vertical.svg')} />
} }
transparent transparent={!isMobile}
> >
<PullToRefresh onRefresh={handleRefresh}> <PullToRefresh onRefresh={handleRefresh}>
<StatusList <StatusList
@ -117,7 +119,7 @@ const Bookmarks: React.FC<IBookmarks> = ({ params }) => {
isLoading={typeof isLoading === 'boolean' ? isLoading : true} isLoading={typeof isLoading === 'boolean' ? isLoading : true}
onLoadMore={() => handleLoadMore(dispatch, folderId)} onLoadMore={() => handleLoadMore(dispatch, folderId)}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</PullToRefresh> </PullToRefresh>
</Column> </Column>

View File

@ -6,6 +6,7 @@ import { useCommunityStream } from 'soapbox/api/hooks';
import PullToRefresh from 'soapbox/components/pull-to-refresh'; import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { Column } from 'soapbox/components/ui'; import { Column } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch, useSettings, useTheme } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useSettings, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -23,6 +24,7 @@ const CommunityTimeline = () => {
const next = useAppSelector(state => state.timelines.get('community')?.next); const next = useAppSelector(state => state.timelines.get('community')?.next);
const timelineId = 'community'; const timelineId = 'community';
const isMobile = useIsMobile();
const handleLoadMore = (maxId: string) => { const handleLoadMore = (maxId: string) => {
dispatch(expandCommunityTimeline({ url: next, maxId, onlyMedia })); dispatch(expandCommunityTimeline({ url: next, maxId, onlyMedia }));
@ -39,7 +41,7 @@ const CommunityTimeline = () => {
}, [onlyMedia]); }, [onlyMedia]);
return ( return (
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent> <Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent={!isMobile}>
<PullToRefresh onRefresh={handleRefresh}> <PullToRefresh onRefresh={handleRefresh}>
<Timeline <Timeline
className='black:p-4 black:sm:p-5' className='black:p-4 black:sm:p-5'
@ -48,7 +50,7 @@ const CommunityTimeline = () => {
prefix='home' prefix='home'
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</PullToRefresh> </PullToRefresh>
</Column> </Column>

View File

@ -8,7 +8,7 @@ import { useAccount } from 'soapbox/api/hooks';
import Hashtag from 'soapbox/components/hashtag'; import Hashtag from 'soapbox/components/hashtag';
import IconButton from 'soapbox/components/icon-button'; import IconButton from 'soapbox/components/icon-button';
import ScrollableList from 'soapbox/components/scrollable-list'; import ScrollableList from 'soapbox/components/scrollable-list';
import { HStack, Tabs, Text } from 'soapbox/components/ui'; import { HStack, Spinner, Tabs, Text } from 'soapbox/components/ui';
import AccountContainer from 'soapbox/containers/account-container'; import AccountContainer from 'soapbox/containers/account-container';
import StatusContainer from 'soapbox/containers/status-container'; import StatusContainer from 'soapbox/containers/status-container';
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account'; import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder-account';
@ -172,6 +172,8 @@ const SearchResults = () => {
/> />
</div> </div>
); );
} else {
noResultsMessage = <Spinner />;
} }
} }
@ -224,7 +226,7 @@ const SearchResults = () => {
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
placeholderComponent={placeholderComponent} placeholderComponent={placeholderComponent}
placeholderCount={20} placeholderCount={20}
className={clsx({ listClassName={clsx({
'divide-gray-200 dark:divide-gray-800 divide-solid divide-y': selectedFilter === 'statuses', 'divide-gray-200 dark:divide-gray-800 divide-solid divide-y': selectedFilter === 'statuses',
})} })}
itemClassName={clsx({ itemClassName={clsx({

View File

@ -8,6 +8,7 @@ import List, { ListItem } from 'soapbox/components/list';
import { Column, Toggle } from 'soapbox/components/ui'; import { Column, Toggle } from 'soapbox/components/ui';
import Timeline from 'soapbox/features/ui/components/timeline'; import Timeline from 'soapbox/features/ui/components/timeline';
import { useAppDispatch, useAppSelector, useFeatures, useLoggedIn, useTheme } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useFeatures, useLoggedIn, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
interface IHashtagTimeline { interface IHashtagTimeline {
params?: { params?: {
@ -24,6 +25,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
const next = useAppSelector(state => state.timelines.get(`hashtag:${id}`)?.next); const next = useAppSelector(state => state.timelines.get(`hashtag:${id}`)?.next);
const { isLoggedIn } = useLoggedIn(); const { isLoggedIn } = useLoggedIn();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
const handleLoadMore = (maxId: string) => { const handleLoadMore = (maxId: string) => {
dispatch(expandHashtagTimeline(id, { url: next, maxId })); dispatch(expandHashtagTimeline(id, { url: next, maxId }));
@ -50,7 +52,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
}, [id]); }, [id]);
return ( return (
<Column label={`#${id}`} transparent> <Column label={`#${id}`} transparent={!isMobile}>
{features.followHashtags && isLoggedIn && ( {features.followHashtags && isLoggedIn && (
<List> <List>
<ListItem <ListItem
@ -69,7 +71,7 @@ export const HashtagTimeline: React.FC<IHashtagTimeline> = ({ params }) => {
timelineId={`hashtag:${id}`} timelineId={`hashtag:${id}`}
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />} emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</Column> </Column>
); );

View File

@ -7,6 +7,7 @@ import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { Column, Stack, Text } from 'soapbox/components/ui'; import { Column, Stack, Text } from 'soapbox/components/ui';
import Timeline from 'soapbox/features/ui/components/timeline'; import Timeline from 'soapbox/features/ui/components/timeline';
import { useAppSelector, useAppDispatch, useFeatures, useInstance, useTheme } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useFeatures, useInstance, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' }, title: { id: 'column.home', defaultMessage: 'Home' },
@ -20,6 +21,7 @@ const HomeTimeline: React.FC = () => {
const theme = useTheme(); const theme = useTheme();
const polling = useRef<NodeJS.Timeout | null>(null); const polling = useRef<NodeJS.Timeout | null>(null);
const isMobile = useIsMobile();
const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true); const isPartial = useAppSelector(state => state.timelines.get('home')?.isPartial === true);
const next = useAppSelector(state => state.timelines.get('home')?.next); const next = useAppSelector(state => state.timelines.get('home')?.next);
@ -60,14 +62,14 @@ const HomeTimeline: React.FC = () => {
}, [isPartial]); }, [isPartial]);
return ( return (
<Column label={intl.formatMessage(messages.title)} transparent withHeader={false}> <Column className='py-0' label={intl.formatMessage(messages.title)} transparent={!isMobile} withHeader={false}>
<PullToRefresh onRefresh={handleRefresh}> <PullToRefresh onRefresh={handleRefresh}>
<Timeline <Timeline
className='black:p-4 black:sm:p-5' className='black:p-4 black:sm:p-5'
scrollKey='home_timeline' scrollKey='home_timeline'
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
timelineId='home' timelineId='home'
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
showAds showAds
emptyMessage={ emptyMessage={
<Stack space={1}> <Stack space={1}>

View File

@ -6,6 +6,7 @@ import { useCommunityStream } from 'soapbox/api/hooks';
import PullToRefresh from 'soapbox/components/pull-to-refresh'; import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { Column } from 'soapbox/components/ui'; import { Column } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch, useInstance, useTheme } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useInstance, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import AboutPage from '../about'; import AboutPage from '../about';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -16,6 +17,7 @@ const LandingTimeline = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const instance = useInstance(); const instance = useInstance();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
const timelineEnabled = !instance.pleroma.metadata.restrict_unauthenticated.timelines.local; const timelineEnabled = !instance.pleroma.metadata.restrict_unauthenticated.timelines.local;
const next = useAppSelector(state => state.timelines.get('community')?.next); const next = useAppSelector(state => state.timelines.get('community')?.next);
@ -43,7 +45,7 @@ const LandingTimeline = () => {
}, []); }, []);
return ( return (
<Column transparent withHeader={false}> <Column transparent={!isMobile} withHeader={false}>
<div className='my-12 mb-16 px-4 sm:mb-20'> <div className='my-12 mb-16 px-4 sm:mb-20'>
<SiteBanner /> <SiteBanner />
</div> </div>
@ -57,7 +59,7 @@ const LandingTimeline = () => {
prefix='home' prefix='home'
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />} emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</PullToRefresh> </PullToRefresh>
) : ( ) : (

View File

@ -9,6 +9,7 @@ import { useListStream } from 'soapbox/api/hooks';
import MissingIndicator from 'soapbox/components/missing-indicator'; import MissingIndicator from 'soapbox/components/missing-indicator';
import { Column, Button, Spinner } from 'soapbox/components/ui'; import { Column, Button, Spinner } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useTheme } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -16,6 +17,7 @@ const ListTimeline: React.FC = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
const list = useAppSelector((state) => state.lists.get(id)); const list = useAppSelector((state) => state.lists.get(id));
const next = useAppSelector(state => state.timelines.get(`list:${id}`)?.next); const next = useAppSelector(state => state.timelines.get(`list:${id}`)?.next);
@ -60,14 +62,14 @@ const ListTimeline: React.FC = () => {
); );
return ( return (
<Column label={title} transparent> <Column label={title} transparent={!isMobile}>
<Timeline <Timeline
className='black:p-4 black:sm:p-5' className='black:p-4 black:sm:p-5'
scrollKey='list_timeline' scrollKey='list_timeline'
timelineId={`list:${id}`} timelineId={`list:${id}`}
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</Column> </Column>
); );

View File

@ -8,6 +8,7 @@ import { usePublicStream } from 'soapbox/api/hooks';
import PullToRefresh from 'soapbox/components/pull-to-refresh'; import PullToRefresh from 'soapbox/components/pull-to-refresh';
import { Accordion, Column } from 'soapbox/components/ui'; import { Accordion, Column } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch, useInstance, useSettings, useTheme, useFeatures } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useInstance, useSettings, useTheme, useFeatures } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import PinnedHostsPicker from '../remote-timeline/components/pinned-hosts-picker'; import PinnedHostsPicker from '../remote-timeline/components/pinned-hosts-picker';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -29,6 +30,7 @@ const PublicTimeline = () => {
const next = useAppSelector(state => state.timelines.get('public')?.next); const next = useAppSelector(state => state.timelines.get('public')?.next);
const timelineId = 'public'; const timelineId = 'public';
const isMobile = useIsMobile();
const explanationBoxExpanded = settings.explanationBox; const explanationBoxExpanded = settings.explanationBox;
const showExplanationBox = settings.showExplanationBox && !features.nostr; const showExplanationBox = settings.showExplanationBox && !features.nostr;
@ -56,7 +58,7 @@ const PublicTimeline = () => {
}, [onlyMedia]); }, [onlyMedia]);
return ( return (
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent> <Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)} transparent={!isMobile}>
<PinnedHostsPicker /> <PinnedHostsPicker />
{showExplanationBox && ( {showExplanationBox && (
@ -96,7 +98,7 @@ const PublicTimeline = () => {
prefix='home' prefix='home'
onLoadMore={handleLoadMore} onLoadMore={handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />} emptyMessage={<FormattedMessage id='empty_column.public' defaultMessage='There is nothing here! Write something publicly, or manually follow users from other servers to fill it up' />}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</PullToRefresh> </PullToRefresh>
</Column> </Column>

View File

@ -8,6 +8,7 @@ import { expandStatusQuotes, fetchStatusQuotes } from 'soapbox/actions/status-qu
import StatusList from 'soapbox/components/status-list'; import StatusList from 'soapbox/components/status-list';
import { Column } from 'soapbox/components/ui'; import { Column } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useTheme } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.quotes', defaultMessage: 'Post quotes' }, heading: { id: 'column.quotes', defaultMessage: 'Post quotes' },
@ -21,6 +22,7 @@ const Quotes: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const { statusId } = useParams<{ statusId: string }>(); const { statusId } = useParams<{ statusId: string }>();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
const statusIds = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'items'], ImmutableOrderedSet<string>())); const statusIds = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'items'], ImmutableOrderedSet<string>()));
const isLoading = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'isLoading'], true)); const isLoading = useAppSelector((state) => state.status_lists.getIn([`quotes:${statusId}`, 'isLoading'], true));
@ -37,7 +39,7 @@ const Quotes: React.FC = () => {
const emptyMessage = <FormattedMessage id='empty_column.quotes' defaultMessage='This post has not been quoted yet.' />; const emptyMessage = <FormattedMessage id='empty_column.quotes' defaultMessage='This post has not been quoted yet.' />;
return ( return (
<Column label={intl.formatMessage(messages.heading)} transparent> <Column label={intl.formatMessage(messages.heading)} transparent={!isMobile}>
<StatusList <StatusList
className='black:p-4 black:sm:p-5' className='black:p-4 black:sm:p-5'
statusIds={statusIds as ImmutableOrderedSet<string>} statusIds={statusIds as ImmutableOrderedSet<string>}
@ -47,7 +49,7 @@ const Quotes: React.FC = () => {
onLoadMore={() => handleLoadMore(statusId, dispatch)} onLoadMore={() => handleLoadMore(statusId, dispatch)}
onRefresh={handleRefresh} onRefresh={handleRefresh}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</Column> </Column>
); );

View File

@ -7,6 +7,7 @@ import { useRemoteStream } from 'soapbox/api/hooks';
import IconButton from 'soapbox/components/icon-button'; import IconButton from 'soapbox/components/icon-button';
import { Column, HStack, Text } from 'soapbox/components/ui'; import { Column, HStack, Text } from 'soapbox/components/ui';
import { useAppSelector, useAppDispatch, useSettings, useTheme } from 'soapbox/hooks'; import { useAppSelector, useAppDispatch, useSettings, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -32,6 +33,7 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
const next = useAppSelector(state => state.timelines.get('remote')?.next); const next = useAppSelector(state => state.timelines.get('remote')?.next);
const pinned = settings.remote_timeline.pinnedHosts.includes(instance); const pinned = settings.remote_timeline.pinnedHosts.includes(instance);
const isMobile = useIsMobile();
const handleCloseClick: React.MouseEventHandler = () => { const handleCloseClick: React.MouseEventHandler = () => {
history.push('/timeline/fediverse'); history.push('/timeline/fediverse');
@ -48,7 +50,7 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
}, [onlyMedia]); }, [onlyMedia]);
return ( return (
<Column label={instance} transparent> <Column label={instance} transparent={!isMobile}>
{instance && <PinnedHostsPicker host={instance} />} {instance && <PinnedHostsPicker host={instance} />}
{!pinned && ( {!pinned && (
@ -76,7 +78,7 @@ const RemoteTimeline: React.FC<IRemoteTimeline> = ({ params }) => {
values={{ instance }} values={{ instance }}
/> />
} }
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</Column> </Column>
); );

View File

@ -4,6 +4,7 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { importFetchedStatuses } from 'soapbox/actions/importer'; import { importFetchedStatuses } from 'soapbox/actions/importer';
import { expandTimelineSuccess } from 'soapbox/actions/timelines'; import { expandTimelineSuccess } from 'soapbox/actions/timelines';
import { useAppDispatch, useTheme } from 'soapbox/hooks'; import { useAppDispatch, useTheme } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import { Column } from '../../components/ui'; import { Column } from '../../components/ui';
import Timeline from '../ui/components/timeline'; import Timeline from '../ui/components/timeline';
@ -32,6 +33,7 @@ const TestTimeline: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const theme = useTheme(); const theme = useTheme();
const isMobile = useIsMobile();
React.useEffect(() => { React.useEffect(() => {
dispatch(importFetchedStatuses(MOCK_STATUSES)); dispatch(importFetchedStatuses(MOCK_STATUSES));
@ -39,12 +41,12 @@ const TestTimeline: React.FC = () => {
}, []); }, []);
return ( return (
<Column label={intl.formatMessage(messages.title)} transparent> <Column label={intl.formatMessage(messages.title)} transparent={!isMobile}>
<Timeline <Timeline
scrollKey={`${timelineId}_timeline`} scrollKey={`${timelineId}_timeline`}
timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`} timelineId={`${timelineId}${onlyMedia ? ':media' : ''}`}
emptyMessage={<FormattedMessage id='empty_column.test' defaultMessage='The test timeline is empty.' />} emptyMessage={<FormattedMessage id='empty_column.test' defaultMessage='The test timeline is empty.' />}
divideType={theme === 'black' ? 'border' : 'space'} divideType={(theme === 'black' || isMobile) ? 'border' : 'space'}
/> />
</Column> </Column>
); );

View File

@ -11,6 +11,7 @@ import SiteLogo from 'soapbox/components/site-logo';
import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui'; import { Avatar, Button, Form, HStack, IconButton, Input, Tooltip } from 'soapbox/components/ui';
import Search from 'soapbox/features/compose/components/search'; import Search from 'soapbox/features/compose/components/search';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useRegistrationStatus } from 'soapbox/hooks'; import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useRegistrationStatus } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import { isStandalone } from 'soapbox/utils/state'; import { isStandalone } from 'soapbox/utils/state';
import ProfileDropdown from './profile-dropdown'; import ProfileDropdown from './profile-dropdown';
@ -33,6 +34,7 @@ const Navbar = () => {
const { isOpen } = useRegistrationStatus(); const { isOpen } = useRegistrationStatus();
const { account } = useOwnAccount(); const { account } = useOwnAccount();
const node = useRef(null); const node = useRef(null);
const isMobile = useIsMobile();
const [isLoading, setLoading] = useState<boolean>(false); const [isLoading, setLoading] = useState<boolean>(false);
const [username, setUsername] = useState<string>(''); const [username, setUsername] = useState<string>('');
@ -72,7 +74,14 @@ const Navbar = () => {
if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />; if (mfaToken) return <Redirect to={`/login?token=${encodeURIComponent(mfaToken)}`} />;
return ( return (
<nav className='sticky top-0 z-50 bg-white shadow black:border-b black:border-b-gray-800 black:bg-black dark:bg-primary-900' ref={node} data-testid='navbar'> <nav
className={clsx(
'sticky top-0 z-50 border-gray-200 bg-white shadow black:border-b black:border-b-gray-800 black:bg-black dark:border-gray-800 dark:bg-primary-900',
{ 'border-b': isMobile },
)}
ref={node}
data-testid='navbar'
>
<div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'> <div className='mx-auto max-w-7xl px-2 sm:px-6 lg:px-8'>
<div className='relative flex h-12 justify-between lg:h-16'> <div className='relative flex h-12 justify-between lg:h-16'>
{account && ( {account && (

6
src/hooks/useIsMobile.ts Normal file
View File

@ -0,0 +1,6 @@
import { useScreenWidth } from './useScreenWidth';
export function useIsMobile() {
const screenWidth = useScreenWidth();
return screenWidth <= 581;
}

View File

@ -0,0 +1,19 @@
import { useState, useEffect } from 'react';
export function useScreenWidth() {
const [screenWidth, setScreenWidth] = useState(window.innerWidth);
useEffect(() => {
const checkWindowSize = () => {
setScreenWidth(window.innerWidth);
};
window.addEventListener('resize', checkWindowSize);
return () => {
window.removeEventListener('resize', checkWindowSize);
};
}, []);
return screenWidth;
}

View File

@ -17,6 +17,7 @@ import {
AnnouncementsPanel, AnnouncementsPanel,
} from 'soapbox/features/ui/util/async-components'; } from 'soapbox/features/ui/util/async-components';
import { useAppSelector, useOwnAccount, useFeatures, useSoapboxConfig, useDraggedFiles, useAppDispatch } from 'soapbox/hooks'; import { useAppSelector, useOwnAccount, useFeatures, useSoapboxConfig, useDraggedFiles, useAppDispatch } from 'soapbox/hooks';
import { useIsMobile } from 'soapbox/hooks/useIsMobile';
import { Avatar, Card, CardBody, HStack, Layout } from '../components/ui'; import { Avatar, Card, CardBody, HStack, Layout } from '../components/ui';
import ComposeForm from '../features/compose/components/compose-form'; import ComposeForm from '../features/compose/components/compose-form';
@ -36,6 +37,7 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
const composeId = 'home'; const composeId = 'home';
const composeBlock = useRef<HTMLDivElement>(null); const composeBlock = useRef<HTMLDivElement>(null);
const isMobile = useIsMobile();
const hasPatron = soapboxConfig.extensions.getIn(['patron', 'enabled']) === true; const hasPatron = soapboxConfig.extensions.getIn(['patron', 'enabled']) === true;
const hasCrypto = typeof soapboxConfig.cryptoAddresses.getIn([0, 'ticker']) === 'string'; const hasCrypto = typeof soapboxConfig.cryptoAddresses.getIn([0, 'ticker']) === 'string';
@ -50,12 +52,13 @@ const HomePage: React.FC<IHomePage> = ({ children }) => {
return ( return (
<> <>
<Layout.Main className='space-y-3 pt-3 black:space-y-0 sm:pt-0 dark:divide-gray-800'> <Layout.Main className={clsx('black:space-y-0 dark:divide-gray-800', { 'pt-3 sm:pt-0 space-y-3': !isMobile })}>
{me && ( {me && (
<Card <Card
className={clsx('relative z-[1] transition black:border-b black:border-gray-800', { className={clsx('relative z-[1] border-gray-200 transition black:border-b black:border-gray-800 dark:border-gray-800', {
'border-2 border-primary-600 border-dashed z-[99]': isDragging, 'border-2 border-primary-600 border-dashed z-[99]': isDragging,
'ring-2 ring-offset-2 ring-primary-600': isDraggedOver, 'ring-2 ring-offset-2 ring-primary-600': isDraggedOver,
'border-b': isMobile,
})} })}
variant='rounded' variant='rounded'
ref={composeBlock} ref={composeBlock}